Skip to main content

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