Skip to content

Commit

Permalink
feat(config): add vault provider
Browse files Browse the repository at this point in the history
Signed-off-by: Frank Yang <[email protected]>
  • Loading branch information
FrankYang0529 committed Oct 19, 2022
1 parent bda5677 commit 7f57e0a
Show file tree
Hide file tree
Showing 15 changed files with 441 additions and 33 deletions.
250 changes: 226 additions & 24 deletions Cargo.lock

Large diffs are not rendered by default.

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
50 changes: 50 additions & 0 deletions crates/config/src/provider/vault.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use anyhow::{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,
}

impl VaultProvider {
pub fn new(url: impl Into<String>, token: impl Into<String>) -> Self {
Self {
url: url.into(),
token: token.into(),
}
}
}

#[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 keys = key.0.split('_').collect::<Vec<_>>();
if keys.len() == 1 {
return Err(anyhow!("vault key must contain the mount path"));
}
let mount = keys[0];
let path = keys[1..].join("/");
let secret: Secret = kv2::read(&client, 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 CONFIG_FILE: &str = "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 = CONFIG_FILE,
long = "config-file",
env = CONFIG_FILE,
)]
pub 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.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
39 changes: 39 additions & 0 deletions crates/trigger/src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
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,
}

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)
}
}
39 changes: 36 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,29 @@ 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)
}
};
providers.push(Box::new(provider));
}
// if let Some(vault_config) = &builder_config.vault_config {
// providers.push(Box::new(VaultProvider::new(
// &vault_config.url,
// &vault_config.token,
// )))
// }
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/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/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/config-test/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "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]
4 changes: 4 additions & 0 deletions tests/http/config-test/builder_config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[[config_provider]]
type = "vault"
url = "http://127.0.0.1:8200"
token = "root"
19 changes: 19 additions & 0 deletions tests/http/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 = "config-test"
trigger = { type = "http", base = "/" }
version = "0.1.0"

[variables]
secret_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]
secret_password = "{{ secret_password }}"
15 changes: 15 additions & 0 deletions tests/http/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("secret_password").expect("Failed to acquire password from vault");
Ok(http::Response::builder()
.status(200)
.body(Some(format!("Got password {}", password).into()))?)
}

0 comments on commit 7f57e0a

Please sign in to comment.