1#![deny(missing_docs)]
2use async_trait::async_trait;
7use layer0::secret::SecretSource;
8use neuron_secret::{SecretError, SecretLease, SecretResolver, SecretValue};
9
10pub struct EnvResolver;
21
22#[async_trait]
23impl SecretResolver for EnvResolver {
24 async fn resolve(&self, source: &SecretSource) -> Result<SecretLease, SecretError> {
25 match source {
26 SecretSource::Custom { provider, config } if provider == "env" => {
27 let var_name =
28 config
29 .get("var_name")
30 .and_then(|v| v.as_str())
31 .ok_or_else(|| {
32 SecretError::NotFound("env source requires config.var_name".into())
33 })?;
34 match std::env::var(var_name) {
35 Ok(val) => Ok(SecretLease::permanent(SecretValue::new(val.into_bytes()))),
36 Err(_) => Err(SecretError::NotFound(format!("env var {var_name} not set"))),
37 }
38 }
39 _ => Err(SecretError::NoResolver("env".into())),
40 }
41 }
42}
43
44#[cfg(test)]
45mod tests {
46 use super::*;
47 use std::sync::Arc;
48
49 fn _assert_send_sync<T: Send + Sync>() {}
50
51 #[test]
52 fn object_safety() {
53 _assert_send_sync::<Box<dyn SecretResolver>>();
54 _assert_send_sync::<Arc<dyn SecretResolver>>();
55 let _: Box<dyn SecretResolver> = Box::new(EnvResolver);
56 let _: Arc<dyn SecretResolver> = Arc::new(EnvResolver);
57 }
58
59 #[tokio::test]
60 async fn resolves_set_env_var() {
61 unsafe { std::env::set_var("NEURON_TEST_SECRET_ENV", "test-value-42") };
63 let resolver = EnvResolver;
64 let source = SecretSource::Custom {
65 provider: "env".into(),
66 config: serde_json::json!({ "var_name": "NEURON_TEST_SECRET_ENV" }),
67 };
68 let lease = resolver.resolve(&source).await.unwrap();
69 lease.value.with_bytes(|b| assert_eq!(b, b"test-value-42"));
70 unsafe { std::env::remove_var("NEURON_TEST_SECRET_ENV") };
72 }
73
74 #[tokio::test]
75 async fn rejects_missing_env_var() {
76 unsafe { std::env::remove_var("NEURON_TEST_MISSING_VAR") };
78 let resolver = EnvResolver;
79 let source = SecretSource::Custom {
80 provider: "env".into(),
81 config: serde_json::json!({ "var_name": "NEURON_TEST_MISSING_VAR" }),
82 };
83 let err = resolver.resolve(&source).await.unwrap_err();
84 assert!(matches!(err, SecretError::NotFound(_)));
85 assert!(err.to_string().contains("NEURON_TEST_MISSING_VAR"));
86 }
87
88 #[tokio::test]
89 async fn rejects_non_custom_source() {
90 let resolver = EnvResolver;
91 let source = SecretSource::Vault {
92 mount: "secret".into(),
93 path: "data/key".into(),
94 };
95 let err = resolver.resolve(&source).await.unwrap_err();
96 assert!(matches!(err, SecretError::NoResolver(_)));
97 }
98
99 #[tokio::test]
100 async fn rejects_custom_with_wrong_provider() {
101 let resolver = EnvResolver;
102 let source = SecretSource::Custom {
103 provider: "1password".into(),
104 config: serde_json::json!({}),
105 };
106 let err = resolver.resolve(&source).await.unwrap_err();
107 assert!(matches!(err, SecretError::NoResolver(_)));
108 }
109
110 #[tokio::test]
111 async fn rejects_missing_var_name_config() {
112 let resolver = EnvResolver;
113 let source = SecretSource::Custom {
114 provider: "env".into(),
115 config: serde_json::json!({}),
116 };
117 let err = resolver.resolve(&source).await.unwrap_err();
118 assert!(matches!(err, SecretError::NotFound(_)));
119 assert!(err.to_string().contains("var_name"));
120 }
121}