Skip to main content

harn_vm/secrets/
memory.rs

1use std::collections::BTreeMap;
2use std::sync::{Arc, Mutex};
3
4use async_trait::async_trait;
5
6use super::{
7    emit_secret_access_event, RotationHandle, SecretBytes, SecretError, SecretId, SecretMeta,
8    SecretProvider, SecretVersion,
9};
10
11/// A process-local secret provider whose values are zeroized when dropped.
12///
13/// Hosts use this for session credentials that must not be written to a native
14/// keyring or projected into the process environment. Clones share one store.
15#[derive(Clone, Debug)]
16pub struct MemorySecretProvider {
17    provider: String,
18    inner: Arc<Mutex<BTreeMap<(String, String), VersionedSecret>>>,
19}
20
21#[derive(Debug, Default)]
22struct VersionedSecret {
23    latest: Option<u64>,
24    versions: BTreeMap<u64, SecretBytes>,
25}
26
27impl MemorySecretProvider {
28    pub fn new(provider: impl Into<String>) -> Self {
29        Self {
30            provider: provider.into(),
31            inner: Arc::new(Mutex::new(BTreeMap::new())),
32        }
33    }
34
35    pub fn empty() -> Self {
36        // Preserve the long-standing connector testkit label for callers that
37        // used this constructor through its former re-export path.
38        Self::new("connector-testkit")
39    }
40
41    pub fn with_secret(mut self, id: SecretId, value: impl AsRef<[u8]>) -> Self {
42        self.insert(id, value);
43        self
44    }
45
46    pub fn insert(&mut self, id: SecretId, value: impl AsRef<[u8]>) {
47        let mut inner = self.inner.lock().expect("memory secret provider poisoned");
48        insert_secret(&mut inner, id, SecretBytes::from(value.as_ref()));
49    }
50
51    pub fn snapshot(&self) -> Vec<SecretMeta> {
52        let inner = self.inner.lock().expect("memory secret provider poisoned");
53        inner
54            .iter()
55            .filter_map(|((namespace, name), secret)| {
56                let latest = secret.latest?;
57                Some(SecretMeta {
58                    id: SecretId::new(namespace.clone(), name.clone())
59                        .with_version(SecretVersion::Exact(latest)),
60                    provider: self.provider.clone(),
61                })
62            })
63            .collect()
64    }
65}
66
67#[async_trait]
68impl SecretProvider for MemorySecretProvider {
69    async fn get(&self, id: &SecretId) -> Result<SecretBytes, SecretError> {
70        let inner = self.inner.lock().expect("memory secret provider poisoned");
71        let secret = inner
72            .get(&(id.namespace.clone(), id.name.clone()))
73            .ok_or_else(|| not_found(&self.provider, id))?;
74        let version = match id.version {
75            SecretVersion::Latest => secret.latest,
76            SecretVersion::Exact(version) => Some(version),
77        }
78        .ok_or_else(|| not_found(&self.provider, id))?;
79        let value = secret
80            .versions
81            .get(&version)
82            .map(SecretBytes::reborrow)
83            .ok_or_else(|| not_found(&self.provider, id))?;
84        emit_secret_access_event("memory", id);
85        Ok(value)
86    }
87
88    async fn put(&self, id: &SecretId, value: SecretBytes) -> Result<(), SecretError> {
89        let mut inner = self.inner.lock().expect("memory secret provider poisoned");
90        insert_secret(&mut inner, id.clone(), value);
91        Ok(())
92    }
93
94    async fn rotate(&self, id: &SecretId) -> Result<RotationHandle, SecretError> {
95        let mut inner = self.inner.lock().expect("memory secret provider poisoned");
96        let secret = inner
97            .entry((id.namespace.clone(), id.name.clone()))
98            .or_default();
99        let from_version = secret.latest;
100        let to_version = from_version.unwrap_or(0) + 1;
101        let value = from_version
102            .and_then(|version| secret.versions.get(&version).map(SecretBytes::reborrow))
103            .unwrap_or_else(|| SecretBytes::from(Vec::new()));
104        secret.versions.insert(to_version, value);
105        secret.latest = Some(to_version);
106        Ok(RotationHandle {
107            provider: self.provider.clone(),
108            id: SecretId::new(id.namespace.clone(), id.name.clone())
109                .with_version(SecretVersion::Exact(to_version)),
110            from_version,
111            to_version: Some(to_version),
112        })
113    }
114
115    async fn list(&self, prefix: &SecretId) -> Result<Vec<SecretMeta>, SecretError> {
116        Ok(self
117            .snapshot()
118            .into_iter()
119            .filter(|meta| {
120                meta.id.namespace == prefix.namespace && meta.id.name.starts_with(&prefix.name)
121            })
122            .collect())
123    }
124
125    fn namespace(&self) -> &str {
126        &self.provider
127    }
128
129    fn supports_versions(&self) -> bool {
130        true
131    }
132}
133
134fn insert_secret(
135    inner: &mut BTreeMap<(String, String), VersionedSecret>,
136    id: SecretId,
137    value: SecretBytes,
138) {
139    let secret = inner.entry((id.namespace, id.name)).or_default();
140    let version = match id.version {
141        SecretVersion::Latest => secret.latest.unwrap_or(0) + 1,
142        SecretVersion::Exact(version) => version,
143    };
144    secret.versions.insert(version, value);
145    secret.latest = Some(secret.latest.map_or(version, |latest| latest.max(version)));
146}
147
148fn not_found(provider: &str, id: &SecretId) -> SecretError {
149    SecretError::NotFound {
150        provider: provider.to_string(),
151        id: id.clone(),
152    }
153}