Skip to content

Commit

Permalink
add support for reading secrets from a JSON file
Browse files Browse the repository at this point in the history
This uses the same format as for the KV store, introduced in #365.

Fixes #364.
  • Loading branch information
joeshaw committed Sep 16, 2024
1 parent a9fb534 commit 233dcae
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 6 deletions.
1 change: 1 addition & 0 deletions cli/tests/integration/secret_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
73 changes: 67 additions & 6 deletions lib/src/config/secret_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand All @@ -23,12 +23,66 @@ impl TryFrom<Table> 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<toml::Value>
// 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<toml::Value> = 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<toml::Value> = 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() {
Expand Down Expand Up @@ -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<HashMap<String, String>, SecretStoreConfigError> {
let data = fs::read_to_string(filename).map_err(SecretStoreConfigError::IoError)?;
let map: HashMap<String, String> =
serde_json::from_str(&data).map_err(|_| SecretStoreConfigError::FileWrongFormat)?;
Ok(map)
}
8 changes: 8 additions & 0 deletions lib/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.")]
Expand Down
4 changes: 4 additions & 0 deletions test-fixtures/data/json-secret_store.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"first": "first secret",
"second": "another secret"
}
7 changes: 7 additions & 0 deletions test-fixtures/src/bin/secret-store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down

0 comments on commit 233dcae

Please sign in to comment.