Skip to content

Commit

Permalink
feat: Add support for fields-based indexing
Browse files Browse the repository at this point in the history
Signed-off-by: Joonas Bergius <joonas@cosmonic.com>
  • Loading branch information
joonas committed Jul 31, 2024
1 parent 22e1462 commit 52be681
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 49 deletions.
4 changes: 2 additions & 2 deletions secrets/secrets-vault/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ tokio = { version = "1.38.0", default-features = false, features = ["full"] }
tracing = { version = "0.1.40", default-features = false, features = [] }
tracing-subscriber = { version = "0.3.18", default-features = false, features = ["fmt", "env-filter"] }
vaultrs = { version = "0.7.2", default-features = false, features = ["rustls"] }
wasmcloud-secrets-types = { version = "0.1.0" }
wasmcloud-secrets-types = { version = "0.2.0" }
wascap = { version = "0.15.0" }

[dev-dependencies]
testcontainers = { version = "0.20.0", default-features = false }
wasmcloud-secrets-client = { version = "0.1.0" }
wasmcloud-secrets-client = { version = "0.2.0" }
64 changes: 42 additions & 22 deletions secrets/secrets-vault/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use jsonwebtoken::{get_current_timestamp, Algorithm, EncodingKey};
use nkeys::{KeyPair, XKey};
use serde::{Deserialize, Serialize};
use std::{net::SocketAddrV4, result::Result as StdResult, str::FromStr};
use tracing::debug;
use tracing::info;
use vaultrs::{
api::kv2::{requests::ReadSecretRequest, responses::ReadSecretResponse},
client::{Client, VaultClient, VaultClientSettingsBuilder},
Expand Down Expand Up @@ -98,8 +98,17 @@ impl TryFrom<&SecretRequest> for VaultPolicy {
type Error = anyhow::Error;

fn try_from(request: &SecretRequest) -> StdResult<Self, Self::Error> {
serde_json::from_str::<Self>(&request.context.application.policy)
.map_err(|e| anyhow!("failed to deserialize policy: {}", e.to_string()))
let policy = serde_json::from_str::<serde_json::Value>(&request.context.application.policy)
.map_err(|e| anyhow!("failed to extract policy: {}", e.to_string()))?;
let properties = policy
.get("properties")
.ok_or_else(|| anyhow!("failed to extract policy properties"))?;
serde_json::from_str::<Self>(&properties.to_string()).map_err(|e| {
anyhow!(
"failed to deserialize vault policy from properties: {}",
e.to_string()
)
})
}
}

Expand All @@ -118,26 +127,27 @@ struct VaultAuthClaims {
}

struct VaultSecretRef {
secret_path: String,
path: String,
field: Option<String>,
version: Option<u64>,
}

impl TryFrom<&SecretRequest> for VaultSecretRef {
type Error = anyhow::Error;

fn try_from(request: &SecretRequest) -> StdResult<Self, Self::Error> {
let secret_path = request.name.clone();
let version = if let Some(version) = request.version.clone() {
version
.parse::<u64>()
.map(Some)
.map_err(|_| anyhow!("unable to convert requested version to integer"))?
} else {
None
};
let version = request
.version
.to_owned()
.map(|v| {
v.parse::<u64>()
.map_err(|_| anyhow!("unable to convert requested version to integer"))
})
.transpose()?;

Ok(Self {
secret_path,
path: request.key.to_owned(),
field: request.field.to_owned(),
version,
})
}
Expand Down Expand Up @@ -225,7 +235,7 @@ impl VaultSecretsBackend {
}

async fn start_nats_subscriber(&self) -> Result<()> {
debug!(
info!(
"Subscribing to messages addressed to {} under queue group {}",
self.subject_mapper.secrets_wildcard_subject(),
self.subject_mapper.queue_group_name(),
Expand Down Expand Up @@ -323,20 +333,30 @@ impl VaultSecretsBackend {
let secret_ref = VaultSecretRef::try_from(&secret_request)
.map_err(|e| GetSecretError::Other(e.to_string()))?;

let secret = Self::fetch_secret(
let response = Self::fetch_secret(
&vault_client,
&policy
.secret_engine_path
.unwrap_or_else(|| self.vault_config.default_secret_engine.to_owned()),
secret_ref,
&secret_ref,
)
.await?;

let secret_version = response.metadata.version.to_string();
let secret = if let Some(field) = secret_ref.field {
response
.data
.get(field)
.map(|v| v.as_str().unwrap())
.map(ToString::to_string)
} else {
Some(response.data.to_string())
};

let secret_response = SecretResponse {
secret: Some(Secret {
name: secret_request.name,
version: secret.metadata.version.to_string(),
string_secret: Some(secret.data.to_string()),
version: secret_version,
string_secret: secret,
binary_secret: None,
}),
error: None,
Expand Down Expand Up @@ -480,11 +500,11 @@ impl VaultSecretsBackend {
async fn fetch_secret(
client: &VaultClient,
mount: &str,
secret_ref: VaultSecretRef,
secret_ref: &VaultSecretRef,
) -> Result<ReadSecretResponse, GetSecretError> {
let request = ReadSecretRequest::builder()
.mount(mount)
.path(secret_ref.secret_path)
.path(&secret_ref.path)
.version(secret_ref.version)
.build()
.unwrap();
Expand Down
8 changes: 4 additions & 4 deletions secrets/secrets-vault/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ struct Args {
struct NatsClientOpts {
/// NATS Server address to connect to listen for secrets requests.
#[arg(long = "nats-address", env = "SV_NATS_ADDRESS")]
pub address: String,
pub nats_address: String,

/// JWT for authenticating the NATS connection
#[arg(long = "nats-jwt", env = "SV_NATS_JWT", requires = "seed")]
Expand All @@ -42,7 +42,7 @@ impl NatsClientOpts {
async move { kp.sign(&nonce).map_err(async_nats::AuthError::new) }
});
}
Ok(async_nats::connect_with_options(self.address, options).await?)
Ok(async_nats::connect_with_options(self.nats_address, options).await?)
}
}

Expand Down Expand Up @@ -81,7 +81,7 @@ struct SecretsServerOpts {
struct VaultOpts {
/// Vault server address to connect to.
#[arg(long = "vault-address", env = "SV_VAULT_ADDRESS")]
pub address: String,
pub vault_address: String,

/// Path where the JWT auth method is mounted
#[arg(long = "vault-auth-method", env = "SV_VAULT_AUTH_METHOD")]
Expand Down Expand Up @@ -110,7 +110,7 @@ struct VaultOpts {
impl From<VaultOpts> for VaultConfig {
fn from(opts: VaultOpts) -> Self {
Self {
address: opts.address,
address: opts.vault_address,
auth_mount: opts.auth_method_path.trim_matches('/').to_owned(),
jwt_audience: opts.jwt_audience,
default_secret_engine: opts.default_secret_engine.trim_matches('/').to_owned(),
Expand Down
36 changes: 15 additions & 21 deletions secrets/secrets-vault/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::{collections::HashMap, net::SocketAddrV4};
use anyhow::Result;
use nkeys::{KeyPair, XKey};
use secrets_vault::{SubjectMapper, VaultConfig, VaultSecretsBackend};
use serde::{Deserialize, Serialize};
use serde::Serialize;
use serde_json::json;
use testcontainers::{
core::{Host as TestHost, WaitFor},
Expand All @@ -30,11 +30,6 @@ const NATS_SERVER_PORT: u16 = 4222;
const VAULT_SERVER_PORT: u16 = 8200;
const VAULT_ROOT_TOKEN_ID: &str = "vault-root-token-id";

#[derive(Serialize, Deserialize)]
struct StoredSecret {
value: String,
}

#[tokio::test]
async fn test_server_xkey() -> Result<()> {
let xkey = nkeys::XKey::new();
Expand Down Expand Up @@ -148,20 +143,17 @@ async fn test_get() -> Result<()> {

configure_vault_jwt_auth(&vault_client, resolve_jwks_url(jwks_port)).await?;

let stored_secret = StoredSecret {
value: "this-is-a-secret".to_string(),
};
let secret_key = "secret-key";
let stored_secret = HashMap::from([(secret_key, "this-is-a-secret")]);

store_secret_in_engine_at_path(
&vault_client,
&json!(stored_secret),
stored_secret.clone(),
SECRETS_ENGINE_MOUNT,
SECRETS_SECRET_NAME,
)
.await?;

//let nats_wrpc_client = async_nats::connect(nats_address)
//.await
//.expect("should be able to create a connection to nats");
let wsc = wasmcloud_secrets_client::Client::new("vault-test", "wasmcloud.secrets", nats_client)
.await
.expect("should be able to instantiate wasmcloud-secrets-client");
Expand All @@ -182,16 +174,19 @@ async fn test_get() -> Result<()> {

let request_xkey = XKey::new();
let secret_request = SecretRequest {
name: SECRETS_SECRET_NAME.to_string(),
key: SECRETS_SECRET_NAME.to_string(),
field: Some(secret_key.to_owned()),
version: None,
context: SecretsContext {
entity_jwt: entity_claims.encode(&claims_signer).unwrap(),
host_jwt: host_claims.encode(&claims_signer).unwrap(),
application: Application {
name: Some("test-app".to_string()),
policy: json!({
"role_name": SECRETS_ROLE_NAME,
"namespace": "foobar"
"type": "properties.secret.wasmcloud.dev/v1alpha1",
"properties": {
"roleName": SECRETS_ROLE_NAME,
}
})
.to_string(),
},
Expand All @@ -202,11 +197,10 @@ async fn test_get() -> Result<()> {
.await
.expect("should have gotten a secret");

let actual: StoredSecret = serde_json::from_str(secret.string_secret.unwrap().as_str())
.expect("should have deserialized secret.string_secret into StoredSecret");
let expected = stored_secret.value;
let actual = secret.string_secret.unwrap_or_default();
let expected = stored_secret.get(secret_key).unwrap();

assert_eq!(actual.value, expected);
assert_eq!(&actual, expected);

Ok(())
}
Expand Down Expand Up @@ -316,7 +310,7 @@ async fn configure_vault_jwt_auth(vault_client: &VaultClient, jwks_url: String)

async fn store_secret_in_engine_at_path(
vault_client: &VaultClient,
value: &impl Serialize,
value: impl Serialize,
mount: &str,
path: &str,
) -> Result<()> {
Expand Down

0 comments on commit 52be681

Please sign in to comment.