#![cfg(feature = "extism-runtime")]
use serde::{Deserialize, Serialize};
use uni_plugin::FnError;
use super::HostSvcCtx;
#[derive(Debug, Deserialize)]
struct AcquireReq {
id: String,
}
#[derive(Debug, Serialize)]
struct AcquireResp {
opaque_id: u64,
}
fn do_acquire(ctx: &HostSvcCtx, req: AcquireReq) -> Result<AcquireResp, FnError> {
if !ctx.effective.iter().any(|c| c.secret_allows(&req.id)) {
return Err(FnError::new(
0xC10,
format!(
"uni.secret.acquire: id `{}` not in granted Secret allow-list",
req.id
),
));
}
let store = ctx
.secrets
.as_ref()
.ok_or_else(|| FnError::new(0xC11, "uni.secret.acquire: no secret store configured"))?;
let handle = store.acquire(&req.id)?;
Ok(AcquireResp {
opaque_id: handle.opaque_id(),
})
}
extism::host_fn!(pub(crate) uni_secret_acquire(ctx: HostSvcCtx; req_json: String) -> String {
let bundle = ctx.get()?;
let bundle = bundle
.lock()
.map_err(|_| extism::Error::msg("uni.secret.acquire: host service ctx poisoned"))?;
super::dispatch_json(&bundle, &req_json, "uni.secret.acquire", do_acquire)
.map_err(|e| extism::Error::msg(e.to_string()))
});
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
use uni_plugin::secrets::SecretStore;
use uni_plugin::{Capability, CapabilitySet};
fn ctx_with(caps: CapabilitySet, store: Option<Arc<SecretStore>>) -> HostSvcCtx {
HostSvcCtx {
effective: caps,
kms: None,
secrets: store,
http: None,
}
}
fn secret_caps(pattern: &str) -> CapabilitySet {
CapabilitySet::from_iter_of([Capability::Secret {
ids: vec![pattern.into()],
}])
}
#[test]
fn acquire_returns_handle_when_granted() {
let store = Arc::new(SecretStore::new());
store.seal("db-password", b"hunter2".to_vec());
let ctx = ctx_with(secret_caps("db-*"), Some(store));
let resp = do_acquire(
&ctx,
AcquireReq {
id: "db-password".into(),
},
)
.expect("acquire");
assert!(resp.opaque_id > 0);
}
#[test]
fn acquire_denied_out_of_allowlist() {
let store = Arc::new(SecretStore::new());
store.seal("api-key", b"secret".to_vec());
let ctx = ctx_with(secret_caps("db-*"), Some(store));
let err = do_acquire(
&ctx,
AcquireReq {
id: "api-key".into(),
},
)
.expect_err("must deny");
assert!(err.message.contains("not in granted Secret allow-list"));
}
#[test]
fn acquire_fails_loudly_without_store() {
let ctx = ctx_with(secret_caps("*"), None);
let err = do_acquire(&ctx, AcquireReq { id: "x".into() }).expect_err("no store");
assert!(err.message.contains("no secret store configured"));
}
}