docbox_secrets/
aws.rs

1//! # AWS secret manager
2//!
3//! Secret manager backed by [AWS secrets manager](https://docs.aws.amazon.com/secretsmanager/).
4//! Inherits the loaded [SdkConfig] and all configuration provided to it.
5//!
6//! Intended for AWS hosted environments
7
8use crate::{Secret, SecretManager};
9use aws_config::SdkConfig;
10use aws_sdk_secretsmanager::{
11    error::SdkError,
12    operation::{
13        create_secret::CreateSecretError, delete_secret::DeleteSecretError,
14        get_secret_value::GetSecretValueError, update_secret::UpdateSecretError,
15    },
16};
17use std::fmt::Debug;
18use thiserror::Error;
19
20pub type SecretsManagerClient = aws_sdk_secretsmanager::Client;
21
22pub struct AwsSecretManager {
23    client: SecretsManagerClient,
24}
25
26impl AwsSecretManager {
27    pub fn from_sdk_config(aws_config: &SdkConfig) -> Self {
28        let client = SecretsManagerClient::new(aws_config);
29        Self::new(client)
30    }
31
32    pub fn new(client: SecretsManagerClient) -> Self {
33        Self { client }
34    }
35}
36
37#[derive(Debug, Error)]
38pub enum AwsSecretError {
39    #[error("failed to get secret value: {0}")]
40    GetSecretValue(SdkError<GetSecretValueError>),
41    #[error("failed to create secret: {0}")]
42    CreateSecret(SdkError<CreateSecretError>),
43    #[error("failed to delete secret: {0}")]
44    DeleteSecret(SdkError<DeleteSecretError>),
45    #[error("failed to update secret: {0}")]
46    UpdateSecret(SdkError<UpdateSecretError>),
47}
48
49impl SecretManager for AwsSecretManager {
50    async fn get_secret(&self, name: &str) -> anyhow::Result<Option<super::Secret>> {
51        let result = self
52            .client
53            .get_secret_value()
54            .secret_id(name)
55            .send()
56            .await
57            .map_err(AwsSecretError::GetSecretValue)?;
58
59        if let Some(value) = result.secret_string {
60            return Ok(Some(Secret::String(value)));
61        }
62
63        if let Some(value) = result.secret_binary {
64            return Ok(Some(Secret::Binary(value.into_inner())));
65        }
66
67        Ok(None)
68    }
69
70    async fn set_secret(&self, name: &str, value: &str) -> anyhow::Result<()> {
71        let err = match self
72            .client
73            .create_secret()
74            .secret_string(value)
75            .name(name)
76            .send()
77            .await
78        {
79            Ok(_) => return Ok(()),
80            Err(err) => err,
81        };
82
83        // Handle secret already existing
84        if err
85            .as_service_error()
86            .is_some_and(|value| value.is_resource_exists_exception())
87        {
88            self.client
89                .update_secret()
90                .secret_string(value)
91                .secret_id(name)
92                .send()
93                .await
94                .map_err(AwsSecretError::UpdateSecret)?;
95
96            return Ok(());
97        }
98
99        Err(AwsSecretError::CreateSecret(err).into())
100    }
101
102    async fn delete_secret(&self, name: &str) -> anyhow::Result<()> {
103        let err = match self.client.delete_secret().secret_id(name).send().await {
104            Ok(_) => return Ok(()),
105            Err(err) => err,
106        };
107
108        // Handle secret doesn't exist
109        if err
110            .as_service_error()
111            .is_some_and(|value| value.is_resource_not_found_exception())
112        {
113            return Ok(());
114        }
115
116        Err(AwsSecretError::DeleteSecret(err).into())
117    }
118}