trelent-hyok 0.1.12

A Rust library implementing Hold Your Own Key (HYOK) encryption patterns with support for multiple cloud providers
Documentation
//! AWS Secrets Manager integration for DEK storage.
//!
//! This module provides integration with AWS Secrets Manager for secure storage
//! of Data Encryption Keys (DEKs). It handles:
//!
//! - Secret creation and updates
//! - Secure retrieval of stored keys
//! - AWS-compliant secret naming
//! - Error handling for AWS operations
//!
#![cfg(feature = "aws")]

use std::sync::Arc;

use async_trait::async_trait;
use aws_sdk_kms::error::SdkError;
use aws_sdk_secretsmanager::Client;

use crate::error::generator::PersistError;

use super::DEKPersisterTrait;

/// AWS Secrets Manager implementation for persisting Data Encryption Keys (DEK).
///
/// This persister:
/// - Stores DEKs as secrets in AWS Secrets Manager
/// - Uses context strings to generate unique secret names
/// - Handles secret creation, updates, and retrieval
/// - Validates secret names against AWS requirements
///
/// # Costs
///
/// Be aware that AWS Secrets Manager incurs costs based on:
/// - Number of secrets stored
/// - Number of API calls
/// - Secret versions maintained
///
/// # Example
/// ```no_run
/// use std::sync::Arc;
/// use aws_sdk_secretsmanager::Client;
/// use hyokashi::AWSPersister;
///
///     let client = Arc::new(Client::new(&aws_config::load_from_env().await));
///     let persister = AWSPersister::new(client);
///
///     // Store a key
///     let my_key = vec![1, 2, 3];
///     let stored = persister.persist(&my_key, "my-context".to_string()).await?;
///
///     // Retrieve the key
///     let retrieved = persister.fetch("my-context".to_string()).await?;
///
/// ```
pub struct AWSPersister {
    pub(crate) client: Arc<Client>,
}

impl AWSPersister {
    /// Creates a new `AWSPersister` with the provided AWS Secrets Manager client.
    ///
    /// # Arguments
    ///
    /// * `client` - An `Arc` wrapped AWS Secrets Manager client
    pub fn new(client: Arc<Client>) -> Self {
        Self { client }
    }
}

#[async_trait]
impl DEKPersisterTrait for AWSPersister {
    /// Persists a DEK in AWS Secrets Manager, using the provided context to label the secret.
    ///
    /// # Arguments
    ///
    /// * `dek` - The key bytes to store
    /// * `context` - A string used to generate the secret name
    ///
    /// # Errors
    ///
    /// Returns a `PersistError` if the secret cannot be created or updated
    async fn persist(&self, dek: &Vec<u8>, context: String) -> Result<Vec<u8>, PersistError> {
        let hex_secret = hex::encode(dek);
        let secret = self.client.get_secret_value().secret_id(context.clone()).send();
        match secret.await {
            Ok(result) =>
                match result.name() {
                    Some(_) =>
                        self.client
                            .update_secret()
                            .secret_id(context)
                            .secret_string(hex_secret.clone())
                            .send().await
                            .map_err(|e| {
                                PersistError::Error(
                                    format!("Error when persisting secret: {:?}", e)
                                )
                            })
                            .map(|_| hex_secret.into_bytes()),

                    None =>
                        self.client
                            .create_secret()
                            .name(context)
                            .secret_string(hex_secret.clone())
                            .send().await
                            .map_err(|e| {
                                PersistError::Error(
                                    format!("Error when persisting secret: {:?}", e)
                                )
                            })
                            .map(|_| hex_secret.into_bytes()),
                }
            Err(e) =>
                match e {
                    SdkError::ServiceError(ref err) => {
                        if err.err().is_resource_not_found_exception() {
                            self.client
                                .create_secret()
                                .name(context)
                                .secret_string(hex_secret.clone())
                                .send().await
                                .map_err(|e| {
                                    PersistError::Error(
                                        format!("Error when persisting secret: {:?}", e)
                                    )
                                })
                                .map(|_| hex_secret.into_bytes())
                        } else {
                            Err(
                                PersistError::Error(
                                    format!("Error when identifying secret: {:?}", e)
                                )
                            )
                        }
                    }
                    _ =>
                        Err(PersistError::Error(format!("Error when identifying secret: {:?}", e))),
                }
        }
    }

    /// Retrieves a DEK from AWS Secrets Manager using the provided context.
    ///
    /// # Arguments
    ///
    /// * `context` - The context string used when the key was stored
    ///
    /// # Errors
    ///
    /// Returns a `PersistError` if:
    /// - The secret cannot be found
    /// - The secret value cannot be decoded
    /// - AWS API calls fail
    async fn fetch(&self, context: String) -> Result<Vec<u8>, PersistError> {
        let secret = self.client.get_secret_value().secret_id(&context).send();
        match secret.await {
            Ok(secret) =>
                match secret.secret_string() {
                    Some(secret) =>
                        hex
                            ::decode(secret)
                            .map_err(|e| {
                                PersistError::Error(
                                    format!("Could not decode secret, Error: {:?}", e)
                                )
                            }),
                    None =>
                        Err(
                            PersistError::Error(
                                format!("No secret with identifier ({}) was found in result", context)
                            )
                        ),
                }
            Err(e) =>
                Err(
                    PersistError::Error(
                        format!(
                            "Could not fetch secret with identifier ({}), Error: {:?}",
                            context,
                            e
                        )
                    )
                ),
        }
    }
}