#![forbid(unsafe_code)]
#![warn(missing_docs)]
use aws_config::SdkConfig;
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use thiserror::Error;
pub mod aws;
pub mod memory;
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(tag = "provider", rename_all = "snake_case")]
pub enum SecretsManagerConfig {
Aws(aws::AwsSecretManagerConfig),
Memory(memory::MemorySecretManagerConfig),
}
impl Default for SecretsManagerConfig {
fn default() -> Self {
Self::Aws(Default::default())
}
}
#[derive(Debug, Error)]
pub enum SecretsManagerConfigError {
#[error(transparent)]
Memory(memory::MemorySecretManagerConfigError),
#[error(transparent)]
Aws(aws::AwsSecretsManagerConfigError),
}
impl SecretsManagerConfig {
pub fn from_env() -> Result<Self, SecretsManagerConfigError> {
let variant = std::env::var("DOCBOX_SECRET_MANAGER").unwrap_or_else(|_| "aws".to_string());
match variant.as_str() {
"memory" => memory::MemorySecretManagerConfig::from_env()
.map(Self::Memory)
.map_err(SecretsManagerConfigError::Memory),
_ => aws::AwsSecretManagerConfig::from_env()
.map(Self::Aws)
.map_err(SecretsManagerConfigError::Aws),
}
}
}
#[derive(Clone)]
pub enum SecretManager {
Aws(aws::AwsSecretManager),
Memory(memory::MemorySecretManager),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SetSecretOutcome {
Created,
Updated,
}
impl SecretManager {
pub fn from_config(aws_config: &SdkConfig, config: SecretsManagerConfig) -> Self {
match config {
SecretsManagerConfig::Memory(config) => {
tracing::debug!("using in memory secret manager");
SecretManager::Memory(memory::MemorySecretManager::new(
config
.secrets
.into_iter()
.map(|(key, value)| (key, Secret::String(value)))
.collect(),
config.default.map(Secret::String),
))
}
SecretsManagerConfig::Aws(config) => {
tracing::debug!("using aws secret manager");
SecretManager::Aws(aws::AwsSecretManager::from_config(aws_config, config))
}
}
}
#[tracing::instrument(skip(self))]
pub async fn get_secret(&self, name: &str) -> Result<Option<Secret>, SecretManagerError> {
tracing::debug!(?name, "reading secret");
match self {
SecretManager::Aws(inner) => inner.get_secret(name).await,
SecretManager::Memory(inner) => inner.get_secret(name).await,
}
}
#[tracing::instrument(skip(self))]
pub async fn has_secret(&self, name: &str) -> Result<bool, SecretManagerError> {
tracing::debug!(?name, "reading secret");
match self {
SecretManager::Aws(inner) => inner.has_secret(name).await,
SecretManager::Memory(inner) => inner.has_secret(name).await,
}
}
#[tracing::instrument(skip(self, value))]
pub async fn set_secret(
&self,
name: &str,
value: &str,
) -> Result<SetSecretOutcome, SecretManagerError> {
tracing::debug!(?name, "writing secret");
match self {
SecretManager::Aws(inner) => inner.set_secret(name, value).await,
SecretManager::Memory(inner) => inner.set_secret(name, value).await,
}
}
#[tracing::instrument(skip(self))]
pub async fn delete_secret(&self, name: &str, force: bool) -> Result<(), SecretManagerError> {
tracing::debug!(?name, "deleting secret");
match self {
SecretManager::Aws(inner) => inner.delete_secret(name, force).await,
SecretManager::Memory(inner) => inner.delete_secret(name, force).await,
}
}
#[tracing::instrument(skip(self))]
pub async fn parsed_secret<D: DeserializeOwned>(
&self,
name: &str,
) -> Result<Option<D>, SecretManagerError> {
let secret = match self.get_secret(name).await? {
Some(value) => value,
None => return Ok(None),
};
let value: Result<D, serde_json::Error> = match secret {
Secret::String(value) => serde_json::from_str(&value),
Secret::Binary(value) => serde_json::from_slice(value.as_ref()),
};
let value = match value {
Ok(value) => value,
Err(error) => {
tracing::error!(?error, "failed to parse JSON secret");
return Err(SecretManagerError::ParseSecret);
}
};
Ok(Some(value))
}
}
#[derive(Debug, Error)]
pub enum SecretManagerError {
#[error(transparent)]
Aws(Box<aws::AwsSecretError>),
#[error(transparent)]
Memory(memory::MemorySecretError),
#[error("failed to parse secret JSON")]
ParseSecret,
}
impl From<aws::AwsSecretError> for SecretManagerError {
fn from(value: aws::AwsSecretError) -> Self {
Self::Aws(Box::new(value))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Secret {
String(String),
Binary(Vec<u8>),
}
pub(crate) trait SecretManagerImpl: Send + Sync {
async fn get_secret(&self, name: &str) -> Result<Option<Secret>, SecretManagerError>;
async fn has_secret(&self, name: &str) -> Result<bool, SecretManagerError>;
async fn set_secret(
&self,
name: &str,
value: &str,
) -> Result<SetSecretOutcome, SecretManagerError>;
async fn delete_secret(&self, name: &str, force: bool) -> Result<(), SecretManagerError>;
}