docbox_core/secrets/
mod.rs1use 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 Memory(AwsSecretManagerConfig),
24
25 Json(JsonSecretManagerConfig),
27
28 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 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 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}