docbox_core/secrets/
aws.rs

1use std::{collections::HashMap, fmt::Debug};
2
3use aws_sdk_secretsmanager::{
4    error::SdkError,
5    operation::{
6        create_secret::CreateSecretError, get_secret_value::GetSecretValueError,
7        update_secret::UpdateSecretError,
8    },
9};
10use serde::{Deserialize, Serialize};
11use thiserror::Error;
12
13use crate::aws::SecretsManagerClient;
14
15use super::{Secret, SecretManager};
16
17#[derive(Clone, Deserialize, Serialize)]
18pub struct AwsSecretManagerConfig {
19    /// Collection of secrets to include
20    #[serde(default)]
21    pub secrets: HashMap<String, String>,
22    /// Optional default secret
23    #[serde(default)]
24    pub default: Option<String>,
25}
26
27impl AwsSecretManagerConfig {
28    pub fn from_env() -> anyhow::Result<Self> {
29        let default = std::env::var("DOCBOX_SECRET_MANAGER_DEFAULT").ok();
30
31        Ok(Self {
32            default,
33            secrets: Default::default(),
34        })
35    }
36}
37
38impl Debug for AwsSecretManagerConfig {
39    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40        f.debug_struct("AwsSecretManagerConfig").finish()
41    }
42}
43
44pub struct AwsSecretManager {
45    client: SecretsManagerClient,
46}
47
48impl AwsSecretManager {
49    pub fn new(client: SecretsManagerClient) -> Self {
50        Self { client }
51    }
52}
53
54#[derive(Debug, Error)]
55pub enum AwsSecretError {
56    #[error("failed to get secret value: {0}")]
57    GetSecretValue(SdkError<GetSecretValueError>),
58    #[error("failed to create secret: {0}")]
59    CreateSecret(SdkError<CreateSecretError>),
60    #[error("failed to update secret: {0}")]
61    UpdateSecret(SdkError<UpdateSecretError>),
62}
63
64impl SecretManager for AwsSecretManager {
65    async fn get_secret(&self, name: &str) -> anyhow::Result<Option<super::Secret>> {
66        let result = self
67            .client
68            .get_secret_value()
69            .secret_id(name)
70            .send()
71            .await
72            .map_err(AwsSecretError::GetSecretValue)?;
73
74        if let Some(value) = result.secret_string {
75            return Ok(Some(Secret::String(value)));
76        }
77
78        if let Some(value) = result.secret_binary {
79            return Ok(Some(Secret::Binary(value.into_inner())));
80        }
81
82        Ok(None)
83    }
84
85    async fn create_secret(&self, name: &str, value: &str) -> anyhow::Result<()> {
86        let err = match self
87            .client
88            .create_secret()
89            .secret_string(value)
90            .name(name)
91            .send()
92            .await
93        {
94            Ok(_) => return Ok(()),
95            Err(err) => err,
96        };
97
98        // Handle secret already existing
99        if err
100            .as_service_error()
101            .is_some_and(|value| value.is_resource_exists_exception())
102        {
103            self.client
104                .update_secret()
105                .secret_string(value)
106                .secret_id(name)
107                .send()
108                .await
109                .map_err(AwsSecretError::UpdateSecret)?;
110
111            return Ok(());
112        }
113
114        Err(AwsSecretError::CreateSecret(err).into())
115    }
116}