Skip to main content

neuron_secret_vault/
lib.rs

1#![deny(missing_docs)]
2//! Stub secret resolver for HashiCorp Vault KV.
3//!
4//! This crate provides the correct trait impl shape for a Vault secret resolver.
5//! The actual Vault SDK integration is not implemented — all resolve calls return
6//! `SecretError::BackendError`.
7
8use async_trait::async_trait;
9use layer0::secret::SecretSource;
10use neuron_auth::AuthProvider;
11use neuron_secret::{SecretError, SecretLease, SecretResolver};
12use std::sync::Arc;
13
14/// Stub resolver for HashiCorp Vault KV.
15pub struct VaultResolver {
16    _addr: String,
17    _auth: Arc<dyn AuthProvider>,
18}
19
20impl VaultResolver {
21    /// Create a new Vault resolver (stub).
22    pub fn new(addr: impl Into<String>, auth: Arc<dyn AuthProvider>) -> Self {
23        Self {
24            _addr: addr.into(),
25            _auth: auth,
26        }
27    }
28}
29
30#[async_trait]
31impl SecretResolver for VaultResolver {
32    async fn resolve(&self, source: &SecretSource) -> Result<SecretLease, SecretError> {
33        match source {
34            SecretSource::Vault { mount, path } => Err(SecretError::BackendError(format!(
35                "VaultResolver is a stub — would resolve {mount}/{path}"
36            ))),
37            _ => Err(SecretError::NoResolver("vault".into())),
38        }
39    }
40}
41
42#[cfg(test)]
43mod tests {
44    use super::*;
45    use neuron_auth::{AuthError, AuthRequest, AuthToken};
46
47    struct StubAuth;
48    #[async_trait]
49    impl AuthProvider for StubAuth {
50        async fn provide(&self, _request: &AuthRequest) -> Result<AuthToken, AuthError> {
51            Ok(AuthToken::permanent(b"stub".to_vec()))
52        }
53    }
54
55    fn _assert_send_sync<T: Send + Sync>() {}
56
57    #[test]
58    fn object_safety() {
59        _assert_send_sync::<Box<dyn SecretResolver>>();
60        _assert_send_sync::<Arc<dyn SecretResolver>>();
61        let auth: Arc<dyn AuthProvider> = Arc::new(StubAuth);
62        let resolver = VaultResolver::new("https://vault:8200", auth);
63        let _: Box<dyn SecretResolver> = Box::new(resolver);
64    }
65
66    #[tokio::test]
67    async fn matches_vault_source() {
68        let auth: Arc<dyn AuthProvider> = Arc::new(StubAuth);
69        let resolver = VaultResolver::new("https://vault:8200", auth);
70        let source = SecretSource::Vault {
71            mount: "secret".into(),
72            path: "data/key".into(),
73        };
74        let err = resolver.resolve(&source).await.unwrap_err();
75        assert!(matches!(err, SecretError::BackendError(_)));
76        assert!(err.to_string().contains("stub"));
77    }
78
79    #[tokio::test]
80    async fn rejects_wrong_source() {
81        let auth: Arc<dyn AuthProvider> = Arc::new(StubAuth);
82        let resolver = VaultResolver::new("https://vault:8200", auth);
83        let source = SecretSource::OsKeystore {
84            service: "test".into(),
85        };
86        let err = resolver.resolve(&source).await.unwrap_err();
87        assert!(matches!(err, SecretError::NoResolver(_)));
88    }
89}