config_secret/
secret.rs

1use std::{env, path::Path};
2
3use config::{ConfigError, File, Map, Source, Value, ValueKind};
4
5#[derive(Clone, Debug, Default)]
6pub struct EnvironmentSecretFile {
7    /// Optional prefix that will limit access to the environment to only keys that
8    /// begin with the defined prefix.
9    ///
10    /// A prefix with a separator of `_` is tested to be present on each key before its considered
11    /// to be part of the secret environment.
12    ///
13    /// For example, the key `CONFIG_DEBUG` would become `DEBUG` with a prefix of `config`.
14    prefix: Option<String>,
15
16    /// Optional character sequence that separates the prefix from the rest of the key
17    /// Defaults to `separator` or `_`
18    prefix_separator: Option<String>,
19
20    /// Suffix that will limit secrets in the environment to only keys that ends with the defined
21    /// prefix.
22    ///
23    /// A suffix with a separator of `_` is tested to be present on each key before its considered
24    /// to be part of the secret environment.
25    ///
26    /// The default value is `FILE`.
27    ///
28    /// For example, the key `CONFIG_FILE` would parse the file pointed in the variable and collect
29    /// the content config into the key `config`.
30    suffix: Option<String>,
31
32    /// Optional character sequence that separates the prefix from the rest of the key
33    /// Defaults to `separator` or `_`
34    suffix_separator: Option<String>,
35
36    /// Optional character sequence that separates each key segment in an environment key pattern.
37    /// Consider a nested configuration such as `redis.password`, a separator of `_` would allow
38    /// an environment key of `REDIS_PASSWORD` to match.
39    separator: Option<String>,
40
41    // Preserve the prefix while parsing
42    keep_prefix: bool,
43}
44
45impl EnvironmentSecretFile {
46    pub fn with_prefix(s: &str) -> Self {
47        Self {
48            prefix: Some(s.into()),
49            ..Self::default()
50        }
51    }
52
53    pub fn prefix(mut self, s: &str) -> Self {
54        self.prefix = Some(s.into());
55        self
56    }
57
58    pub fn prefix_separator(mut self, s: &str) -> Self {
59        self.prefix_separator = Some(s.into());
60        self
61    }
62
63    pub fn suffix(mut self, s: &str) -> Self {
64        self.suffix = Some(s.into());
65        self
66    }
67
68    pub fn suffix_separator(mut self, s: &str) -> Self {
69        self.suffix_separator = Some(s.into());
70        self
71    }
72
73    pub fn separator(mut self, s: &str) -> Self {
74        self.separator = Some(s.into());
75        self
76    }
77
78    pub fn keep_prefix(mut self, keep: bool) -> Self {
79        self.keep_prefix = keep;
80        self
81    }
82}
83
84impl Source for EnvironmentSecretFile {
85    fn clone_into_box(&self) -> Box<dyn Source + Send + Sync> {
86        Box::new((*self).clone())
87    }
88
89    fn collect(&self) -> Result<Map<String, Value>, ConfigError> {
90        let mut m = Map::new();
91
92        let separator = self.separator.as_deref().unwrap_or("");
93        let prefix_separator = match (self.prefix_separator.as_deref(), self.separator.as_deref()) {
94            (Some(pre), _) => pre,
95            (None, Some(sep)) => sep,
96            (None, None) => "_",
97        };
98        let suffix_separator = match (self.suffix_separator.as_deref(), self.separator.as_deref()) {
99            (Some(suf), _) => suf,
100            (None, Some(sep)) => sep,
101            (None, None) => "_",
102        };
103
104        let prefix_pattern = self
105            .prefix
106            .as_ref()
107            .map(|prefix| format!("{}{}", prefix, prefix_separator).to_lowercase());
108
109        let suffix = self.suffix.as_ref().map_or_else(|| "FILE", |s| s.as_str());
110        let suffix_pattern = format!("{}{}", suffix_separator, suffix).to_lowercase();
111
112        let full_pattern = if let Some(prefix) = self.prefix.as_ref() {
113            if prefix_separator == suffix_separator {
114                format!("{}{}{}", prefix, prefix_separator, suffix).to_lowercase()
115            } else {
116                format!("{}{}", prefix, suffix)
117            }
118        } else {
119            suffix.to_string()
120        };
121
122        let mut error: Option<ConfigError> = None;
123
124        env::vars().for_each(|(key, value): (String, String)| {
125            // Stop processing on error
126            if error.as_ref().is_some() {
127                return;
128            }
129
130            // Treat empty environment variables as unset
131            if value.is_empty() {
132                return;
133            }
134
135            let mut key = key.to_lowercase();
136
137            if key == full_pattern {
138                let path = Path::new(&value);
139                let file = File::from(path);
140                let map = file.collect();
141
142                match map {
143                    Ok(map) => {
144                        for (key, value) in map.into_iter() {
145                            m.insert(key, value);
146                        }
147                    }
148                    Err(err) => {
149                        error = Some(err);
150                    }
151                }
152
153                return;
154            }
155
156            // Check for prefix
157            if let Some(ref prefix_pattern) = prefix_pattern {
158                if key.starts_with(prefix_pattern) {
159                    if !self.keep_prefix {
160                        // Remove this prefix from the key
161                        key = key[prefix_pattern.len()..].to_string();
162                    }
163                } else {
164                    // Skip this key
165                    return;
166                }
167            }
168
169            // Check for suffix
170            if key.ends_with(&suffix_pattern) {
171                // Remove this suffix from the key
172                let len = key.len() - suffix_pattern.len();
173                key = key[..len].to_string();
174            } else {
175                // Skip this key
176                return;
177            }
178
179            // If separator is given replace with `.`
180            if !separator.is_empty() {
181                key = key.replace(separator, ".");
182            }
183
184            let path = Path::new(&value);
185            let file = File::from(path);
186            let map = file.collect();
187
188            match map {
189                Ok(map) => {
190                    let uri = format!("secret:{}:{}", key, value);
191                    m.insert(key, Value::new(Some(&uri), ValueKind::Table(map)));
192                }
193                Err(err) => {
194                    error = Some(err);
195                }
196            }
197        });
198
199        match error {
200            Some(err) => Err(err),
201            None => Ok(m),
202        }
203    }
204}