1use std::{
2 collections::{HashMap, HashSet},
3 io::Read,
4 sync::LazyLock,
5};
6
7use futures::TryFutureExt;
8use indexmap::IndexMap;
9use regex::{Captures, Regex};
10use serde::{Deserialize, Serialize};
11use toml::value::Table;
12use vector_lib::config::ComponentKey;
13
14use crate::{
15 config::{
16 loading::{deserialize_table, prepare_input, process::Process, ComponentHint, Loader},
17 SecretBackend,
18 },
19 secrets::SecretBackends,
20 signal,
21};
22
23pub static COLLECTOR: LazyLock<Regex> =
31 LazyLock::new(|| Regex::new(r"SECRET\[([[:word:]]+)\.([[:word:].-]+)\]").unwrap());
32
33#[derive(Debug, Default, Deserialize, Serialize)]
35pub(crate) struct SecretBackendOuter {
36 #[serde(default)]
37 pub(crate) secret: IndexMap<ComponentKey, SecretBackends>,
38}
39
40#[derive(Debug, Default, Deserialize, Serialize)]
42pub struct SecretBackendLoader {
43 backends: IndexMap<ComponentKey, SecretBackends>,
44 pub(crate) secret_keys: HashMap<String, HashSet<String>>,
45}
46
47impl SecretBackendLoader {
48 pub(crate) fn new() -> Self {
49 Self {
50 backends: IndexMap::new(),
51 secret_keys: HashMap::new(),
52 }
53 }
54
55 pub(crate) async fn retrieve(
56 &mut self,
57 signal_rx: &mut signal::SignalRx,
58 ) -> Result<HashMap<String, String>, String> {
59 let mut secrets: HashMap<String, String> = HashMap::new();
60
61 for (backend_name, keys) in &self.secret_keys {
62 let backend = self.backends
63 .get_mut(&ComponentKey::from(backend_name.clone()))
64 .ok_or_else(|| {
65 format!("Backend \"{backend_name}\" is required for secret retrieval but was not found in config.")
66 })?;
67
68 debug!(message = "Retrieving secrets from a backend.", backend = ?backend_name, keys = ?keys);
69 let backend_secrets = backend
70 .retrieve(keys.clone(), signal_rx)
71 .map_err(|e| {
72 format!("Error while retrieving secret from backend \"{backend_name}\": {e}.",)
73 })
74 .await?;
75
76 for (k, v) in backend_secrets {
77 trace!(message = "Successfully retrieved a secret.", backend = ?backend_name, key = ?k);
78 secrets.insert(format!("{backend_name}.{k}"), v);
79 }
80 }
81
82 Ok(secrets)
83 }
84
85 pub(crate) fn has_secrets_to_retrieve(&self) -> bool {
86 !self.secret_keys.is_empty()
87 }
88}
89
90impl Process for SecretBackendLoader {
91 fn prepare<R: Read>(&mut self, input: R) -> Result<String, Vec<String>> {
92 let config_string = prepare_input(input)?;
93 collect_secret_keys(&config_string, &mut self.secret_keys);
95 Ok(config_string)
96 }
97
98 fn merge(&mut self, table: Table, _: Option<ComponentHint>) -> Result<(), Vec<String>> {
99 if table.contains_key("secret") {
100 let additional = deserialize_table::<SecretBackendOuter>(table)?;
101 self.backends.extend(additional.secret);
102 }
103 Ok(())
104 }
105}
106
107impl Loader<SecretBackendLoader> for SecretBackendLoader {
108 fn take(self) -> SecretBackendLoader {
109 self
110 }
111}
112
113fn collect_secret_keys(input: &str, keys: &mut HashMap<String, HashSet<String>>) {
114 COLLECTOR.captures_iter(input).for_each(|cap| {
115 if let (Some(backend), Some(key)) = (cap.get(1), cap.get(2)) {
116 if let Some(keys) = keys.get_mut(backend.as_str()) {
117 keys.insert(key.as_str().to_string());
118 } else {
119 keys.insert(
120 backend.as_str().to_string(),
121 HashSet::from_iter(std::iter::once(key.as_str().to_string())),
122 );
123 }
124 }
125 });
126}
127
128pub fn interpolate(input: &str, secrets: &HashMap<String, String>) -> Result<String, Vec<String>> {
129 let mut errors = Vec::<String>::new();
130 let output = COLLECTOR
131 .replace_all(input, |caps: &Captures<'_>| {
132 caps.get(1)
133 .and_then(|b| caps.get(2).map(|k| (b, k)))
134 .and_then(|(b, k)| secrets.get(&format!("{}.{}", b.as_str(), k.as_str())))
135 .cloned()
136 .unwrap_or_else(|| {
137 errors.push(format!(
138 "Unable to find secret replacement for {}.",
139 caps.get(0).unwrap().as_str()
140 ));
141 "".to_string()
142 })
143 })
144 .into_owned();
145 if errors.is_empty() {
146 Ok(output)
147 } else {
148 Err(errors)
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use std::collections::HashMap;
155
156 use indoc::indoc;
157
158 use super::{collect_secret_keys, interpolate};
159
160 #[test]
161 fn replacement() {
162 let secrets: HashMap<String, String> = vec![
163 ("a.secret.key".into(), "value".into()),
164 ("a...key".into(), "a...value".into()),
165 ]
166 .into_iter()
167 .collect();
168
169 assert_eq!(
170 Ok("value".into()),
171 interpolate("SECRET[a.secret.key]", &secrets)
172 );
173 assert_eq!(
174 Ok("value value".into()),
175 interpolate("SECRET[a.secret.key] SECRET[a.secret.key]", &secrets)
176 );
177
178 assert_eq!(
179 Ok("xxxvalueyyy".into()),
180 interpolate("xxxSECRET[a.secret.key]yyy", &secrets)
181 );
182 assert_eq!(
183 Ok("a...value".into()),
184 interpolate("SECRET[a...key]", &secrets)
185 );
186 assert_eq!(
187 Ok("xxxSECRET[non_matching_syntax]yyy".into()),
188 interpolate("xxxSECRET[non_matching_syntax]yyy", &secrets)
189 );
190 assert_eq!(
191 Err(vec![
192 "Unable to find secret replacement for SECRET[a.non.existing.key].".into()
193 ]),
194 interpolate("xxxSECRET[a.non.existing.key]yyy", &secrets)
195 );
196 }
197
198 #[test]
199 fn collection() {
200 let mut keys = HashMap::new();
201 collect_secret_keys(
202 indoc! {r"
203 SECRET[first_backend.secret_key]
204 SECRET[first_backend.secret-key]
205 SECRET[first_backend.another_secret_key]
206 SECRET[second_backend.secret_key]
207 SECRET[second_backend.secret.key]
208 SECRET[first_backend.a_third.secret_key]
209 SECRET[first_backend...an_extra_secret_key]
210 SECRET[non_matching_syntax]
211 SECRET[.non.matching.syntax]
212 "},
213 &mut keys,
214 );
215 assert_eq!(keys.len(), 2);
216 assert!(keys.contains_key("first_backend"));
217 assert!(keys.contains_key("second_backend"));
218
219 let first_backend_keys = keys.get("first_backend").unwrap();
220 assert_eq!(first_backend_keys.len(), 5);
221 assert!(first_backend_keys.contains("secret_key"));
222 assert!(first_backend_keys.contains("secret-key"));
223 assert!(first_backend_keys.contains("another_secret_key"));
224 assert!(first_backend_keys.contains("a_third.secret_key"));
225 assert!(first_backend_keys.contains("..an_extra_secret_key"));
226
227 let second_backend_keys = keys.get("second_backend").unwrap();
228 assert_eq!(second_backend_keys.len(), 2);
229 assert!(second_backend_keys.contains("secret_key"));
230 assert!(second_backend_keys.contains("secret.key"));
231 }
232
233 #[test]
234 fn collection_duplicates() {
235 let mut keys = HashMap::new();
236 collect_secret_keys(
237 indoc! {r"
238 SECRET[first_backend.secret_key]
239 SECRET[first_backend.secret_key]
240 "},
241 &mut keys,
242 );
243
244 let first_backend_keys = keys.get("first_backend").unwrap();
245 assert_eq!(first_backend_keys.len(), 1);
246 assert!(first_backend_keys.contains("secret_key"));
247 }
248}