Skip to content

Commit 9f8b675

Browse files
vparfonovopenshift-merge-bot[bot]
authored andcommitted
LOG-7168: align file secret implementation with upstream
Signed-off-by: Vitalii Parfonov <[email protected]>
1 parent 0f22b51 commit 9f8b675

File tree

2 files changed

+63
-109
lines changed

2 files changed

+63
-109
lines changed

src/secrets/file.rs

Lines changed: 15 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
use std::collections::{HashMap, HashSet};
2-
use std::fs::File;
3-
use std::io::{ErrorKind, Read};
4-
use std::path::Path;
2+
use std::path::PathBuf;
53

64
use vector_lib::configurable::{component::GenerateConfig, configurable_component};
75

@@ -11,14 +9,14 @@ use crate::{config::SecretBackend, signal};
119
#[configurable_component(secrets("file"))]
1210
#[derive(Clone, Debug)]
1311
pub struct FileBackend {
14-
/// Base path to use for fetching secrets from files.
15-
pub base_path: String,
12+
/// File path to read secrets from.
13+
pub path: PathBuf,
1614
}
1715

1816
impl GenerateConfig for FileBackend {
1917
fn generate_config() -> toml::Value {
2018
toml::Value::try_from(FileBackend {
21-
base_path: String::from("/path/to/secrets"),
19+
path: PathBuf::from("/path/to/secret"),
2220
})
2321
.unwrap()
2422
}
@@ -28,99 +26,21 @@ impl SecretBackend for FileBackend {
2826
async fn retrieve(
2927
&mut self,
3028
secret_keys: HashSet<String>,
31-
_signal_rx: &mut signal::SignalRx,
29+
_: &mut signal::SignalRx,
3230
) -> crate::Result<HashMap<String, String>> {
33-
retrieve_secrets(&self.base_path, secret_keys)
34-
}
35-
}
36-
37-
fn retrieve_secrets(
38-
base: &String,
39-
secret_keys: HashSet<String>,
40-
) -> crate::Result<HashMap<String, String>> {
41-
let base_path = Path::new(&base);
42-
let mut secrets = HashMap::new();
43-
for k in secret_keys.into_iter() {
44-
let secret_path = Path::new(&k);
45-
if secret_path.is_absolute() {
46-
return Err(format!("secret key can not be absolute: {}", k).into());
47-
}
48-
49-
let file_path = base_path.join(secret_path);
50-
let file_result = File::open(file_path);
51-
match file_result {
52-
Ok(mut file) => {
53-
let mut contents = String::new();
54-
file.read_to_string(&mut contents)?;
55-
if contents.is_empty() {
31+
let contents = tokio::fs::read_to_string(&self.path).await?;
32+
let output = serde_json::from_str::<HashMap<String, String>>(&contents)?;
33+
let mut secrets = HashMap::new();
34+
for k in secret_keys.into_iter() {
35+
if let Some(secret) = output.get(&k) {
36+
if secret.is_empty() {
5637
return Err(format!("secret for key '{}' was empty", k).into());
5738
}
58-
59-
secrets.insert(k.to_string(), contents);
60-
}
61-
Err(error) => {
62-
if error.kind() == ErrorKind::NotFound {
63-
return Err(format!("secret file for '{}' not found", k).into());
64-
}
65-
return Err(format!("error reading file for '{}': {}", k, error).into());
39+
secrets.insert(k, secret.to_string());
40+
} else {
41+
return Err(format!("secret for key '{}' was not retrieved", k).into());
6642
}
6743
}
68-
}
69-
Ok(secrets)
70-
}
71-
72-
#[cfg(test)]
73-
mod tests {
74-
use super::*;
75-
76-
#[test]
77-
fn retrieves_secret() {
78-
let base_path = String::from("tests/secrets/");
79-
let mut secret_keys = HashSet::new();
80-
secret_keys.insert(String::from("secret"));
81-
82-
let result = retrieve_secrets(&base_path, secret_keys);
83-
let values = result.expect("error reading secret");
84-
assert!(values.contains_key("secret"));
85-
assert_eq!(values.get("secret").unwrap(), "value\n");
86-
}
87-
88-
#[test]
89-
fn reject_absolute_path() {
90-
let base_path = String::from("tests/secrets/");
91-
let mut secret_keys = HashSet::new();
92-
secret_keys.insert(String::from("/absolute/path"));
93-
94-
let result = retrieve_secrets(&base_path, secret_keys);
95-
let error = result.expect_err("absolute key should produce error");
96-
assert_eq!(
97-
error.to_string(),
98-
"secret key can not be absolute: /absolute/path"
99-
);
100-
}
101-
102-
#[test]
103-
fn reject_empty_secret() {
104-
let base_path = String::from("tests/secrets/");
105-
let mut secret_keys = HashSet::new();
106-
secret_keys.insert(String::from("empty"));
107-
108-
let result = retrieve_secrets(&base_path, secret_keys);
109-
let error = result.expect_err("empty secret should produce error");
110-
assert_eq!(error.to_string(), "secret for key 'empty' was empty");
111-
}
112-
113-
#[test]
114-
fn secret_not_found() {
115-
let base_path = String::from("tests/secrets/");
116-
let mut secret_keys = HashSet::new();
117-
secret_keys.insert(String::from("does_not_exist"));
118-
119-
let result = retrieve_secrets(&base_path, secret_keys);
120-
let error = result.expect_err("secret should not be found");
121-
assert_eq!(
122-
error.to_string(),
123-
"secret file for 'does_not_exist' not found"
124-
);
44+
Ok(secrets)
12545
}
12646
}

src/secrets/mod.rs

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
use std::collections::{HashMap, HashSet};
33

44
use enum_dispatch::enum_dispatch;
5-
use vector_lib::configurable::{configurable_component, NamedComponent};
5+
use vector_lib::configurable::configurable_component;
66

7+
use crate::config::GenerateConfig;
78
use crate::{config::SecretBackend, signal};
89

910
#[cfg(feature = "secrets-aws-secrets-manager")]
@@ -13,12 +14,50 @@ mod exec;
1314
mod file;
1415
mod test;
1516

16-
/// Configurable secret backends in Vector.
17+
/// Configuration options to retrieve secrets from external backend in order to avoid storing secrets in plaintext
18+
/// in Vector config. Multiple backends can be configured. Use `SECRET[<backend_name>.<secret_key>]` to tell Vector to retrieve the secret. This placeholder is replaced by the secret
19+
/// retrieved from the relevant backend.
20+
///
21+
/// When `type` is `exec`, the provided command will be run and provided a list of
22+
/// secrets to fetch, determined from the configuration file, on stdin as JSON in the format:
23+
///
24+
/// ```json
25+
/// {"version": "1.0", "secrets": ["secret1", "secret2"]}
26+
/// ```
27+
///
28+
/// The executable is expected to respond with the values of these secrets on stdout, also as JSON, in the format:
29+
///
30+
/// ```json
31+
/// {
32+
/// "secret1": {"value": "secret_value", "error": null},
33+
/// "secret2": {"value": null, "error": "could not fetch the secret"}
34+
/// }
35+
/// ```
36+
/// If an `error` is returned for any secrets, or if the command exits with a non-zero status code,
37+
/// Vector will log the errors and exit.
38+
///
39+
/// Otherwise, the secret must be a JSON text string with key/value pairs. For example:
40+
/// ```json
41+
/// {
42+
/// "username": "test",
43+
/// "password": "example-password"
44+
/// }
45+
/// ```
46+
///
47+
/// If an error occurred while reading the file or retrieving the secrets, Vector logs the error and exits.
48+
///
49+
/// Secrets are loaded when Vector starts or if Vector receives a `SIGHUP` signal triggering its
50+
/// configuration reload process.
1751
#[allow(clippy::large_enum_variant)]
18-
#[configurable_component]
52+
#[configurable_component(global_option("secret"))]
1953
#[derive(Clone, Debug)]
2054
#[enum_dispatch(SecretBackend)]
2155
#[serde(tag = "type", rename_all = "snake_case")]
56+
#[configurable(metadata(
57+
docs::enum_tag_description = "secret type",
58+
docs::common = false,
59+
docs::required = false,
60+
))]
2261
pub enum SecretBackends {
2362
/// File.
2463
File(file::FileBackend),
@@ -38,16 +77,11 @@ pub enum SecretBackends {
3877
Test(test::TestBackend),
3978
}
4079

41-
// TODO: Use `enum_dispatch` here.
42-
impl NamedComponent for SecretBackends {
43-
fn get_component_name(&self) -> &'static str {
44-
match self {
45-
Self::File(config) => config.get_component_name(),
46-
Self::Directory(config) => config.get_component_name(),
47-
Self::Exec(config) => config.get_component_name(),
48-
#[cfg(feature = "secrets-aws-secrets-manager")]
49-
Self::AwsSecretsManager(config) => config.get_component_name(),
50-
Self::Test(config) => config.get_component_name(),
51-
}
80+
impl GenerateConfig for SecretBackends {
81+
fn generate_config() -> toml::Value {
82+
toml::Value::try_from(Self::File(file::FileBackend {
83+
path: "path/to/file".into(),
84+
}))
85+
.unwrap()
5286
}
5387
}

0 commit comments

Comments
 (0)