docbox_core/secrets/
mod.rs

1use aws::AwsSecretManager;
2use aws_config::SdkConfig;
3use docbox_database::{DbConnectErr, DbSecretManager, DbSecrets};
4use memory::MemorySecretManager;
5use serde::{Deserialize, de::DeserializeOwned};
6
7use crate::{
8    aws::SecretsManagerClient,
9    secrets::{
10        aws::AwsSecretManagerConfig,
11        json::{JsonSecretManager, JsonSecretManagerConfig},
12    },
13};
14
15pub mod aws;
16pub mod json;
17pub mod memory;
18
19#[derive(Debug, Clone, Deserialize)]
20#[serde(tag = "provider", rename_all = "snake_case")]
21pub enum SecretsManagerConfig {
22    /// In-memory secret manager
23    Memory(AwsSecretManagerConfig),
24
25    /// Encrypted JSON file secret manager
26    Json(JsonSecretManagerConfig),
27
28    /// AWS secret manager
29    Aws,
30}
31
32impl SecretsManagerConfig {
33    pub fn from_env() -> anyhow::Result<Self> {
34        let variant = std::env::var("DOCBOX_SECRET_MANAGER").unwrap_or_else(|_| "aws".to_string());
35        match variant.as_str() {
36            "memory" => AwsSecretManagerConfig::from_env().map(Self::Memory),
37            "json" => JsonSecretManagerConfig::from_env().map(Self::Json),
38            _ => Ok(Self::Aws),
39        }
40    }
41}
42
43pub enum AppSecretManager {
44    Aws(AwsSecretManager),
45    Memory(MemorySecretManager),
46    Json(JsonSecretManager),
47}
48
49impl AppSecretManager {
50    /// Create the secret manager from the provided config
51    pub fn from_config(aws_config: &SdkConfig, config: SecretsManagerConfig) -> Self {
52        match config {
53            SecretsManagerConfig::Memory(config) => {
54                tracing::debug!("using in memory secret manager");
55                AppSecretManager::Memory(MemorySecretManager::new(
56                    config
57                        .secrets
58                        .into_iter()
59                        .map(|(key, value)| (key, Secret::String(value)))
60                        .collect(),
61                    config.default.map(Secret::String),
62                ))
63            }
64
65            SecretsManagerConfig::Json(config) => {
66                tracing::debug!("using json secret manager");
67                AppSecretManager::Json(JsonSecretManager::from_config(config))
68            }
69            SecretsManagerConfig::Aws => {
70                tracing::debug!("using aws secret manager");
71                let client = SecretsManagerClient::new(aws_config);
72                AppSecretManager::Aws(AwsSecretManager::new(client))
73            }
74        }
75    }
76
77    pub async fn get_secret(&self, name: &str) -> anyhow::Result<Option<Secret>> {
78        tracing::debug!(?name, "reading secret");
79        match self {
80            AppSecretManager::Aws(inner) => inner.get_secret(name).await,
81            AppSecretManager::Memory(inner) => inner.get_secret(name).await,
82            AppSecretManager::Json(inner) => inner.get_secret(name).await,
83        }
84    }
85
86    pub async fn create_secret(&self, name: &str, value: &str) -> anyhow::Result<()> {
87        tracing::debug!(?name, "writing secret");
88        match self {
89            AppSecretManager::Aws(inner) => inner.create_secret(name, value).await,
90            AppSecretManager::Memory(inner) => inner.create_secret(name, value).await,
91            AppSecretManager::Json(inner) => inner.create_secret(name, value).await,
92        }
93    }
94
95    /// Obtains a secret parsing it as JSON of type [D]
96    pub async fn parsed_secret<D: DeserializeOwned>(
97        &self,
98        name: &str,
99    ) -> anyhow::Result<Option<D>> {
100        let secret = match self.get_secret(name).await? {
101            Some(value) => value,
102            None => return Ok(None),
103        };
104        let value: D = match secret {
105            Secret::String(value) => serde_json::from_str(&value)?,
106            Secret::Binary(value) => serde_json::from_slice(value.as_ref())?,
107        };
108        Ok(Some(value))
109    }
110}
111
112#[derive(Debug, Clone, PartialEq, Eq)]
113pub enum Secret {
114    String(String),
115    Binary(Vec<u8>),
116}
117
118pub(crate) trait SecretManager: Send + Sync {
119    async fn get_secret(&self, name: &str) -> anyhow::Result<Option<Secret>>;
120
121    async fn create_secret(&self, name: &str, value: &str) -> anyhow::Result<()>;
122}
123
124impl DbSecretManager for AppSecretManager {
125    async fn get_secret(&self, name: &str) -> Result<Option<DbSecrets>, DbConnectErr> {
126        self.parsed_secret(name)
127            .await
128            .map_err(|err| DbConnectErr::SecretsManager(err.into()))
129    }
130}