olai_stack_runtime/
secrets.rs1use std::collections::BTreeMap;
4use std::fmt;
5use std::sync::Arc;
6
7use olai_stack_state::SecretRef;
8
9#[derive(Clone, PartialEq, Eq)]
11pub struct SecretValue(String);
12
13impl SecretValue {
14 pub fn new(value: impl Into<String>) -> Self {
16 SecretValue(value.into())
17 }
18
19 pub fn expose(&self) -> &str {
21 &self.0
22 }
23}
24
25impl fmt::Debug for SecretValue {
26 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27 f.write_str("<redacted>")
28 }
29}
30
31impl fmt::Display for SecretValue {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 f.write_str("<redacted>")
34 }
35}
36
37#[derive(Debug, thiserror::Error)]
39pub enum SecretError {
40 #[error("no secret provider `{0}` is configured")]
42 UnknownProvider(String),
43 #[error("secret `{key}` is not set in provider `{provider}`")]
45 Missing {
46 provider: String,
48 key: String,
50 },
51 #[error("secret provider `{provider}` failed for `{key}`: {reason}")]
53 Provider {
54 provider: String,
56 key: String,
58 reason: String,
60 },
61}
62
63pub trait SecretProvider: Send + Sync {
66 fn name(&self) -> &str;
68
69 fn get(&self, key: &str) -> Result<SecretValue, SecretError>;
71}
72
73#[derive(Clone, Debug, Default)]
75pub struct EnvProvider;
76
77impl SecretProvider for EnvProvider {
78 fn name(&self) -> &str {
79 "env"
80 }
81
82 fn get(&self, key: &str) -> Result<SecretValue, SecretError> {
83 std::env::var(key)
84 .map(SecretValue::new)
85 .map_err(|_| SecretError::Missing {
86 provider: self.name().into(),
87 key: key.into(),
88 })
89 }
90}
91
92#[cfg(feature = "keychain")]
95#[derive(Clone, Debug)]
96pub struct KeychainProvider {
97 service: String,
98}
99
100#[cfg(feature = "keychain")]
101impl KeychainProvider {
102 pub fn new(service: impl Into<String>) -> Self {
104 KeychainProvider {
105 service: service.into(),
106 }
107 }
108
109 fn entry(&self, key: &str) -> Result<keyring::Entry, SecretError> {
110 keyring::Entry::new(&self.service, key).map_err(|e| self.failed(key, e))
111 }
112
113 fn failed(&self, key: &str, e: keyring::Error) -> SecretError {
114 SecretError::Provider {
115 provider: "keychain".into(),
116 key: key.into(),
117 reason: e.to_string(),
118 }
119 }
120
121 pub fn set(&self, key: &str, value: &SecretValue) -> Result<(), SecretError> {
123 self.entry(key)?
124 .set_password(value.expose())
125 .map_err(|e| self.failed(key, e))
126 }
127
128 pub fn delete(&self, key: &str) -> Result<(), SecretError> {
130 match self.entry(key)?.delete_credential() {
131 Ok(()) | Err(keyring::Error::NoEntry) => Ok(()),
132 Err(e) => Err(self.failed(key, e)),
133 }
134 }
135}
136
137#[cfg(feature = "keychain")]
138impl SecretProvider for KeychainProvider {
139 fn name(&self) -> &str {
140 "keychain"
141 }
142
143 fn get(&self, key: &str) -> Result<SecretValue, SecretError> {
144 match self.entry(key)?.get_password() {
145 Ok(value) => Ok(SecretValue::new(value)),
146 Err(keyring::Error::NoEntry) => Err(SecretError::Missing {
147 provider: self.name().into(),
148 key: key.into(),
149 }),
150 Err(e) => Err(self.failed(key, e)),
151 }
152 }
153}
154
155#[derive(Clone, Default)]
159pub struct SecretProviders(BTreeMap<String, Arc<dyn SecretProvider>>);
160
161impl fmt::Debug for SecretProviders {
162 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163 f.debug_set().entries(self.0.keys()).finish()
164 }
165}
166
167impl SecretProviders {
168 pub fn new() -> Self {
170 Self::default()
171 }
172
173 pub fn with(mut self, provider: impl SecretProvider + 'static) -> Self {
176 self.0
177 .insert(provider.name().to_owned(), Arc::new(provider));
178 self
179 }
180
181 pub fn resolve(&self, secret: &SecretRef) -> Result<SecretValue, SecretError> {
183 self.0
184 .get(&secret.provider)
185 .ok_or_else(|| SecretError::UnknownProvider(secret.provider.clone()))?
186 .get(&secret.key)
187 }
188}