diff --git a/cli/tests/integration/secret_store.rs b/cli/tests/integration/secret_store.rs index 25ef674a..dbc911ed 100644 --- a/cli/tests/integration/secret_store.rs +++ b/cli/tests/integration/secret_store.rs @@ -14,6 +14,7 @@ viceroy_test!(secret_store_works, |is_component| { language = "rust" [local_server] secret_stores.store_one = [{key = "first", data = "This is some data"},{key = "second", file = "../test-fixtures/data/kv-store.txt"}] + secret_stores.store_two = {file = "../test-fixtures/data/json-secret_store.json", format = "json"} "#; let resp = Test::using_fixture("secret-store.wasm") diff --git a/lib/src/config/secret_store.rs b/lib/src/config/secret_store.rs index 28c00b29..e2395ac5 100644 --- a/lib/src/config/secret_store.rs +++ b/lib/src/config/secret_store.rs @@ -3,7 +3,7 @@ use { error::{FastlyConfigError, SecretStoreConfigError}, secret_store::{SecretStore, SecretStores}, }, - std::{convert::TryFrom, fs}, + std::{collections::HashMap, convert::TryFrom, fs}, toml::value::Table, }; @@ -23,12 +23,66 @@ impl TryFrom for SecretStoreConfig { }); } - let items = items.as_array().ok_or_else(|| { - FastlyConfigError::InvalidSecretStoreDefinition { - name: store_name.to_string(), - err: SecretStoreConfigError::NotAnArray, + // Either the items here is from a top-level file with + // "file" and "format" keys or it's an inline array. + // We try to parse either one of them to the same Vec + // to allow them to run through the same validation path futher down + let file_path = items + .as_table() + .and_then(|table| table.get("file")) + .and_then(|file| file.as_str()); + let file_format = items + .as_table() + .and_then(|table| table.get("format")) + .and_then(|format| format.as_str()); + + let items: Vec = match (file_path, file_format) { + (Some(file_path), Some(file_type)) => { + if file_type != "json" { + return Err(FastlyConfigError::InvalidSecretStoreDefinition { + name: store_name.to_string(), + err: SecretStoreConfigError::InvalidFileFormat(file_type.to_string()), + }); + } + + let json = read_json_contents(&file_path).map_err(|e| { + FastlyConfigError::InvalidSecretStoreDefinition { + name: store_name.to_string(), + err: e, + } + })?; + + let toml: Vec = json + .into_iter() + .map(|(key, value)| { + toml::toml! { + key = key + data = value + } + }) + .collect(); + + toml + } + (None, None) => { + // No file or format specified, parse the TOML as an array + items + .as_array() + .ok_or_else(|| FastlyConfigError::InvalidSecretStoreDefinition { + name: store_name.to_string(), + err: SecretStoreConfigError::NotAnArray, + })? + .clone() } - })?; + // This means that *either* `format` or `file` is set, which isn't allowed + // we need both or neither. + (_, _) => { + return Err(FastlyConfigError::InvalidSecretStoreDefinition { + name: store_name.to_string(), + err: SecretStoreConfigError::OnlyOneFormatOrFileSet, + }); + } + }; let mut secret_store = SecretStore::new(); for item in items.iter() { @@ -112,3 +166,10 @@ fn is_valid_name(name: &str) -> bool { .chars() .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-' || c == '.') } + +fn read_json_contents(filename: &str) -> Result, SecretStoreConfigError> { + let data = fs::read_to_string(filename).map_err(SecretStoreConfigError::IoError)?; + let map: HashMap = + serde_json::from_str(&data).map_err(|_| SecretStoreConfigError::FileWrongFormat)?; + Ok(map) +} diff --git a/lib/src/error.rs b/lib/src/error.rs index b6ff8538..765399a8 100644 --- a/lib/src/error.rs +++ b/lib/src/error.rs @@ -666,6 +666,14 @@ pub enum SecretStoreConfigError { #[error(transparent)] IoError(std::io::Error), + #[error("'{0}' is not a valid format for the secret store. Supported format(s) are: 'json'.")] + InvalidFileFormat(String), + #[error("When using a top-level 'file' to load data, both 'file' and 'format' must be set.")] + OnlyOneFormatOrFileSet, + #[error( + "The file is of the wrong format. The file is expected to contain a single JSON object." + )] + FileWrongFormat, #[error("The `file` and `data` keys for the object `{0}` are set. Only one can be used.")] FileAndData(String), #[error("The `file` or `data` key for the object `{0}` is not set. One must be used.")] diff --git a/test-fixtures/data/json-secret_store.json b/test-fixtures/data/json-secret_store.json new file mode 100644 index 00000000..c62326c1 --- /dev/null +++ b/test-fixtures/data/json-secret_store.json @@ -0,0 +1,4 @@ +{ + "first": "first secret", + "second": "another secret" +} diff --git a/test-fixtures/src/bin/secret-store.rs b/test-fixtures/src/bin/secret-store.rs index 33644fd8..f8771fe0 100644 --- a/test-fixtures/src/bin/secret-store.rs +++ b/test-fixtures/src/bin/secret-store.rs @@ -22,6 +22,13 @@ fn main() { _ => panic!(), } + let store_two = SecretStore::open("store_two").unwrap(); + assert_eq!(store_two.get("first").unwrap().plaintext(), "first secret"); + assert_eq!( + store_two.get("second").unwrap().plaintext(), + "another secret", + ); + let hello_bytes = "hello, wasm_world!".as_bytes().to_vec(); let secret = Secret::from_bytes(hello_bytes).unwrap(); assert_eq!("hello, wasm_world!", secret.plaintext());