use crate::contract::capability::{EnvPolicy, EnvPolicyError, EnvSource, SecretRef};
use std::collections::BTreeMap;
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum SecretResolveError {
Unknown {
reference: SecretRef,
},
Backend {
reference: SecretRef,
detail: String,
},
}
impl std::fmt::Display for SecretResolveError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Unknown { reference } => {
write!(f, "secret lease {:?} is unknown or expired", reference.id())
}
Self::Backend { reference, detail } => write!(
f,
"secret lease {:?} could not be resolved: {detail}",
reference.id()
),
}
}
}
impl std::error::Error for SecretResolveError {}
pub trait SecretResolver {
fn resolve(&self, reference: &SecretRef) -> Result<String, SecretResolveError>;
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum EnvLowerError {
Invalid(EnvPolicyError),
Secret(SecretResolveError),
}
impl std::fmt::Display for EnvLowerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Invalid(e) => write!(f, "environment policy is invalid: {e}"),
Self::Secret(e) => write!(f, "environment secret lease unresolved: {e}"),
}
}
}
impl std::error::Error for EnvLowerError {}
pub fn lower_env(
policy: &EnvPolicy,
resolver: &dyn SecretResolver,
) -> Result<Vec<(String, String)>, EnvLowerError> {
policy.validate().map_err(EnvLowerError::Invalid)?;
let EnvPolicy::Exact(entries) = policy;
let mut envp: Vec<(String, String)> = Vec::with_capacity(entries.len());
for entry in entries {
let value = match &entry.source {
EnvSource::Literal(value) => value.clone(),
EnvSource::SecretLease(reference) => {
resolver.resolve(reference).map_err(EnvLowerError::Secret)?
}
};
envp.push((entry.name.clone(), value));
}
Ok(envp)
}
#[derive(Clone, Debug, Default)]
pub struct MapSecretResolver {
table: BTreeMap<String, String>,
}
impl MapSecretResolver {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with(mut self, reference: impl Into<String>, value: impl Into<String>) -> Self {
self.table.insert(reference.into(), value.into());
self
}
}
impl SecretResolver for MapSecretResolver {
fn resolve(&self, reference: &SecretRef) -> Result<String, SecretResolveError> {
self.table
.get(reference.id())
.cloned()
.ok_or_else(|| SecretResolveError::Unknown {
reference: reference.clone(),
})
}
}
#[cfg(test)]
mod tests {
use super::{lower_env, EnvLowerError, MapSecretResolver, SecretResolveError, SecretResolver};
use crate::contract::capability::{EnvEntry, EnvPolicy, EnvPolicyError, SecretRef};
#[test]
fn a_bound_lease_resolves_to_its_value() {
let resolver = MapSecretResolver::new().with("lease://a", "s3cr3t");
assert_eq!(
resolver.resolve(&SecretRef::new("lease://a")),
Ok("s3cr3t".to_string())
);
}
#[test]
fn an_unknown_lease_fails_closed() {
let resolver = MapSecretResolver::new();
let reference = SecretRef::new("lease://missing");
assert_eq!(
resolver.resolve(&reference),
Err(SecretResolveError::Unknown { reference })
);
}
#[test]
fn lowering_resolves_literals_and_leases_in_order() {
let resolver = MapSecretResolver::new().with("lease://tok", "RESOLVED-VALUE");
let policy = EnvPolicy::Exact(vec![
EnvEntry::literal("PATH", "/usr/bin:/bin"),
EnvEntry::lease("TOKEN", SecretRef::new("lease://tok")),
]);
let envp = lower_env(&policy, &resolver).expect("lowers cleanly");
assert_eq!(
envp,
vec![
("PATH".to_string(), "/usr/bin:/bin".to_string()),
("TOKEN".to_string(), "RESOLVED-VALUE".to_string()),
]
);
}
#[test]
fn lowering_fails_closed_on_an_unresolvable_lease() {
let resolver = MapSecretResolver::new(); let policy = EnvPolicy::Exact(vec![EnvEntry::lease(
"TOKEN",
SecretRef::new("lease://missing"),
)]);
let result = lower_env(&policy, &resolver);
assert!(
matches!(
result,
Err(EnvLowerError::Secret(SecretResolveError::Unknown { .. }))
),
"an unresolvable lease must fail lowering closed, got {result:?}"
);
}
#[test]
fn lowering_fails_closed_on_an_invalid_policy() {
let resolver = MapSecretResolver::new();
let policy = EnvPolicy::Exact(vec![
EnvEntry::literal("DUP", "a"),
EnvEntry::literal("DUP", "b"),
]);
assert_eq!(
lower_env(&policy, &resolver),
Err(EnvLowerError::Invalid(EnvPolicyError::DuplicateName {
name: "DUP".to_string()
}))
);
}
}