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

feat(config): add vault provider #798

Merged
merged 1 commit into from
Nov 3, 2022
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
250 changes: 226 additions & 24 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ vergen = { version = "7", default-features = false, features = [ "build", "git"
default = []
e2e-tests = []
outbound-redis-tests = []
config-provider-tests = []

[workspace]
members = [
Expand Down
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
openssl req -newkey rsa:2048 -nodes -keyout $(CERT_NAME).key.pem -x509 -days 365 -out $(CERT_NAME).crt.pem
2 changes: 2 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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();
Expand Down
2 changes: 2 additions & 0 deletions crates/config/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions crates/config/src/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::Key;

/// Environment variable based provider.
pub mod env;
pub mod vault;

/// A config provider.
#[async_trait]
Expand Down
57 changes: 57 additions & 0 deletions crates/config/src/provider/vault.rs
Original file line number Diff line number Diff line change
@@ -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<String>,
}

impl VaultProvider {
pub fn new(
url: impl Into<String>,
token: impl Into<String>,
mount: impl Into<String>,
prefix: Option<String>,
) -> 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<Option<String>> {
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))
}
}
12 changes: 9 additions & 3 deletions crates/testing/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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()
}
Expand Down Expand Up @@ -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()
}
Expand Down
3 changes: 2 additions & 1 deletion crates/trigger/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@ 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"

[dev-dependencies]
tempfile = "3.3.0"
toml = "0.5"
tokio = { version = "1.0", features = ["rt", "macros"] }
tokio = { version = "1.0", features = ["rt", "macros"] }
16 changes: 14 additions & 2 deletions crates/trigger/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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<PathBuf>,

#[clap(flatten)]
pub run_config: Executor::RunConfig,

Expand Down Expand Up @@ -103,14 +112,17 @@ 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())?;

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);
Expand Down
41 changes: 41 additions & 0 deletions crates/trigger/src/config.rs
Original file line number Diff line number Diff line change
@@ -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<ConfigProvider>,
}

#[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<String>,
}

impl TriggerExecutorBuilderConfig {
pub fn load_from_file(config_file: Option<PathBuf>) -> Result<Self> {
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)
}
}
36 changes: 33 additions & 3 deletions crates/trigger/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod cli;
pub mod config;
pub mod loader;
pub mod locked;
mod stdio;
Expand All @@ -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";
Expand Down Expand Up @@ -76,7 +80,11 @@ impl<Executor: TriggerExecutor> TriggerExecutorBuilder<Executor> {
self
}

pub async fn build(mut self, app_uri: String) -> Result<Executor>
pub async fn build(
mut self,
app_uri: String,
builder_config: config::TriggerExecutorBuilderConfig,
) -> Result<Executor>
where
Executor::TriggerConfig: DeserializeOwned,
{
Expand All @@ -92,7 +100,9 @@ impl<Executor: TriggerExecutor> TriggerExecutorBuilder<Executor> {
)?;
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),
),
)?;
}

Expand All @@ -109,6 +119,26 @@ impl<Executor: TriggerExecutor> TriggerExecutorBuilder<Executor> {
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<Box<dyn Provider>> {
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<Box<dyn Provider>> {
// EnvProvider
// Look for a .env file in either the manifest parent directory for local apps
Expand Down
2 changes: 2 additions & 0 deletions tests/http/vault-config-test/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[build]
target = "wasm32-wasi"
1 change: 1 addition & 0 deletions tests/http/vault-config-test/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
target/
21 changes: 21 additions & 0 deletions tests/http/vault-config-test/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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]
5 changes: 5 additions & 0 deletions tests/http/vault-config-test/runtime_config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[[config_provider]]
type = "vault"
url = "http://127.0.0.1:8200"
token = "root"
mount = "secret"
19 changes: 19 additions & 0 deletions tests/http/vault-config-test/spin.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
spin_version = "1"
authors = ["Fermyon Engineering <[email protected]>"]
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 }}"
15 changes: 15 additions & 0 deletions tests/http/vault-config-test/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<Response> {
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()))?)
}
Loading