docbox_secrets/
lib.rs

1//! # Secret manager
2//!
3//! Secret management abstraction with multiple supported backends
4//!
5//! ## Environment Variables
6//!
7//! * `DOCBOX_SECRET_MANAGER` - Which secret manager to use ("aws", "json", "memory")
8//!
9//! See individual secret manager module documentation for individual environment variables
10
11use aws_config::SdkConfig;
12use serde::{Deserialize, Serialize, de::DeserializeOwned};
13
14pub mod aws;
15pub mod json;
16pub mod memory;
17
18#[derive(Debug, Clone, Deserialize, Serialize)]
19#[serde(tag = "provider", rename_all = "snake_case")]
20pub enum SecretsManagerConfig {
21    /// In-memory secret manager
22    Memory(memory::MemorySecretManagerConfig),
23
24    /// Encrypted JSON file secret manager
25    Json(json::JsonSecretManagerConfig),
26
27    /// AWS secret manager
28    Aws,
29}
30
31impl SecretsManagerConfig {
32    /// Get the current secret manager config from environment variables
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" => memory::MemorySecretManagerConfig::from_env().map(Self::Memory),
37            "json" => json::JsonSecretManagerConfig::from_env().map(Self::Json),
38            _ => Ok(Self::Aws),
39        }
40    }
41}
42
43/// App blanket secret manager backed by some underlying
44/// secret manager implementation
45pub enum AppSecretManager {
46    Aws(aws::AwsSecretManager),
47    Memory(memory::MemorySecretManager),
48    Json(json::JsonSecretManager),
49}
50
51impl AppSecretManager {
52    /// Create the secret manager from the provided `config`
53    ///
54    /// The `aws_config` is required to provide aws specific settings when the AWS secret
55    /// manager is used
56    pub fn from_config(aws_config: &SdkConfig, config: SecretsManagerConfig) -> Self {
57        match config {
58            SecretsManagerConfig::Memory(config) => {
59                tracing::debug!("using in memory secret manager");
60                AppSecretManager::Memory(memory::MemorySecretManager::new(
61                    config
62                        .secrets
63                        .into_iter()
64                        .map(|(key, value)| (key, Secret::String(value)))
65                        .collect(),
66                    config.default.map(Secret::String),
67                ))
68            }
69
70            SecretsManagerConfig::Json(config) => {
71                tracing::debug!("using json secret manager");
72                AppSecretManager::Json(json::JsonSecretManager::from_config(config))
73            }
74            SecretsManagerConfig::Aws => {
75                tracing::debug!("using aws secret manager");
76                AppSecretManager::Aws(aws::AwsSecretManager::from_sdk_config(aws_config))
77            }
78        }
79    }
80
81    /// Get a secret by `name`
82    pub async fn get_secret(&self, name: &str) -> anyhow::Result<Option<Secret>> {
83        tracing::debug!(?name, "reading secret");
84        match self {
85            AppSecretManager::Aws(inner) => inner.get_secret(name).await,
86            AppSecretManager::Memory(inner) => inner.get_secret(name).await,
87            AppSecretManager::Json(inner) => inner.get_secret(name).await,
88        }
89    }
90
91    /// Set the value of `name` secret to `value`
92    ///
93    /// Will create a new secret if the secret does not already exist
94    pub async fn set_secret(&self, name: &str, value: &str) -> anyhow::Result<()> {
95        tracing::debug!(?name, "writing secret");
96        match self {
97            AppSecretManager::Aws(inner) => inner.set_secret(name, value).await,
98            AppSecretManager::Memory(inner) => inner.set_secret(name, value).await,
99            AppSecretManager::Json(inner) => inner.set_secret(name, value).await,
100        }
101    }
102
103    /// Delete a secret by `name`
104    pub async fn delete_secret(&self, name: &str) -> anyhow::Result<()> {
105        tracing::debug!(?name, "deleting secret");
106        match self {
107            AppSecretManager::Aws(inner) => inner.delete_secret(name).await,
108            AppSecretManager::Memory(inner) => inner.delete_secret(name).await,
109            AppSecretManager::Json(inner) => inner.delete_secret(name).await,
110        }
111    }
112
113    /// Get a secret by `name` parsed as type [D] from JSON
114    pub async fn parsed_secret<D: DeserializeOwned>(
115        &self,
116        name: &str,
117    ) -> anyhow::Result<Option<D>> {
118        let secret = match self.get_secret(name).await? {
119            Some(value) => value,
120            None => return Ok(None),
121        };
122        let value: D = match secret {
123            Secret::String(value) => serde_json::from_str(&value)?,
124            Secret::Binary(value) => serde_json::from_slice(value.as_ref())?,
125        };
126        Ok(Some(value))
127    }
128}
129
130#[derive(Debug, Clone, PartialEq, Eq)]
131pub enum Secret {
132    /// Secret stored as a [String]
133    String(String),
134
135    /// Secret stored as bytes
136    Binary(Vec<u8>),
137}
138
139/// Internal trait defining required async implementations for a secret manager
140pub(crate) trait SecretManager: Send + Sync {
141    async fn get_secret(&self, name: &str) -> anyhow::Result<Option<Secret>>;
142
143    async fn set_secret(&self, name: &str, value: &str) -> anyhow::Result<()>;
144
145    async fn delete_secret(&self, name: &str) -> anyhow::Result<()>;
146}