use std::collections::BTreeMap;
use std::fmt;
use std::sync::Arc;
use olai_stack_state::SecretRef;
#[derive(Clone, PartialEq, Eq)]
pub struct SecretValue(String);
impl SecretValue {
pub fn new(value: impl Into<String>) -> Self {
SecretValue(value.into())
}
pub fn expose(&self) -> &str {
&self.0
}
}
impl fmt::Debug for SecretValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("<redacted>")
}
}
impl fmt::Display for SecretValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("<redacted>")
}
}
#[derive(Debug, thiserror::Error)]
pub enum SecretError {
#[error("no secret provider `{0}` is configured")]
UnknownProvider(String),
#[error("secret `{key}` is not set in provider `{provider}`")]
Missing {
provider: String,
key: String,
},
#[error("secret provider `{provider}` failed for `{key}`: {reason}")]
Provider {
provider: String,
key: String,
reason: String,
},
}
pub trait SecretProvider: Send + Sync {
fn name(&self) -> &str;
fn get(&self, key: &str) -> Result<SecretValue, SecretError>;
}
#[derive(Clone, Debug, Default)]
pub struct EnvProvider;
impl SecretProvider for EnvProvider {
fn name(&self) -> &str {
"env"
}
fn get(&self, key: &str) -> Result<SecretValue, SecretError> {
std::env::var(key)
.map(SecretValue::new)
.map_err(|_| SecretError::Missing {
provider: self.name().into(),
key: key.into(),
})
}
}
#[cfg(feature = "keychain")]
#[derive(Clone, Debug)]
pub struct KeychainProvider {
service: String,
}
#[cfg(feature = "keychain")]
impl KeychainProvider {
pub fn new(service: impl Into<String>) -> Self {
KeychainProvider {
service: service.into(),
}
}
fn entry(&self, key: &str) -> Result<keyring::Entry, SecretError> {
keyring::Entry::new(&self.service, key).map_err(|e| self.failed(key, e))
}
fn failed(&self, key: &str, e: keyring::Error) -> SecretError {
SecretError::Provider {
provider: "keychain".into(),
key: key.into(),
reason: e.to_string(),
}
}
pub fn set(&self, key: &str, value: &SecretValue) -> Result<(), SecretError> {
self.entry(key)?
.set_password(value.expose())
.map_err(|e| self.failed(key, e))
}
pub fn delete(&self, key: &str) -> Result<(), SecretError> {
match self.entry(key)?.delete_credential() {
Ok(()) | Err(keyring::Error::NoEntry) => Ok(()),
Err(e) => Err(self.failed(key, e)),
}
}
}
#[cfg(feature = "keychain")]
impl SecretProvider for KeychainProvider {
fn name(&self) -> &str {
"keychain"
}
fn get(&self, key: &str) -> Result<SecretValue, SecretError> {
match self.entry(key)?.get_password() {
Ok(value) => Ok(SecretValue::new(value)),
Err(keyring::Error::NoEntry) => Err(SecretError::Missing {
provider: self.name().into(),
key: key.into(),
}),
Err(e) => Err(self.failed(key, e)),
}
}
}
#[derive(Clone, Default)]
pub struct SecretProviders(BTreeMap<String, Arc<dyn SecretProvider>>);
impl fmt::Debug for SecretProviders {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_set().entries(self.0.keys()).finish()
}
}
impl SecretProviders {
pub fn new() -> Self {
Self::default()
}
pub fn with(mut self, provider: impl SecretProvider + 'static) -> Self {
self.0
.insert(provider.name().to_owned(), Arc::new(provider));
self
}
pub fn resolve(&self, secret: &SecretRef) -> Result<SecretValue, SecretError> {
self.0
.get(&secret.provider)
.ok_or_else(|| SecretError::UnknownProvider(secret.provider.clone()))?
.get(&secret.key)
}
}