1#![forbid(unsafe_code)]
2#![warn(missing_docs)]
3
4use aws_config::SdkConfig;
18use serde::{Deserialize, Serialize, de::DeserializeOwned};
19use thiserror::Error;
20
21pub mod aws;
22pub mod memory;
23
24#[derive(Debug, Clone, Deserialize, Serialize)]
26#[serde(tag = "provider", rename_all = "snake_case")]
27pub enum SecretsManagerConfig {
28 Aws(aws::AwsSecretManagerConfig),
30
31 Memory(memory::MemorySecretManagerConfig),
33}
34
35#[derive(Debug, Error)]
37pub enum SecretsManagerConfigError {
38 #[error(transparent)]
40 Memory(memory::MemorySecretManagerConfigError),
41
42 #[error(transparent)]
44 Aws(aws::AwsSecretsManagerConfigError),
45}
46
47impl SecretsManagerConfig {
48 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#[derive(Clone)]
65pub enum SecretManager {
66 Aws(aws::AwsSecretManager),
68
69 Memory(memory::MemorySecretManager),
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub enum SetSecretOutcome {
76 Created,
78 Updated,
80}
81
82impl SecretManager {
83 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 #[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 #[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 #[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 #[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 #[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#[derive(Debug, Error)]
190pub enum SecretManagerError {
191 #[error(transparent)]
193 Aws(Box<aws::AwsSecretError>),
194
195 #[error(transparent)]
197 Memory(memory::MemorySecretError),
198
199 #[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#[derive(Debug, Clone, PartialEq, Eq)]
212pub enum Secret {
213 String(String),
215
216 Binary(Vec<u8>),
218}
219
220pub(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}