docbox_secrets/
lib.rs

1#![forbid(unsafe_code)]
2#![warn(missing_docs)]
3
4//! # Secret manager
5//!
6//! Secret management abstraction with multiple supported backends
7//!
8//! ## Environment Variables
9//!
10//! * `DOCBOX_SECRET_MANAGER` - Which secret manager to use ("aws" or "memory")
11//!
12//! See individual secret manager module documentation for individual environment variables
13//!
14//! - [aws]
15//! - [memory]
16
17use aws_config::SdkConfig;
18use serde::{Deserialize, Serialize, de::DeserializeOwned};
19use thiserror::Error;
20
21pub mod aws;
22pub mod memory;
23
24/// Configuration for a secrets manager
25#[derive(Debug, Clone, Deserialize, Serialize)]
26#[serde(tag = "provider", rename_all = "snake_case")]
27pub enum SecretsManagerConfig {
28    /// AWS secret manager
29    Aws(aws::AwsSecretManagerConfig),
30
31    /// In-memory secret manager
32    Memory(memory::MemorySecretManagerConfig),
33}
34
35/// Errors that could occur with a secrets manager config
36#[derive(Debug, Error)]
37pub enum SecretsManagerConfigError {
38    /// Error from the memory secrets manager config
39    #[error(transparent)]
40    Memory(memory::MemorySecretManagerConfigError),
41
42    /// Error from the AWS secrets manager config
43    #[error(transparent)]
44    Aws(aws::AwsSecretsManagerConfigError),
45}
46
47impl SecretsManagerConfig {
48    /// Get the current secret manager config from environment variables
49    pub fn from_env() -> Result<Self, SecretsManagerConfigError> {
50        let variant = std::env::var("DOCBOX_SECRET_MANAGER").unwrap_or_else(|_| "aws".to_string());
51        match variant.as_str() {
52            "memory" => memory::MemorySecretManagerConfig::from_env()
53                .map(Self::Memory)
54                .map_err(SecretsManagerConfigError::Memory),
55
56            _ => aws::AwsSecretManagerConfig::from_env()
57                .map(Self::Aws)
58                .map_err(SecretsManagerConfigError::Aws),
59        }
60    }
61}
62
63/// Secret manager backed by some underlying secret manager implementation
64#[derive(Clone)]
65pub enum SecretManager {
66    /// AWS backed secret manager
67    Aws(aws::AwsSecretManager),
68
69    /// In-memory secret manager
70    Memory(memory::MemorySecretManager),
71}
72
73/// Outcome from setting a secret
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub enum SetSecretOutcome {
76    /// Fresh secret was created
77    Created,
78    /// Secret with the same name was updated
79    Updated,
80}
81
82impl SecretManager {
83    /// Create the secret manager from the provided `config`
84    ///
85    /// The `aws_config` is required to provide aws specific settings when the AWS secret
86    /// manager is used
87    pub fn from_config(aws_config: &SdkConfig, config: SecretsManagerConfig) -> Self {
88        match config {
89            SecretsManagerConfig::Memory(config) => {
90                tracing::debug!("using in memory secret manager");
91                SecretManager::Memory(memory::MemorySecretManager::new(
92                    config
93                        .secrets
94                        .into_iter()
95                        .map(|(key, value)| (key, Secret::String(value)))
96                        .collect(),
97                    config.default.map(Secret::String),
98                ))
99            }
100
101            SecretsManagerConfig::Aws(config) => {
102                tracing::debug!("using aws secret manager");
103                SecretManager::Aws(aws::AwsSecretManager::from_config(aws_config, config))
104            }
105        }
106    }
107
108    /// Get a secret by `name`
109    ///
110    /// When using the memory secret manager this may return a default value, other secret
111    /// managers will only return the actual secret
112    #[tracing::instrument(skip(self))]
113    pub async fn get_secret(&self, name: &str) -> Result<Option<Secret>, SecretManagerError> {
114        tracing::debug!(?name, "reading secret");
115        match self {
116            SecretManager::Aws(inner) => inner.get_secret(name).await,
117            SecretManager::Memory(inner) => inner.get_secret(name).await,
118        }
119    }
120
121    /// Check if a secret exists by `name`
122    ///
123    /// For the in-memory secret manager this will not return true unless the secret
124    /// actually exists (Unlike [SecretManager::get_secret] which can return the default)
125    #[tracing::instrument(skip(self))]
126    pub async fn has_secret(&self, name: &str) -> Result<bool, SecretManagerError> {
127        tracing::debug!(?name, "reading secret");
128        match self {
129            SecretManager::Aws(inner) => inner.has_secret(name).await,
130            SecretManager::Memory(inner) => inner.has_secret(name).await,
131        }
132    }
133
134    /// Set the value of `name` secret to `value`
135    ///
136    /// Will create a new secret if the secret does not already exist
137    #[tracing::instrument(skip(self))]
138    pub async fn set_secret(
139        &self,
140        name: &str,
141        value: &str,
142    ) -> Result<SetSecretOutcome, SecretManagerError> {
143        tracing::debug!(?name, "writing secret");
144        match self {
145            SecretManager::Aws(inner) => inner.set_secret(name, value).await,
146            SecretManager::Memory(inner) => inner.set_secret(name, value).await,
147        }
148    }
149
150    /// Delete a secret by `name`
151    #[tracing::instrument(skip(self))]
152    pub async fn delete_secret(&self, name: &str, force: bool) -> Result<(), SecretManagerError> {
153        tracing::debug!(?name, "deleting secret");
154        match self {
155            SecretManager::Aws(inner) => inner.delete_secret(name, force).await,
156            SecretManager::Memory(inner) => inner.delete_secret(name, force).await,
157        }
158    }
159
160    /// Get a secret by `name` parsed as type [D] from JSON
161    #[tracing::instrument(skip(self))]
162    pub async fn parsed_secret<D: DeserializeOwned>(
163        &self,
164        name: &str,
165    ) -> Result<Option<D>, SecretManagerError> {
166        let secret = match self.get_secret(name).await? {
167            Some(value) => value,
168            None => return Ok(None),
169        };
170
171        let value: Result<D, serde_json::Error> = match secret {
172            Secret::String(value) => serde_json::from_str(&value),
173            Secret::Binary(value) => serde_json::from_slice(value.as_ref()),
174        };
175
176        let value = match value {
177            Ok(value) => value,
178            Err(error) => {
179                tracing::error!(?error, "failed to parse JSON secret");
180                return Err(SecretManagerError::ParseSecret);
181            }
182        };
183
184        Ok(Some(value))
185    }
186}
187
188/// Errors that could occur when using a secrets manager
189#[derive(Debug, Error)]
190pub enum SecretManagerError {
191    /// AWS secrets manager errors
192    #[error(transparent)]
193    Aws(Box<aws::AwsSecretError>),
194
195    /// In-memory secrets manager errors
196    #[error(transparent)]
197    Memory(memory::MemorySecretError),
198
199    /// Error parsing a secret from JSON
200    #[error("failed to parse secret JSON")]
201    ParseSecret,
202}
203
204impl From<aws::AwsSecretError> for SecretManagerError {
205    fn from(value: aws::AwsSecretError) -> Self {
206        Self::Aws(Box::new(value))
207    }
208}
209
210/// Secret stored in a secrets manager
211#[derive(Debug, Clone, PartialEq, Eq)]
212pub enum Secret {
213    /// Secret stored as a [String]
214    String(String),
215
216    /// Secret stored as bytes
217    Binary(Vec<u8>),
218}
219
220/// Internal trait defining required async implementations for a secret manager
221pub(crate) trait SecretManagerImpl: Send + Sync {
222    async fn get_secret(&self, name: &str) -> Result<Option<Secret>, SecretManagerError>;
223
224    async fn has_secret(&self, name: &str) -> Result<bool, SecretManagerError>;
225
226    async fn set_secret(
227        &self,
228        name: &str,
229        value: &str,
230    ) -> Result<SetSecretOutcome, SecretManagerError>;
231
232    async fn delete_secret(&self, name: &str, force: bool) -> Result<(), SecretManagerError>;
233}