Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for reading secrets from a JSON file #428

Merged
merged 2 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 are 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 further 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
Loading