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

Secret Store #210

Merged
merged 1 commit into from Jan 20, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions cli/src/main.rs
Expand Up @@ -45,13 +45,15 @@ pub async fn serve(opts: Opts) -> Result<(), Error> {
let geolocation = config.geolocation();
let dictionaries = config.dictionaries();
let object_store = config.object_store();
let secret_stores = config.secret_stores();
let backend_names = itertools::join(backends.keys(), ", ");

ctx = ctx
.with_backends(backends.clone())
.with_geolocation(geolocation.clone())
.with_dictionaries(dictionaries.clone())
.with_object_store(object_store.clone())
.with_secret_stores(secret_stores.clone())
.with_config_path(config_path.into());

if backend_names.is_empty() {
Expand Down
9 changes: 8 additions & 1 deletion cli/tests/integration/common.rs
Expand Up @@ -11,7 +11,9 @@ use tokio::sync::Mutex;
use tracing_subscriber::filter::EnvFilter;
use viceroy_lib::{
body::Body,
config::{Backend, Backends, Dictionaries, FastlyConfig, Geolocation, ObjectStore},
config::{
Backend, Backends, Dictionaries, FastlyConfig, Geolocation, ObjectStore, SecretStores,
},
ExecuteCtx, ProfilingStrategy, ViceroyService,
};

Expand Down Expand Up @@ -52,6 +54,7 @@ pub struct Test {
dictionaries: Dictionaries,
geolocation: Geolocation,
object_store: ObjectStore,
secret_stores: SecretStores,
hosts: Vec<HostSpec>,
log_stdout: bool,
log_stderr: bool,
Expand All @@ -70,6 +73,7 @@ impl Test {
dictionaries: Dictionaries::new(),
geolocation: Geolocation::new(),
object_store: ObjectStore::new(),
secret_stores: SecretStores::new(),
hosts: Vec::new(),
log_stdout: false,
log_stderr: false,
Expand All @@ -88,6 +92,7 @@ impl Test {
dictionaries: Dictionaries::new(),
geolocation: Geolocation::new(),
object_store: ObjectStore::new(),
secret_stores: SecretStores::new(),
hosts: Vec::new(),
log_stdout: false,
log_stderr: false,
Expand All @@ -103,6 +108,7 @@ impl Test {
dictionaries: config.dictionaries().to_owned(),
geolocation: config.geolocation().to_owned(),
object_store: config.object_store().to_owned(),
secret_stores: config.secret_stores().to_owned(),
..self
})
}
Expand Down Expand Up @@ -206,6 +212,7 @@ impl Test {
.with_dictionaries(self.dictionaries.clone())
.with_geolocation(self.geolocation.clone())
.with_object_store(self.object_store.clone())
.with_secret_stores(self.secret_stores.clone())
.with_log_stderr(self.log_stderr)
.with_log_stdout(self.log_stdout);
let addr: SocketAddr = "127.0.0.1:17878".parse().unwrap();
Expand Down
1 change: 1 addition & 0 deletions cli/tests/integration/main.rs
Expand Up @@ -11,6 +11,7 @@ mod memory;
mod object_store;
mod request;
mod response;
mod secret_store;
mod sending_response;
mod sleep;
mod upstream;
Expand Down
216 changes: 216 additions & 0 deletions cli/tests/integration/secret_store.rs
@@ -0,0 +1,216 @@
use crate::common::{Test, TestResult};
use hyper::{body::to_bytes, StatusCode};
use viceroy_lib::config::FastlyConfig;
use viceroy_lib::error::{FastlyConfigError, SecretStoreConfigError};

#[tokio::test(flavor = "multi_thread")]
async fn secret_store_works() -> TestResult {
const FASTLY_TOML: &str = r#"
name = "secret-store"
description = "secret store test"
authors = ["Jill Bryson <jbryson@fastly.com>", "Rose McDowall <rmcdowall@fastly.com>"]
language = "rust"
[local_server]
secret_store.store_one = [{key = "first", data = "This is some data"},{key = "second", path = "../test-fixtures/data/object-store.txt"}]
"#;

let resp = Test::using_fixture("secret-store.wasm")
.using_fastly_toml(FASTLY_TOML)?
.against_empty()
.await;

assert_eq!(resp.status(), StatusCode::OK);
assert!(to_bytes(resp.into_body())
.await
.expect("can read body")
.to_vec()
.is_empty());

Ok(())
}

fn bad_config_test(toml_fragment: &str) -> Result<FastlyConfig, FastlyConfigError> {
let toml = format!(
r#"
name = "secret-store"
description = "secret store test"
authors = ["Jill Bryson <jbryson@fastly.com>", "Rose McDowall <rmcdowall@fastly.com>"]
language = "rust"
[local_server]
{}
"#,
toml_fragment
);

println!("TOML: {}", toml);
toml.parse::<FastlyConfig>()
}

#[tokio::test(flavor = "multi_thread")]
async fn bad_config_store_not_array() -> TestResult {
const TOML_FRAGMENT: &str = "secret_store.store_one = 1";
match bad_config_test(TOML_FRAGMENT) {
Err(FastlyConfigError::InvalidSecretStoreDefinition {
err: SecretStoreConfigError::NotAnArray,
..
}) => (),
Err(_) => panic!("Expected a FastlyConfigError::InvalidSecretStoreDefinition with SecretStoreConfigError::NotAnArray"),
_ => panic!("Expected an error"),
}
Ok(())
}

#[tokio::test(flavor = "multi_thread")]
async fn bad_config_store_not_table() -> TestResult {
const TOML_FRAGMENT: &str = "secret_store.store_one = [1]";
match bad_config_test(TOML_FRAGMENT) {
Err(FastlyConfigError::InvalidSecretStoreDefinition {
err: SecretStoreConfigError::NotATable,
..
}) => (),
Err(_) => panic!("Expected a FastlyConfigError::InvalidSecretStoreDefinition with SecretStoreConfigError::NotATable"),
_ => panic!("Expected an error"),
}
Ok(())
}

#[tokio::test(flavor = "multi_thread")]
async fn bad_config_no_key() -> TestResult {
const TOML_FRAGMENT: &str = r#"secret_store.store_one = [{data = "This is some data"}]"#;
match bad_config_test(TOML_FRAGMENT) {
Err(FastlyConfigError::InvalidSecretStoreDefinition {
err: SecretStoreConfigError::NoKey,
..
}) => (),
Err(_) => panic!("Expected a FastlyConfigError::InvalidSecretStoreDefinition with SecretStoreConfigError::NoKey"),
_ => panic!("Expected an error"),
}
Ok(())
}

#[tokio::test(flavor = "multi_thread")]
async fn bad_config_key_not_string() -> TestResult {
const TOML_FRAGMENT: &str =
r#"secret_store.store_one = [{key = 1, data = "This is some data"}]"#;
match bad_config_test(TOML_FRAGMENT) {
Err(FastlyConfigError::InvalidSecretStoreDefinition {
err: SecretStoreConfigError::KeyNotAString,
..
}) => (),
Err(_) => panic!("Expected a FastlyConfigError::InvalidSecretStoreDefinition with SecretStoreConfigError::KeyNotAString"),
_ => panic!("Expected an error"),
}
Ok(())
}

#[tokio::test(flavor = "multi_thread")]
async fn bad_config_no_data_or_path() -> TestResult {
const TOML_FRAGMENT: &str = r#"secret_store.store_one = [{key = "first"}]"#;
match bad_config_test(TOML_FRAGMENT) {
Err(FastlyConfigError::InvalidSecretStoreDefinition {
err: SecretStoreConfigError::NoPathOrData(_),
..
}) => (),
Err(_) => panic!("Expected a FastlyConfigError::InvalidSecretStoreDefinition with SecretStoreConfigError::NoPathOrData"),
_ => panic!("Expected an error"),
}
Ok(())
}

#[tokio::test(flavor = "multi_thread")]
async fn bad_config_both_data_and_path() -> TestResult {
const TOML_FRAGMENT: &str = r#"secret_store.store_one = [{key = "first", path = "file.txt", data = "This is some data"}]"#;
match bad_config_test(TOML_FRAGMENT) {
Err(FastlyConfigError::InvalidSecretStoreDefinition {
err: SecretStoreConfigError::PathAndData(_),
..
}) => (),
Err(_) => panic!("Expected a FastlyConfigError::InvalidSecretStoreDefinition with SecretStoreConfigError::PathAndData"),
_ => panic!("Expected an error"),
}
Ok(())
}

#[tokio::test(flavor = "multi_thread")]
async fn bad_config_data_not_string() -> TestResult {
const TOML_FRAGMENT: &str = r#"secret_store.store_one = [{key = "first", data = 1}]"#;
match bad_config_test(TOML_FRAGMENT) {
Err(FastlyConfigError::InvalidSecretStoreDefinition {
err: SecretStoreConfigError::DataNotAString(_),
..
}) => (),
Err(_) => panic!("Expected a FastlyConfigError::InvalidSecretStoreDefinition with SecretStoreConfigError::DataNotAString"),
_ => panic!("Expected an error"),
}
Ok(())
}

#[tokio::test(flavor = "multi_thread")]
async fn bad_config_path_not_string() -> TestResult {
const TOML_FRAGMENT: &str = r#"secret_store.store_one = [{key = "first", path = 1}]"#;
match bad_config_test(TOML_FRAGMENT) {
Err(FastlyConfigError::InvalidSecretStoreDefinition {
err: SecretStoreConfigError::PathNotAString(_),
..
}) => (),
Err(_) => panic!("Expected a FastlyConfigError::InvalidSecretStoreDefinition with SecretStoreConfigError::PathNotAString"),
_ => panic!("Expected an error"),
}
Ok(())
}

#[tokio::test(flavor = "multi_thread")]
async fn bad_config_path_nonexistent() -> TestResult {
const TOML_FRAGMENT: &str =
r#"secret_store.store_one = [{key = "first", path = "nonexistent.txt"}]"#;
match bad_config_test(TOML_FRAGMENT) {
Err(FastlyConfigError::InvalidSecretStoreDefinition {
err: SecretStoreConfigError::IoError(_),
..
}) => (),
Err(_) => panic!("Expected a FastlyConfigError::InvalidSecretStoreDefinition with SecretStoreConfigError::IoError"),
_ => panic!("Expected an error"),
}
Ok(())
}

#[tokio::test(flavor = "multi_thread")]
async fn bad_config_invalid_store_name() -> TestResult {
const TOML_FRAGMENT: &str =
r#"secret_store.store*one = [{key = "first", data = "This is some data"}]"#;
match bad_config_test(TOML_FRAGMENT) {
Err(FastlyConfigError::InvalidFastlyToml(_)) => (),
Err(_) => panic!("Expected a FastlyConfigError::InvalidFastlyToml"),
_ => panic!("Expected an error"),
}
Ok(())
}

#[tokio::test(flavor = "multi_thread")]
async fn bad_config_invalid_secret_name() -> TestResult {
const TOML_FRAGMENT: &str =
r#"secret_store.store_one = [{key = "first*", data = "This is some data"}]"#;
match bad_config_test(TOML_FRAGMENT) {
Err(FastlyConfigError::InvalidSecretStoreDefinition {
err: SecretStoreConfigError::InvalidSecretName(_),
..
}) => (),
Err(_) => panic!("Expected a FastlyConfigError::InvalidSecretStoreDefinition with SecretStoreConfigError::InvalidSecretName"),
_ => panic!("Expected an error"),
}
Ok(())
}

#[tokio::test(flavor = "multi_thread")]
async fn bad_config_secret_name_too_long() -> TestResult {
const TOML_FRAGMENT: &str = r#"secret_store.store_one = [{key = "firstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirst", data = "This is some data"}]"#;
match bad_config_test(TOML_FRAGMENT) {
Err(FastlyConfigError::InvalidSecretStoreDefinition {
err: SecretStoreConfigError::InvalidSecretName(_),
..
}) => (),
Err(_) => panic!("Expected a FastlyConfigError::InvalidSecretStoreDefinition with SecretStoreConfigError::InvalidSecretName"),
_ => panic!("Expected an error"),
}
Ok(())
}
21 changes: 21 additions & 0 deletions lib/compute-at-edge-abi/compute-at-edge.witx
Expand Up @@ -508,6 +508,27 @@
)
)

(module $fastly_secret_store
(@interface func (export "open")
(param $name string)
(result $err (expected $secret_store_handle (error $fastly_status)))
)

(@interface func (export "get")
(param $store $secret_store_handle)
(param $key string)
(result $err (expected $secret_handle (error $fastly_status)))
)

(@interface func (export "plaintext")
(param $secret $secret_handle)
(param $buf (@witx pointer (@witx char8)))
(param $buf_len (@witx usize))
(param $nwritten_out (@witx pointer (@witx usize)))
(result $err (expected (error $fastly_status)))
)
)

(module $fastly_async_io
;;; Blocks until one of the given objects is ready for I/O, or the optional timeout expires.
;;;
Expand Down
5 changes: 4 additions & 1 deletion lib/compute-at-edge-abi/typenames.witx
Expand Up @@ -94,6 +94,10 @@
(typename $dictionary_handle (handle))
;;; A handle to an Object Store.
(typename $object_store_handle (handle))
;;; A handle to a Secret Store.
(typename $secret_store_handle (handle))
;;; A handle to an individual secret.
(typename $secret_handle (handle))
;;; A handle to an object supporting generic async operations.
;;; Can be either a `body_handle` or a `pending_request_handle`.
;;;
Expand All @@ -107,7 +111,6 @@
;;; into, even before the origin itself consumes that data.
(typename $async_item_handle (handle))


;;; A "multi-value" cursor.
(typename $multi_value_cursor u32)
;;; -1 represents "finished", non-negative represents a $multi_value_cursor:
Expand Down