diff --git a/Cargo.lock b/Cargo.lock index 90725f60ee..771311528a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -90,9 +90,9 @@ checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c" [[package]] name = "async-compression" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345fd392ab01f746c717b1357165b76f0b67a60192007b234058c9045fdcf695" +checksum = "942c7cd7ae39e91bde4820d74132e9862e62c2f386c3aa90ccf55949f5bad63a" dependencies = [ "flate2", "futures-core", @@ -559,6 +559,16 @@ dependencies = [ "uuid", ] +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + [[package]] name = "colored" version = "2.0.0" @@ -943,14 +953,68 @@ dependencies = [ "zeroize", ] +[[package]] +name = "cxx" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f83d0ebf42c6eafb8d7c52f7e5f2d3003b89c7aa4fd2b79229209459a849af8" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07d050484b55975889284352b0ffc2ecbda25c0c55978017c132b29ba0818a86" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d2199b00553eda8012dfec8d3b1c75fce747cf27c169a270b3b99e3448ab78" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb67a6de1f602736dd7eaead0080cf3435df806c61b24b13328db128c58868f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "darling" version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.13.4", + "darling_macro 0.13.4", +] + +[[package]] +name = "darling" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4529658bdda7fd6769b8614be250cdcfc3aeb0ee72fe66f9e41e5e5eb73eac02" +dependencies = [ + "darling_core 0.14.1", + "darling_macro 0.14.1", ] [[package]] @@ -967,13 +1031,38 @@ dependencies = [ "syn", ] +[[package]] +name = "darling_core" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "649c91bc01e8b1eac09fb91e8dbc7d517684ca6be8ebc75bb9cafc894f9fdb6f" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + [[package]] name = "darling_macro" version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" dependencies = [ - "darling_core", + "darling_core 0.13.4", + "quote", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc69c5bfcbd2fc09a0f38451d2daf0e372e367986a83906d1b0dbc88134fb5" +dependencies = [ + "darling_core 0.14.1", "quote", "syn", ] @@ -989,6 +1078,37 @@ dependencies = [ "syn", ] +[[package]] +name = "derive_builder" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07adf7be193b71cc36b193d0f5fe60b918a3a9db4dad0449f57bcfd519704a3" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f91d4cfa921f1c05904dc3c57b4a32c38aed3340cce209f3a6fd1478babafc4" +dependencies = [ + "darling 0.14.1", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_builder_macro" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f0314b72bed045f3a68671b3c86328386762c93f82d98c65c3cb5e5f573dd68" +dependencies = [ + "derive_builder_core", + "syn", +] + [[package]] name = "dialoguer" version = "0.9.0" @@ -1771,17 +1891,28 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.50" +version = "0.1.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd911b35d940d2bd0bea0f9100068e5b97b51a1cbe13d13382f132e0365257a0" +checksum = "f5a6ef98976b22b3b7f2f3a806f858cb862044cfa66805aa3ad84cb3d3b785ed" dependencies = [ "android_system_properties", "core-foundation-sys", + "iana-time-zone-haiku", "js-sys", "wasm-bindgen", "winapi", ] +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + [[package]] name = "id-arena" version = "2.2.1" @@ -1985,9 +2116,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.134" +version = "0.2.135" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb" +checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c" [[package]] name = "libgit2-sys" @@ -2023,6 +2154,15 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "link-cplusplus" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +dependencies = [ + "cc", +] + [[package]] name = "linux-raw-sys" version = "0.0.46" @@ -2307,7 +2447,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0df7ac00c4672f9d5aece54ee3347520b7e20f158656c7db2e6de01902eb7a6c" dependencies = [ - "darling", + "darling 0.13.4", "proc-macro-crate 1.2.1", "proc-macro2", "quote", @@ -2764,18 +2904,18 @@ checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" [[package]] name = "path-absolutize" -version = "3.0.13" +version = "3.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3de4b40bd9736640f14c438304c09538159802388febb02c8abaae0846c1f13" +checksum = "0f1d4993b16f7325d90c18c3c6a3327db7808752db8d208cea0acee0abd52c52" dependencies = [ "path-dedot", ] [[package]] name = "path-dedot" -version = "3.0.17" +version = "3.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d611d5291372b3738a34ebf0d1f849e58b1dcc1101032f76a346eaa1f8ddbb5b" +checksum = "9a81540d94551664b72b72829b12bd167c73c9d25fbac0e04fafa8023f7e4901" dependencies = [ "once_cell", ] @@ -3030,9 +3170,9 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro2" -version = "1.0.46" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" dependencies = [ "unicode-ident", ] @@ -3368,6 +3508,40 @@ version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" +[[package]] +name = "rustify" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9c02e25271068de581e03ac3bb44db60165ff1a10d92b9530192ccb898bc706" +dependencies = [ + "anyhow", + "async-trait", + "bytes", + "http", + "reqwest", + "rustify_derive", + "serde", + "serde_json", + "serde_urlencoded", + "thiserror", + "tracing", + "url", +] + +[[package]] +name = "rustify_derive" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58135536c18c04f4634bedad182a3f41baf33ef811cc38a3ec7b7061c57134c8" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "serde_urlencoded", + "syn", + "synstructure", +] + [[package]] name = "rustix" version = "0.35.11" @@ -3467,6 +3641,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "scratch" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" + [[package]] name = "sct" version = "0.7.0" @@ -3580,9 +3760,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.85" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" +checksum = "41feea4228a6f1cd09ec7a3593a682276702cd67b5273544757dae23c096f074" dependencies = [ "itoa 1.0.4", "ryu", @@ -3940,11 +4120,13 @@ dependencies = [ "async-trait", "dotenvy", "once_cell", + "serde", "spin-app", "spin-core", "thiserror", "tokio", "toml", + "vaultrs", "wit-bindgen-wasmtime", ] @@ -4565,9 +4747,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6edf2d6bc038a43d31353570e27270603f4648d18f5ed10c0e179abe43255af" +checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" dependencies = [ "futures-core", "pin-project-lite", @@ -4740,9 +4922,9 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" [[package]] name = "unicode-normalization" @@ -4791,9 +4973,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.1.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f" +checksum = "feb41e78f93363bb2df8b0e86a2ca30eed7806ea16ea0c790d757cf93f79be83" dependencies = [ "serde", ] @@ -4804,6 +4986,26 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vaultrs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267f958930e08323a44c12e6c5461f3eaaa16d88785e9ec8550215b8aafc3d0b" +dependencies = [ + "async-trait", + "bytes", + "derive_builder", + "http", + "reqwest", + "rustify", + "rustify_derive", + "serde", + "serde_json", + "thiserror", + "tracing", + "url", +] + [[package]] name = "vcpkg" version = "0.2.15" diff --git a/Cargo.toml b/Cargo.toml index ca9961a8e7..24372772ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,6 +76,7 @@ vergen = { version = "7", default-features = false, features = [ "build", "git" default = [] e2e-tests = [] outbound-redis-tests = [] +config-provider-tests = [] [workspace] members = [ diff --git a/Makefile b/Makefile index 546611698b..c7e7baa567 100644 --- a/Makefile +++ b/Makefile @@ -37,6 +37,10 @@ test-e2e: test-outbound-redis: RUST_LOG=$(LOG_LEVEL) cargo test --test integration --features outbound-redis-tests --no-fail-fast -- --nocapture +.PHONY: test-config-provider +test-config-provider: + RUST_LOG=$(LOG_LEVEL) cargo test --test integration --features config-provider-tests --no-fail-fast -- integration_tests::config_provider_tests --nocapture + .PHONY: test-sdk-go test-sdk-go: $(MAKE) -C sdk/go test @@ -46,4 +50,4 @@ test-sdk-go: tls: ${CERT_NAME}.crt.pem $(CERT_NAME).crt.pem: - openssl req -newkey rsa:2048 -nodes -keyout $(CERT_NAME).key.pem -x509 -days 365 -out $(CERT_NAME).crt.pem \ No newline at end of file + openssl req -newkey rsa:2048 -nodes -keyout $(CERT_NAME).key.pem -x509 -days 365 -out $(CERT_NAME).crt.pem diff --git a/build.rs b/build.rs index d719c382d3..1855e530c5 100644 --- a/build.rs +++ b/build.rs @@ -8,6 +8,7 @@ use cargo_target_dep::build_target_dep; const RUST_HTTP_INTEGRATION_TEST: &str = "tests/http/simple-spin-rust"; const RUST_HTTP_INTEGRATION_ENV_TEST: &str = "tests/http/headers-env-routes-test"; +const RUST_HTTP_VAULT_CONFIG_TEST: &str = "tests/http/vault-config-test"; const RUST_OUTBOUND_REDIS_INTEGRATION_TEST: &str = "tests/outbound-redis/http-rust-outbound-redis"; fn main() { @@ -54,6 +55,7 @@ error: the `wasm32-wasi` target is not installed cargo_build(RUST_HTTP_INTEGRATION_TEST); cargo_build(RUST_HTTP_INTEGRATION_ENV_TEST); + cargo_build(RUST_HTTP_VAULT_CONFIG_TEST); cargo_build(RUST_OUTBOUND_REDIS_INTEGRATION_TEST); let mut config = vergen::Config::default(); diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index 960dbe38a3..0509328296 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -13,6 +13,8 @@ spin-app = { path = "../app" } spin-core = { path = "../core" } thiserror = "1" tokio = { version = "1", features = ["rt-multi-thread"] } +vaultrs = "0.6.2" +serde = "1.0.145" [dependencies.wit-bindgen-wasmtime] git = "https://github.com/bytecodealliance/wit-bindgen" diff --git a/crates/config/src/provider.rs b/crates/config/src/provider.rs index 9f3799937a..e6dbeaadc5 100644 --- a/crates/config/src/provider.rs +++ b/crates/config/src/provider.rs @@ -6,6 +6,7 @@ use crate::Key; /// Environment variable based provider. pub mod env; +pub mod vault; /// A config provider. #[async_trait] diff --git a/crates/config/src/provider/vault.rs b/crates/config/src/provider/vault.rs new file mode 100644 index 0000000000..58fd653dad --- /dev/null +++ b/crates/config/src/provider/vault.rs @@ -0,0 +1,57 @@ +use anyhow::Result; +use async_trait::async_trait; +use serde::{Deserialize, Serialize}; +use vaultrs::{ + client::{VaultClient, VaultClientSettingsBuilder}, + kv2, +}; + +use crate::{Key, Provider}; + +/// A config Provider that uses HashiCorp Vault. +#[derive(Debug)] +pub struct VaultProvider { + url: String, + token: String, + mount: String, + prefix: Option, +} + +impl VaultProvider { + pub fn new( + url: impl Into, + token: impl Into, + mount: impl Into, + prefix: Option, + ) -> Self { + Self { + url: url.into(), + token: token.into(), + mount: mount.into(), + prefix, + } + } +} + +#[derive(Deserialize, Serialize)] +struct Secret { + value: String, +} + +#[async_trait] +impl Provider for VaultProvider { + async fn get(&self, key: &Key) -> Result> { + let client = VaultClient::new( + VaultClientSettingsBuilder::default() + .address(&self.url) + .token(&self.token) + .build()?, + )?; + let path = match &self.prefix { + Some(prefix) => format!("{}/{}", prefix, key.0), + None => key.0.to_string(), + }; + let secret: Secret = kv2::read(&client, &self.mount, &path).await?; + Ok(Some(secret.value)) + } +} diff --git a/crates/testing/src/lib.rs b/crates/testing/src/lib.rs index 950689918b..7bf3f9c097 100644 --- a/crates/testing/src/lib.rs +++ b/crates/testing/src/lib.rs @@ -18,7 +18,7 @@ use spin_app::{ }; use spin_core::{Module, StoreBuilder}; use spin_http::{HttpExecutorType, HttpTriggerConfig, WagiTriggerConfig}; -use spin_trigger::{TriggerExecutor, TriggerExecutorBuilder}; +use spin_trigger::{config::TriggerExecutorBuilderConfig, TriggerExecutor, TriggerExecutorBuilder}; pub use tokio; @@ -100,7 +100,10 @@ impl HttpTestConfig { Executor::TriggerConfig: DeserializeOwned, { TriggerExecutorBuilder::new(self.build_loader()) - .build(TEST_APP_URI.to_string()) + .build( + TEST_APP_URI.to_string(), + TriggerExecutorBuilderConfig::default(), + ) .await .unwrap() } @@ -136,7 +139,10 @@ impl RedisTestConfig { self.redis_channel = channel.into(); TriggerExecutorBuilder::new(self.build_loader()) - .build(TEST_APP_URI.to_string()) + .build( + TEST_APP_URI.to_string(), + TriggerExecutorBuilderConfig::default(), + ) .await .unwrap() } diff --git a/crates/trigger/Cargo.toml b/crates/trigger/Cargo.toml index b93f84b904..e3aa48b160 100644 --- a/crates/trigger/Cargo.toml +++ b/crates/trigger/Cargo.toml @@ -22,6 +22,7 @@ spin-config = { path = "../config" } spin-core = { path = "../core" } spin-loader = { path = "../loader" } spin-manifest = { path = "../manifest" } +toml = "0.5.9" tracing = { version = "0.1", features = [ "log" ] } url = "2" wasmtime = "0.39.1" @@ -29,4 +30,4 @@ wasmtime = "0.39.1" [dev-dependencies] tempfile = "3.3.0" toml = "0.5" -tokio = { version = "1.0", features = ["rt", "macros"] } \ No newline at end of file +tokio = { version = "1.0", features = ["rt", "macros"] } diff --git a/crates/trigger/src/cli.rs b/crates/trigger/src/cli.rs index 7e41740770..becf264847 100644 --- a/crates/trigger/src/cli.rs +++ b/crates/trigger/src/cli.rs @@ -5,13 +5,14 @@ use clap::{Args, IntoApp, Parser}; use serde::de::DeserializeOwned; use crate::stdio::StdioLoggingTriggerHooks; -use crate::{loader::TriggerLoader, stdio::FollowComponents}; +use crate::{config::TriggerExecutorBuilderConfig, loader::TriggerLoader, stdio::FollowComponents}; use crate::{TriggerExecutor, TriggerExecutorBuilder}; pub const APP_LOG_DIR: &str = "APP_LOG_DIR"; pub const DISABLE_WASMTIME_CACHE: &str = "DISABLE_WASMTIME_CACHE"; pub const FOLLOW_LOG_OPT: &str = "FOLLOW_ID"; pub const WASMTIME_CACHE_FILE: &str = "WASMTIME_CACHE_FILE"; +pub const RUNTIME_CONFIG_FILE: &str = "RUNTIME_CONFIG_FILE"; // Set by `spin up` pub const SPIN_LOCKED_URL: &str = "SPIN_LOCKED_URL"; @@ -70,6 +71,14 @@ where #[clap(long = "allow-transient-write")] pub allow_transient_write: bool, + /// Configuration file for config providers and wasmtime config. + #[clap( + name = RUNTIME_CONFIG_FILE, + long = "runtime-config-file", + env = RUNTIME_CONFIG_FILE, + )] + pub runtime_config_file: Option, + #[clap(flatten)] pub run_config: Executor::RunConfig, @@ -103,6 +112,9 @@ where let loader = TriggerLoader::new(working_dir, self.allow_transient_write); + let trigger_config = + TriggerExecutorBuilderConfig::load_from_file(self.runtime_config_file.clone())?; + let executor: Executor = { let mut builder = TriggerExecutorBuilder::new(loader); self.update_wasmtime_config(builder.wasmtime_config_mut())?; @@ -110,7 +122,7 @@ where let logging_hooks = StdioLoggingTriggerHooks::new(self.follow_components(), self.log); builder.hooks(logging_hooks); - builder.build(locked_url).await? + builder.build(locked_url, trigger_config).await? }; let run_fut = executor.run(self.run_config); diff --git a/crates/trigger/src/config.rs b/crates/trigger/src/config.rs new file mode 100644 index 0000000000..46818359ad --- /dev/null +++ b/crates/trigger/src/config.rs @@ -0,0 +1,41 @@ +use std::path::PathBuf; + +use anyhow::Result; +use serde::Deserialize; +use toml; + +// Config for config providers and wasmtime config +#[derive(Debug, Default, Deserialize)] +pub struct TriggerExecutorBuilderConfig { + #[serde(rename = "config_provider", default)] + pub config_providers: Vec, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "snake_case", tag = "type")] +pub enum ConfigProvider { + Vault(VaultConfig), +} + +// Vault config to initialize vault provider +#[derive(Debug, Default, Deserialize)] +pub struct VaultConfig { + pub url: String, + pub token: String, + pub mount: String, + pub prefix: Option, +} + +impl TriggerExecutorBuilderConfig { + pub fn load_from_file(config_file: Option) -> Result { + let config_file = match config_file { + Some(p) => p, + None => { + return Ok(Self::default()); + } + }; + let content = std::fs::read_to_string(config_file)?; + let config: TriggerExecutorBuilderConfig = toml::from_str(&content)?; + Ok(config) + } +} diff --git a/crates/trigger/src/lib.rs b/crates/trigger/src/lib.rs index 472a328287..896766794f 100644 --- a/crates/trigger/src/lib.rs +++ b/crates/trigger/src/lib.rs @@ -1,4 +1,5 @@ pub mod cli; +pub mod config; pub mod loader; pub mod locked; mod stdio; @@ -14,7 +15,10 @@ pub use async_trait::async_trait; use serde::de::DeserializeOwned; use spin_app::{App, AppComponent, AppLoader, AppTrigger, Loader, OwnedApp}; -use spin_config::{provider::env::EnvProvider, Provider}; +use spin_config::{ + provider::{env::EnvProvider, vault::VaultProvider}, + Provider, +}; use spin_core::{Config, Engine, EngineBuilder, Instance, InstancePre, Store, StoreBuilder}; const SPIN_HOME: &str = ".spin"; @@ -76,7 +80,11 @@ impl TriggerExecutorBuilder { self } - pub async fn build(mut self, app_uri: String) -> Result + pub async fn build( + mut self, + app_uri: String, + builder_config: config::TriggerExecutorBuilderConfig, + ) -> Result where Executor::TriggerConfig: DeserializeOwned, { @@ -92,7 +100,9 @@ impl TriggerExecutorBuilder { )?; self.loader.add_dynamic_host_component( &mut builder, - spin_config::ConfigHostComponent::new(self.default_config_providers(&app_uri)), + spin_config::ConfigHostComponent::new( + self.get_config_providers(&app_uri, &builder_config), + ), )?; } @@ -109,6 +119,26 @@ impl TriggerExecutorBuilder { Executor::new(TriggerAppEngine::new(engine, app_name, app, self.hooks).await?) } + pub fn get_config_providers( + &self, + app_uri: &str, + builder_config: &config::TriggerExecutorBuilderConfig, + ) -> Vec> { + let mut providers = self.default_config_providers(app_uri); + for config_provider in &builder_config.config_providers { + let provider = match config_provider { + config::ConfigProvider::Vault(vault_config) => VaultProvider::new( + &vault_config.url, + &vault_config.token, + &vault_config.mount, + vault_config.prefix.clone(), + ), + }; + providers.push(Box::new(provider)); + } + providers + } + pub fn default_config_providers(&self, app_uri: &str) -> Vec> { // EnvProvider // Look for a .env file in either the manifest parent directory for local apps diff --git a/tests/http/vault-config-test/.cargo/config.toml b/tests/http/vault-config-test/.cargo/config.toml new file mode 100644 index 0000000000..6b77899cb3 --- /dev/null +++ b/tests/http/vault-config-test/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target = "wasm32-wasi" diff --git a/tests/http/vault-config-test/.gitignore b/tests/http/vault-config-test/.gitignore new file mode 100644 index 0000000000..2f7896d1d1 --- /dev/null +++ b/tests/http/vault-config-test/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/tests/http/vault-config-test/Cargo.toml b/tests/http/vault-config-test/Cargo.toml new file mode 100644 index 0000000000..6ee42f207e --- /dev/null +++ b/tests/http/vault-config-test/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "vault-config-test" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = [ "cdylib" ] + +[dependencies] +# Useful crate to handle errors. +anyhow = "1" +# Crate to simplify working with bytes. +bytes = "1" +# General-purpose crate with common HTTP types. +http = "0.2" +# The Spin SDK. +spin-sdk = { path = "../../../sdk/rust"} +# Crate that generates Rust Wasm bindings from a WebAssembly interface. +wit-bindgen-rust = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "e9c7c0a3405845cecd3fe06f3c20ab413302fc73" } + +[workspace] diff --git a/tests/http/vault-config-test/runtime_config.toml b/tests/http/vault-config-test/runtime_config.toml new file mode 100644 index 0000000000..002db0d7cc --- /dev/null +++ b/tests/http/vault-config-test/runtime_config.toml @@ -0,0 +1,5 @@ +[[config_provider]] +type = "vault" +url = "http://127.0.0.1:8200" +token = "root" +mount = "secret" diff --git a/tests/http/vault-config-test/spin.toml b/tests/http/vault-config-test/spin.toml new file mode 100644 index 0000000000..c61db840f7 --- /dev/null +++ b/tests/http/vault-config-test/spin.toml @@ -0,0 +1,19 @@ +spin_version = "1" +authors = ["Fermyon Engineering "] +description = "A simple application that returns query values from config providers" +name = "vault-config-test" +trigger = { type = "http", base = "/" } +version = "0.1.0" + +[variables] +password = { required = true } + +[[component]] +id = "config-test" +source = "target/wasm32-wasi/release/config_test.wasm" +[component.trigger] +route = "/..." +[component.build] +command = "cargo build --target wasm32-wasi --release" +[component.config] +password = "{{ password }}" diff --git a/tests/http/vault-config-test/src/lib.rs b/tests/http/vault-config-test/src/lib.rs new file mode 100644 index 0000000000..849d9c3f01 --- /dev/null +++ b/tests/http/vault-config-test/src/lib.rs @@ -0,0 +1,15 @@ +use anyhow::Result; +use spin_sdk::{ + config, + http::{Request, Response}, + http_component, +}; + +/// A simple Spin HTTP component. +#[http_component] +fn config_test(_req: Request) -> Result { + let password = config::get("password").expect("Failed to acquire password from vault"); + Ok(http::Response::builder() + .status(200) + .body(Some(format!("Got password {}", password).into()))?) +} diff --git a/tests/integration.rs b/tests/integration.rs index 410d8179c5..8b410f228c 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -278,6 +278,7 @@ mod integration_tests { let s = SpinTestController::with_manifest( &format!("{}", manifest.path.display()), &[], + &[], Some(&b.url), ) .await?; @@ -617,6 +618,7 @@ mod integration_tests { RUST_OUTBOUND_REDIS_INTEGRATION_TEST, DEFAULT_MANIFEST_LOCATION ), &[], + &[], None, ) .await?; @@ -634,6 +636,7 @@ mod integration_tests { RUST_HTTP_INTEGRATION_TEST, DEFAULT_MANIFEST_LOCATION ), &[], + &[], None, ) .await?; @@ -654,6 +657,7 @@ mod integration_tests { RUST_HTTP_STATIC_ASSETS_TEST, DEFAULT_MANIFEST_LOCATION ), &[], + &[], None, ) .await?; @@ -673,6 +677,120 @@ mod integration_tests { Ok(()) } + #[cfg(feature = "config-provider-tests")] + mod config_provider_tests { + use super::*; + + const RUST_HTTP_VAULT_CONFIG_TEST: &str = "tests/http/vault-config-test"; + const VAULT_BINARY: &str = "vault"; + const VAULT_ROOT_TOKEN: &str = "root"; + + #[tokio::test] + async fn test_vault_config_provider() -> Result<()> { + let vault = VaultTestController::new().await?; + let http_client = reqwest::Client::new(); + let data = r#" +{ + "data": { + "value": "test_password" + } +} +"#; + let body_map: HashMap> = serde_json::from_str(data)?; + let status = http_client + .post(format!("{}/v1/secret/data/password", &vault.url)) + .header("X-Vault-Token", VAULT_ROOT_TOKEN) + .json(&body_map) + .send() + .await? + .status(); + assert_eq!(status, 200); + + let s = SpinTestController::with_manifest( + &format!( + "{}/{}", + RUST_HTTP_VAULT_CONFIG_TEST, DEFAULT_MANIFEST_LOCATION + ), + &[ + "--runtime-config-file", + &format!("{}/{}", RUST_HTTP_VAULT_CONFIG_TEST, "runtime_config.toml"), + ], + &[], + None, + ) + .await?; + + assert_status(&s, "/", 200).await?; + + Ok(()) + } + + /// Controller for running Vault. + pub struct VaultTestController { + pub url: String, + vault_handle: Child, + } + + impl VaultTestController { + pub async fn new() -> Result { + let address = "127.0.0.1:8200"; + let url = format!("http://{}", address); + + let mut vault_handle = Command::new(get_process(VAULT_BINARY)) + .args(["server", "-dev", "-dev-root-token-id", VAULT_ROOT_TOKEN]) + .spawn() + .with_context(|| "executing vault")?; + + wait_vault(&url, &mut vault_handle, VAULT_BINARY).await?; + + Ok(Self { url, vault_handle }) + } + } + + impl Drop for VaultTestController { + fn drop(&mut self) { + let _ = self.vault_handle.kill(); + } + } + + async fn wait_vault(url: &str, process: &mut Child, target: &str) -> Result<()> { + println!("vault url is {} and process is {:?}", url, process); + let mut wait_count = 0; + loop { + if wait_count >= 120 { + panic!( + "Ran out of retries waiting for {} to start on URL {}", + target, url + ); + } + + if let Ok(Some(_)) = process.try_wait() { + panic!( + "Process exited before starting to serve {} to start on URL {}", + target, url + ); + } + + let client = reqwest::Client::new(); + if let Ok(rsp) = client + .get(format!("{url}/v1/sys/health")) + .header("X-Vault-Token", VAULT_ROOT_TOKEN) + .send() + .await + { + if rsp.status().is_success() { + break; + } + } + + wait_count += 1; + sleep(Duration::from_secs(1)).await; + } + + Ok(()) + } + } + async fn assert_status( s: &SpinTestController, absolute_uri: &str, @@ -705,17 +823,19 @@ mod integration_tests { impl SpinTestController { pub async fn with_manifest( manifest_path: &str, - env: &[&str], + spin_args: &[&str], + spin_app_env: &[&str], bindle_url: Option<&str>, ) -> Result { // start Spin using the given application manifest and wait for the HTTP server to be available. let url = format!("127.0.0.1:{}", get_random_port()?); let mut args = vec!["up", "--file", manifest_path, "--listen", &url]; + args.extend(spin_args); if let Some(b) = bindle_url { args.push("--bindle-server"); args.push(b); } - for v in env { + for v in spin_app_env { args.push("--env"); args.push(v); }