secret-vault 0.10.0

Library provides a secure vault to store application secrets in memory coming from Google/AWS/other secret managers
Documentation
use crate::errors::*;
use crate::*;
use async_trait::*;
use aws_sdk_secretsmanager::types::SdkError;
use rvstruct::ValueStruct;
use secret_vault_value::SecretValue;
use std::collections::HashMap;
use tracing::*;

#[derive(Debug, Clone)]
pub struct AwsSecretManagerSource {
    account_id: String,
    client: aws_sdk_secretsmanager::Client,
    aws_region: aws_sdk_secretsmanager::Region,
}

impl AwsSecretManagerSource {
    pub async fn new(
        account_id: &String,
        region: Option<aws_sdk_secretsmanager::Region>,
    ) -> SecretVaultResult<Self> {
        let shared_config = aws_config::load_from_env().await;
        let effective_region = region.or(shared_config.region().cloned()).ok_or(
            SecretVaultError::InvalidParametersError(SecretVaultInvalidParametersError::new(
                SecretVaultInvalidParametersPublicDetails::new(
                    "region".into(),
                    "AWS region must be specified or available in the AWS shared config".into(),
                ),
            )),
        )?;

        let client = aws_sdk_secretsmanager::Client::new(&shared_config);
        Ok(AwsSecretManagerSource {
            account_id: account_id.clone(),
            client,
            aws_region: effective_region,
        })
    }
}

#[async_trait]
impl SecretsSource for AwsSecretManagerSource {
    fn name(&self) -> String {
        "AmazonSecretManager".to_string()
    }

    async fn get_secrets(
        &self,
        references: &[SecretVaultRef],
    ) -> SecretVaultResult<HashMap<SecretVaultRef, Secret>> {
        let mut result_map: HashMap<SecretVaultRef, Secret> = HashMap::new();

        for secret_ref in references {
            let aws_secret_arn = format!(
                "arn:aws:secretsmanager:{}:{}:secret:{}",
                self.aws_region,
                self.account_id,
                secret_ref.secret_name.value()
            );

            match self
                .client
                .get_secret_value()
                .secret_id(aws_secret_arn.clone())
                .set_version_stage(secret_ref.secret_version.as_ref().map(|v| v.value().into()))
                .send()
                .await
            {
                Ok(aws_secret) => {
                    let maybe_secret_value =
                        aws_secret
                            .secret_string
                            .map(SecretValue::from)
                            .or(aws_secret
                                .secret_binary
                                .map(|secret_binary| SecretValue::new(secret_binary.into_inner())));

                    if let Some(secret_value) = maybe_secret_value {
                        let metadata = SecretMetadata::new();
                        result_map.insert(secret_ref.clone(), Secret::new(secret_value, metadata));
                    } else if secret_ref.required {
                        return Err(SecretVaultError::DataNotFoundError(
                            SecretVaultDataNotFoundError::new(
                                SecretVaultErrorPublicGenericDetails::new("SECRET_PAYLOAD".into()),
                                format!(
                                    "Secret is required but payload is not found for {}",
                                    aws_secret_arn
                                ),
                            ),
                        ));
                    }
                }
                Err(SdkError::ServiceError {
                    err: get_secret_err,
                    raw: _,
                }) if get_secret_err.is_resource_not_found_exception() => {
                    if secret_ref.required {
                        return Err(SecretVaultError::DataNotFoundError(
                                SecretVaultDataNotFoundError::new(
                                    SecretVaultErrorPublicGenericDetails::new("SECRET_NOT_FOUND".into()),
                                    format!(
                                        "Secret is required but not found in environment variables {:?}",
                                        secret_ref.secret_name
                                    ),
                                ),
                            ));
                    } else {
                        debug!("Secret or secret version {}/{:?} doesn't exist and since it is not required it is skipped",aws_secret_arn, &secret_ref.secret_version);
                    }
                }
                Err(err) => {
                    error!(
                        "Unable to read secret or secret version {}/{:?}: {}.",
                        aws_secret_arn, &secret_ref.secret_version, err
                    );
                    return Err(SecretVaultError::from(err));
                }
            }
        }

        Ok(result_map)
    }
}