Fix KMS configuration synchronization across cluster nodes (#855)

* Initial plan

* Add KMS configuration persistence to cluster storage

Co-authored-by: houseme <4829346+houseme@users.noreply.github.com>

* Apply code formatting to KMS configuration changes

Co-authored-by: houseme <4829346+houseme@users.noreply.github.com>

* add comment

* fix fmt

* fix

* Fix overlapping dependabot cargo configurations

Co-authored-by: houseme <4829346+houseme@users.noreply.github.com>

* improve code for comment and replace  `Once_Cell` to `std::sync::OnceLock`

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: houseme <4829346+houseme@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
Co-authored-by: loverustfs <155562731+loverustfs@users.noreply.github.com>
This commit is contained in:
Copilot
2025-11-16 00:05:03 +08:00
committed by GitHub
parent f73fa59bf6
commit b7964081ce
31 changed files with 1077 additions and 565 deletions
+8 -8
View File
@@ -14,7 +14,7 @@
//! API types for KMS dynamic configuration
use crate::config::{KmsBackend, KmsConfig, VaultAuthMethod};
use crate::config::{BackendConfig, CacheConfig, KmsBackend, KmsConfig, LocalConfig, TlsConfig, VaultAuthMethod, VaultConfig};
use crate::service_manager::KmsServiceStatus;
use crate::types::{KeyMetadata, KeyUsage};
use serde::{Deserialize, Serialize};
@@ -212,12 +212,12 @@ impl From<&KmsConfig> for KmsConfigSummary {
};
let backend_summary = match &config.backend_config {
crate::config::BackendConfig::Local(local_config) => BackendSummary::Local {
BackendConfig::Local(local_config) => BackendSummary::Local {
key_dir: local_config.key_dir.clone(),
has_master_key: local_config.master_key.is_some(),
file_permissions: local_config.file_permissions,
},
crate::config::BackendConfig::Vault(vault_config) => BackendSummary::Vault {
BackendConfig::Vault(vault_config) => BackendSummary::Vault {
address: vault_config.address.clone(),
auth_method_type: match &vault_config.auth_method {
VaultAuthMethod::Token { .. } => "token".to_string(),
@@ -248,7 +248,7 @@ impl ConfigureLocalKmsRequest {
KmsConfig {
backend: KmsBackend::Local,
default_key_id: self.default_key_id.clone(),
backend_config: crate::config::BackendConfig::Local(crate::config::LocalConfig {
backend_config: BackendConfig::Local(LocalConfig {
key_dir: self.key_dir.clone(),
master_key: self.master_key.clone(),
file_permissions: self.file_permissions,
@@ -256,7 +256,7 @@ impl ConfigureLocalKmsRequest {
timeout: Duration::from_secs(self.timeout_seconds.unwrap_or(30)),
retry_attempts: self.retry_attempts.unwrap_or(3),
enable_cache: self.enable_cache.unwrap_or(true),
cache_config: crate::config::CacheConfig {
cache_config: CacheConfig {
max_keys: self.max_cached_keys.unwrap_or(1000),
ttl: Duration::from_secs(self.cache_ttl_seconds.unwrap_or(3600)),
enable_metrics: true,
@@ -271,7 +271,7 @@ impl ConfigureVaultKmsRequest {
KmsConfig {
backend: KmsBackend::Vault,
default_key_id: self.default_key_id.clone(),
backend_config: crate::config::BackendConfig::Vault(crate::config::VaultConfig {
backend_config: BackendConfig::Vault(VaultConfig {
address: self.address.clone(),
auth_method: self.auth_method.clone(),
namespace: self.namespace.clone(),
@@ -279,7 +279,7 @@ impl ConfigureVaultKmsRequest {
kv_mount: self.kv_mount.clone().unwrap_or_else(|| "secret".to_string()),
key_path_prefix: self.key_path_prefix.clone().unwrap_or_else(|| "rustfs/kms/keys".to_string()),
tls: if self.skip_tls_verify.unwrap_or(false) {
Some(crate::config::TlsConfig {
Some(TlsConfig {
ca_cert_path: None,
client_cert_path: None,
client_key_path: None,
@@ -292,7 +292,7 @@ impl ConfigureVaultKmsRequest {
timeout: Duration::from_secs(self.timeout_seconds.unwrap_or(30)),
retry_attempts: self.retry_attempts.unwrap_or(3),
enable_cache: self.enable_cache.unwrap_or(true),
cache_config: crate::config::CacheConfig {
cache_config: CacheConfig {
max_keys: self.max_cached_keys.unwrap_or(1000),
ttl: Duration::from_secs(self.cache_ttl_seconds.unwrap_or(3600)),
enable_metrics: true,
+18
View File
@@ -200,6 +200,16 @@ pub struct BackendInfo {
impl BackendInfo {
/// Create a new backend info
///
/// # Arguments
/// * `backend_type` - The type of the backend
/// * `version` - The version of the backend
/// * `endpoint` - The endpoint or location of the backend
/// * `healthy` - Whether the backend is healthy
///
/// # Returns
/// A new BackendInfo instance
///
pub fn new(backend_type: String, version: String, endpoint: String, healthy: bool) -> Self {
Self {
backend_type,
@@ -211,6 +221,14 @@ impl BackendInfo {
}
/// Add metadata to the backend info
///
/// # Arguments
/// * `key` - Metadata key
/// * `value` - Metadata value
///
/// # Returns
/// Updated BackendInfo instance
///
pub fn with_metadata(mut self, key: String, value: String) -> Self {
self.metadata.insert(key, value);
self
+44
View File
@@ -34,6 +34,13 @@ pub struct KmsCache {
impl KmsCache {
/// Create a new KMS cache with the specified capacity
///
/// # Arguments
/// * `capacity` - Maximum number of entries in the cache
///
/// # Returns
/// A new instance of `KmsCache`
///
pub fn new(capacity: u64) -> Self {
Self {
key_metadata_cache: Cache::builder()
@@ -48,22 +55,47 @@ impl KmsCache {
}
/// Get key metadata from cache
///
/// # Arguments
/// * `key_id` - The ID of the key to retrieve metadata for
///
/// # Returns
/// An `Option` containing the `KeyMetadata` if found, or `None` if not found
///
pub async fn get_key_metadata(&self, key_id: &str) -> Option<KeyMetadata> {
self.key_metadata_cache.get(key_id).await
}
/// Put key metadata into cache
///
/// # Arguments
/// * `key_id` - The ID of the key to store metadata for
/// * `metadata` - The `KeyMetadata` to store in the cache
///
pub async fn put_key_metadata(&mut self, key_id: &str, metadata: &KeyMetadata) {
self.key_metadata_cache.insert(key_id.to_string(), metadata.clone()).await;
self.key_metadata_cache.run_pending_tasks().await;
}
/// Get data key from cache
///
/// # Arguments
/// * `key_id` - The ID of the key to retrieve the data key for
///
/// # Returns
/// An `Option` containing the `CachedDataKey` if found, or `None` if not found
///
pub async fn get_data_key(&self, key_id: &str) -> Option<CachedDataKey> {
self.data_key_cache.get(key_id).await
}
/// Put data key into cache
///
/// # Arguments
/// * `key_id` - The ID of the key to store the data key for
/// * `plaintext` - The plaintext data key bytes
/// * `ciphertext` - The ciphertext data key bytes
///
pub async fn put_data_key(&mut self, key_id: &str, plaintext: &[u8], ciphertext: &[u8]) {
let cached_key = CachedDataKey {
plaintext: plaintext.to_vec(),
@@ -75,11 +107,19 @@ impl KmsCache {
}
/// Remove key metadata from cache
///
/// # Arguments
/// * `key_id` - The ID of the key to remove metadata for
///
pub async fn remove_key_metadata(&mut self, key_id: &str) {
self.key_metadata_cache.remove(key_id).await;
}
/// Remove data key from cache
///
/// # Arguments
/// * `key_id` - The ID of the key to remove the data key for
///
pub async fn remove_data_key(&mut self, key_id: &str) {
self.data_key_cache.remove(key_id).await;
}
@@ -95,6 +135,10 @@ impl KmsCache {
}
/// Get cache statistics (hit count, miss count)
///
/// # Returns
/// A tuple containing total entries and total misses
///
pub fn stats(&self) -> (u64, u64) {
let metadata_stats = (
self.key_metadata_cache.entry_count(),
+35
View File
@@ -52,6 +52,16 @@ pub struct AesCipher {
impl AesCipher {
/// Create a new AES cipher with the given key
///
/// #Arguments
/// * `key` - A byte slice representing the AES-256 key (32 bytes)
///
/// #Errors
/// Returns `KmsError` if the key size is invalid
///
/// #Returns
/// A Result containing the AesCipher instance
///
pub fn new(key: &[u8]) -> Result<Self> {
if key.len() != 32 {
return Err(KmsError::invalid_key_size(32, key.len()));
@@ -142,6 +152,16 @@ pub struct ChaCha20Cipher {
impl ChaCha20Cipher {
/// Create a new ChaCha20 cipher with the given key
///
/// #Arguments
/// * `key` - A byte slice representing the ChaCha20-Poly1305 key (32 bytes)
///
/// #Errors
/// Returns `KmsError` if the key size is invalid
///
/// #Returns
/// A Result containing the ChaCha20Cipher instance
///
pub fn new(key: &[u8]) -> Result<Self> {
if key.len() != 32 {
return Err(KmsError::invalid_key_size(32, key.len()));
@@ -228,6 +248,14 @@ impl ObjectCipher for ChaCha20Cipher {
}
/// Create a cipher instance for the given algorithm and key
///
/// #Arguments
/// * `algorithm` - The encryption algorithm to use
/// * `key` - A byte slice representing the encryption key
///
/// #Returns
/// A Result containing a boxed ObjectCipher instance
///
pub fn create_cipher(algorithm: &EncryptionAlgorithm, key: &[u8]) -> Result<Box<dyn ObjectCipher>> {
match algorithm {
EncryptionAlgorithm::Aes256 | EncryptionAlgorithm::AwsKms => Ok(Box::new(AesCipher::new(key)?)),
@@ -236,6 +264,13 @@ pub fn create_cipher(algorithm: &EncryptionAlgorithm, key: &[u8]) -> Result<Box<
}
/// Generate a random IV for the given algorithm
///
/// #Arguments
/// * `algorithm` - The encryption algorithm for which to generate the IV
///
/// #Returns
/// A vector containing the generated IV bytes
///
pub fn generate_iv(algorithm: &EncryptionAlgorithm) -> Vec<u8> {
let iv_size = match algorithm {
EncryptionAlgorithm::Aes256 | EncryptionAlgorithm::AwsKms => 12,
+106 -6
View File
@@ -18,6 +18,12 @@ use crate::encryption::ciphers::{create_cipher, generate_iv};
use crate::error::{KmsError, Result};
use crate::manager::KmsManager;
use crate::types::*;
use base64::Engine;
use rand::random;
use std::collections::HashMap;
use std::io::Cursor;
use tokio::io::{AsyncRead, AsyncReadExt};
use tracing::{debug, info};
use zeroize::Zeroize;
/// Data key for object encryption
@@ -36,12 +42,6 @@ impl Drop for DataKey {
self.plaintext_key.zeroize();
}
}
use base64::Engine;
use rand::random;
use std::collections::HashMap;
use std::io::Cursor;
use tokio::io::{AsyncRead, AsyncReadExt};
use tracing::{debug, info};
/// Service for encrypting and decrypting S3 objects with KMS integration
pub struct ObjectEncryptionService {
@@ -59,51 +59,110 @@ pub struct EncryptionResult {
impl ObjectEncryptionService {
/// Create a new object encryption service
///
/// # Arguments
/// * `kms_manager` - KMS manager to use for key operations
///
/// # Returns
/// New ObjectEncryptionService instance
///
pub fn new(kms_manager: KmsManager) -> Self {
Self { kms_manager }
}
/// Create a new master key (delegates to KMS manager)
///
/// # Arguments
/// * `request` - CreateKeyRequest with key parameters
///
/// # Returns
/// CreateKeyResponse with created key details
///
pub async fn create_key(&self, request: CreateKeyRequest) -> Result<CreateKeyResponse> {
self.kms_manager.create_key(request).await
}
/// Describe a master key (delegates to KMS manager)
///
/// # Arguments
/// * `request` - DescribeKeyRequest with key ID
///
/// # Returns
/// DescribeKeyResponse with key metadata
///
pub async fn describe_key(&self, request: DescribeKeyRequest) -> Result<DescribeKeyResponse> {
self.kms_manager.describe_key(request).await
}
/// List master keys (delegates to KMS manager)
///
/// # Arguments
/// * `request` - ListKeysRequest with listing parameters
///
/// # Returns
/// ListKeysResponse with list of keys
///
pub async fn list_keys(&self, request: ListKeysRequest) -> Result<ListKeysResponse> {
self.kms_manager.list_keys(request).await
}
/// Generate a data encryption key (delegates to KMS manager)
///
/// # Arguments
/// * `request` - GenerateDataKeyRequest with key parameters
///
/// # Returns
/// GenerateDataKeyResponse with generated key details
///
pub async fn generate_data_key(&self, request: GenerateDataKeyRequest) -> Result<GenerateDataKeyResponse> {
self.kms_manager.generate_data_key(request).await
}
/// Get the default key ID
///
/// # Returns
/// Option with default key ID if configured
///
pub fn get_default_key_id(&self) -> Option<&String> {
self.kms_manager.get_default_key_id()
}
/// Get cache statistics
///
/// # Returns
/// Option with (hits, misses) if caching is enabled
///
pub async fn cache_stats(&self) -> Option<(u64, u64)> {
self.kms_manager.cache_stats().await
}
/// Clear the cache
///
/// # Returns
/// Result indicating success or failure
///
pub async fn clear_cache(&self) -> Result<()> {
self.kms_manager.clear_cache().await
}
/// Get backend health status
///
/// # Returns
/// Result indicating if backend is healthy
///
pub async fn health_check(&self) -> Result<bool> {
self.kms_manager.health_check().await
}
/// Create a data encryption key for object encryption
///
/// # Arguments
/// * `kms_key_id` - Optional KMS key ID to use (uses default if None)
/// * `context` - ObjectEncryptionContext with bucket and object key
///
/// # Returns
/// Tuple with DataKey and encrypted key blob
///
pub async fn create_data_key(
&self,
kms_key_id: &Option<String>,
@@ -146,6 +205,14 @@ impl ObjectEncryptionService {
}
/// Decrypt a data encryption key
///
/// # Arguments
/// * `encrypted_key` - Encrypted data key blob
/// * `context` - ObjectEncryptionContext with bucket and object key
///
/// # Returns
/// DataKey with decrypted key
///
pub async fn decrypt_data_key(&self, encrypted_key: &[u8], _context: &ObjectEncryptionContext) -> Result<DataKey> {
let decrypt_request = DecryptRequest {
ciphertext: encrypted_key.to_vec(),
@@ -429,6 +496,17 @@ impl ObjectEncryptionService {
}
/// Decrypt object with customer-provided key (SSE-C)
///
/// # Arguments
/// * `bucket` - S3 bucket name
/// * `object_key` - S3 object key
/// * `ciphertext` - Encrypted data
/// * `metadata` - Encryption metadata
/// * `customer_key` - Customer-provided 256-bit key
///
/// # Returns
/// Decrypted data as a reader
///
pub async fn decrypt_object_with_customer_key(
&self,
bucket: &str,
@@ -481,6 +559,14 @@ impl ObjectEncryptionService {
}
/// Validate encryption context
///
/// # Arguments
/// * `actual` - Actual encryption context from metadata
/// * `expected` - Expected encryption context to validate against
///
/// # Returns
/// Result indicating success or context mismatch
///
fn validate_encryption_context(&self, actual: &HashMap<String, String>, expected: &HashMap<String, String>) -> Result<()> {
for (key, expected_value) in expected {
match actual.get(key) {
@@ -499,6 +585,13 @@ impl ObjectEncryptionService {
}
/// Convert encryption metadata to HTTP headers for S3 compatibility
///
/// # Arguments
/// * `metadata` - EncryptionMetadata to convert
///
/// # Returns
/// HashMap of HTTP headers
///
pub fn metadata_to_headers(&self, metadata: &EncryptionMetadata) -> HashMap<String, String> {
let mut headers = HashMap::new();
@@ -542,6 +635,13 @@ impl ObjectEncryptionService {
}
/// Parse encryption metadata from HTTP headers
///
/// # Arguments
/// * `headers` - HashMap of HTTP headers
///
/// # Returns
/// EncryptionMetadata parsed from headers
///
pub fn headers_to_metadata(&self, headers: &HashMap<String, String>) -> Result<EncryptionMetadata> {
let algorithm = headers
.get("x-amz-server-side-encryption")
+16 -2
View File
@@ -116,7 +116,7 @@ impl KmsError {
Self::BackendError { message: message.into() }
}
/// Create an access denied error
/// Create access denied error
pub fn access_denied<S: Into<String>>(message: S) -> Self {
Self::AccessDenied { message: message.into() }
}
@@ -184,7 +184,7 @@ impl KmsError {
}
}
// Convert from standard library errors
/// Convert from standard library errors
impl From<std::io::Error> for KmsError {
fn from(error: std::io::Error) -> Self {
Self::IoError {
@@ -206,6 +206,13 @@ impl From<serde_json::Error> for KmsError {
impl KmsError {
/// Create a KMS error from AES-GCM error
///
/// #Arguments
/// * `error` - The AES-GCM error to convert
///
/// #Returns
/// * `KmsError` - The corresponding KMS error
///
pub fn from_aes_gcm_error(error: aes_gcm::Error) -> Self {
Self::CryptographicError {
operation: "AES-GCM".to_string(),
@@ -214,6 +221,13 @@ impl KmsError {
}
/// Create a KMS error from ChaCha20-Poly1305 error
///
/// #Arguments
/// * `error` - The ChaCha20-Poly1305 error to convert
///
/// #Returns
/// * `KmsError` - The corresponding KMS error
///
pub fn from_chacha20_error(error: chacha20poly1305::Error) -> Self {
Self::CryptographicError {
operation: "ChaCha20-Poly1305".to_string(),
+7 -7
View File
@@ -19,7 +19,7 @@ use crate::config::{BackendConfig, KmsConfig};
use crate::encryption::service::ObjectEncryptionService;
use crate::error::{KmsError, Result};
use crate::manager::KmsManager;
use std::sync::Arc;
use std::sync::{Arc, OnceLock};
use tokio::sync::RwLock;
use tracing::{error, info, warn};
@@ -71,7 +71,7 @@ impl KmsServiceManager {
/// Configure KMS with new configuration
pub async fn configure(&self, new_config: KmsConfig) -> Result<()> {
tracing::info!("CLAUDE DEBUG: configure() called with backend: {:?}", new_config.backend);
info!("CLAUDE DEBUG: configure() called with backend: {:?}", new_config.backend);
info!("Configuring KMS with backend: {:?}", new_config.backend);
// Update configuration
@@ -92,7 +92,7 @@ impl KmsServiceManager {
/// Start KMS service with current configuration
pub async fn start(&self) -> Result<()> {
tracing::info!("CLAUDE DEBUG: start() called");
info!("CLAUDE DEBUG: start() called");
let config = {
let config_guard = self.config.read().await;
match config_guard.as_ref() {
@@ -254,7 +254,7 @@ impl Default for KmsServiceManager {
}
/// Global KMS service manager instance
static GLOBAL_KMS_SERVICE_MANAGER: once_cell::sync::OnceCell<Arc<KmsServiceManager>> = once_cell::sync::OnceCell::new();
static GLOBAL_KMS_SERVICE_MANAGER: OnceLock<Arc<KmsServiceManager>> = OnceLock::new();
/// Initialize global KMS service manager
pub fn init_global_kms_service_manager() -> Arc<KmsServiceManager> {
@@ -270,12 +270,12 @@ pub fn get_global_kms_service_manager() -> Option<Arc<KmsServiceManager>> {
/// Get global encryption service (if KMS is running)
pub async fn get_global_encryption_service() -> Option<Arc<ObjectEncryptionService>> {
tracing::info!("CLAUDE DEBUG: get_global_encryption_service called");
info!("CLAUDE DEBUG: get_global_encryption_service called");
let manager = get_global_kms_service_manager().unwrap_or_else(|| {
tracing::warn!("CLAUDE DEBUG: KMS service manager not initialized, initializing now as fallback");
warn!("CLAUDE DEBUG: KMS service manager not initialized, initializing now as fallback");
init_global_kms_service_manager()
});
let service = manager.get_encryption_service().await;
tracing::info!("CLAUDE DEBUG: get_encryption_service returned: {}", service.is_some());
info!("CLAUDE DEBUG: get_encryption_service returned: {}", service.is_some());
service
}
+176
View File
@@ -42,6 +42,17 @@ pub struct DataKey {
impl DataKey {
/// Create a new data key
///
/// # Arguments
/// * `key_id` - Unique identifier for the key
/// * `version` - Key version number
/// * `plaintext` - Optional plaintext key material
/// * `ciphertext` - Encrypted key material
/// * `key_spec` - Key specification (e.g., "AES_256")
///
/// # Returns
/// A new `DataKey` instance
///
pub fn new(key_id: String, version: u32, plaintext: Option<Vec<u8>>, ciphertext: Vec<u8>, key_spec: String) -> Self {
Self {
key_id,
@@ -55,6 +66,11 @@ impl DataKey {
}
/// Clear the plaintext key material from memory for security
///
/// # Security
/// This method zeroes out the plaintext key material before dropping it
/// to prevent sensitive data from lingering in memory.
///
pub fn clear_plaintext(&mut self) {
if let Some(ref mut plaintext) = self.plaintext {
// Zero out the memory before dropping
@@ -64,6 +80,14 @@ impl DataKey {
}
/// Add metadata to the data key
///
/// # Arguments
/// * `key` - Metadata key
/// * `value` - Metadata value
///
/// # Returns
/// Updated `DataKey` instance with added metadata
///
pub fn with_metadata(mut self, key: String, value: String) -> Self {
self.metadata.insert(key, value);
self
@@ -97,6 +121,15 @@ pub struct MasterKey {
impl MasterKey {
/// Create a new master key
///
/// # Arguments
/// * `key_id` - Unique identifier for the key
/// * `algorithm` - Key algorithm (e.g., "AES-256")
/// * `created_by` - Optional creator/owner of the key
///
/// # Returns
/// A new `MasterKey` instance
///
pub fn new(key_id: String, algorithm: String, created_by: Option<String>) -> Self {
Self {
key_id,
@@ -113,6 +146,16 @@ impl MasterKey {
}
/// Create a new master key with description
///
/// # Arguments
/// * `key_id` - Unique identifier for the key
/// * `algorithm` - Key algorithm (e.g., "AES-256")
/// * `created_by` - Optional creator/owner of the key
/// * `description` - Optional key description
///
/// # Returns
/// A new `MasterKey` instance with description
///
pub fn new_with_description(
key_id: String,
algorithm: String,
@@ -218,6 +261,14 @@ pub struct GenerateKeyRequest {
impl GenerateKeyRequest {
/// Create a new generate key request
///
/// # Arguments
/// * `master_key_id` - Master key ID to use for encryption
/// * `key_spec` - Key specification (e.g., "AES_256")
///
/// # Returns
/// A new `GenerateKeyRequest` instance
///
pub fn new(master_key_id: String, key_spec: String) -> Self {
Self {
master_key_id,
@@ -229,12 +280,27 @@ impl GenerateKeyRequest {
}
/// Add encryption context
///
/// # Arguments
/// * `key` - Context key
/// * `value` - Context value
///
/// # Returns
/// Updated `GenerateKeyRequest` instance with added context
///
pub fn with_context(mut self, key: String, value: String) -> Self {
self.encryption_context.insert(key, value);
self
}
/// Set key length explicitly
///
/// # Arguments
/// * `length` - Key length in bytes
///
/// # Returns
/// Updated `GenerateKeyRequest` instance with specified key length
///
pub fn with_length(mut self, length: u32) -> Self {
self.key_length = Some(length);
self
@@ -256,6 +322,14 @@ pub struct EncryptRequest {
impl EncryptRequest {
/// Create a new encrypt request
///
/// # Arguments
/// * `key_id` - Key ID to use for encryption
/// * `plaintext` - Plaintext data to encrypt
///
/// # Returns
/// A new `EncryptRequest` instance
///
pub fn new(key_id: String, plaintext: Vec<u8>) -> Self {
Self {
key_id,
@@ -266,6 +340,14 @@ impl EncryptRequest {
}
/// Add encryption context
///
/// # Arguments
/// * `key` - Context key
/// * `value` - Context value
///
/// # Returns
/// Updated `EncryptRequest` instance with added context
///
pub fn with_context(mut self, key: String, value: String) -> Self {
self.encryption_context.insert(key, value);
self
@@ -298,6 +380,13 @@ pub struct DecryptRequest {
impl DecryptRequest {
/// Create a new decrypt request
///
/// # Arguments
/// * `ciphertext` - Ciphertext to decrypt
///
/// # Returns
/// A new `DecryptRequest` instance
///
pub fn new(ciphertext: Vec<u8>) -> Self {
Self {
ciphertext,
@@ -307,6 +396,14 @@ impl DecryptRequest {
}
/// Add encryption context
///
/// # Arguments
/// * `key` - Context key
/// * `value` - Context value
///
/// # Returns
/// Updated `DecryptRequest` instance with added context
///
pub fn with_context(mut self, key: String, value: String) -> Self {
self.encryption_context.insert(key, value);
self
@@ -365,6 +462,13 @@ pub struct OperationContext {
impl OperationContext {
/// Create a new operation context
///
/// # Arguments
/// * `principal` - User or service performing the operation
///
/// # Returns
/// A new `OperationContext` instance
///
pub fn new(principal: String) -> Self {
Self {
operation_id: Uuid::new_v4(),
@@ -376,18 +480,40 @@ impl OperationContext {
}
/// Add additional context
///
/// # Arguments
/// * `key` - Context key
/// * `value` - Context value
///
/// # Returns
/// Updated `OperationContext` instance with added context
///
pub fn with_context(mut self, key: String, value: String) -> Self {
self.additional_context.insert(key, value);
self
}
/// Set source IP
///
/// # Arguments
/// * `ip` - Source IP address
///
/// # Returns
/// Updated `OperationContext` instance with source IP
///
pub fn with_source_ip(mut self, ip: String) -> Self {
self.source_ip = Some(ip);
self
}
/// Set user agent
///
/// # Arguments
/// * `agent` - User agent string
///
/// # Returns
/// Updated `OperationContext` instance with user agent
///
pub fn with_user_agent(mut self, agent: String) -> Self {
self.user_agent = Some(agent);
self
@@ -411,6 +537,14 @@ pub struct ObjectEncryptionContext {
impl ObjectEncryptionContext {
/// Create a new object encryption context
///
/// # Arguments
/// * `bucket` - Bucket name
/// * `object_key` - Object key
///
/// # Returns
/// A new `ObjectEncryptionContext` instance
///
pub fn new(bucket: String, object_key: String) -> Self {
Self {
bucket,
@@ -422,18 +556,40 @@ impl ObjectEncryptionContext {
}
/// Set content type
///
/// # Arguments
/// * `content_type` - Content type string
///
/// # Returns
/// Updated `ObjectEncryptionContext` instance with content type
///
pub fn with_content_type(mut self, content_type: String) -> Self {
self.content_type = Some(content_type);
self
}
/// Set object size
///
/// # Arguments
/// * `size` - Object size in bytes
///
/// # Returns
/// Updated `ObjectEncryptionContext` instance with size
///
pub fn with_size(mut self, size: u64) -> Self {
self.size = Some(size);
self
}
/// Add encryption context
///
/// # Arguments
/// * `key` - Context key
/// * `value` - Context value
///
/// # Returns
/// Updated `ObjectEncryptionContext` instance with added context
///
pub fn with_encryption_context(mut self, key: String, value: String) -> Self {
self.encryption_context.insert(key, value);
self
@@ -503,6 +659,10 @@ pub enum KeySpec {
impl KeySpec {
/// Get the key size in bytes
///
/// # Returns
/// Key size in bytes
///
pub fn key_size(&self) -> usize {
match self {
Self::Aes256 => 32,
@@ -512,6 +672,10 @@ impl KeySpec {
}
/// Get the string representation for backends
///
/// # Returns
/// Key specification as a string
///
pub fn as_str(&self) -> &'static str {
match self {
Self::Aes256 => "AES_256",
@@ -636,6 +800,14 @@ pub struct GenerateDataKeyRequest {
impl GenerateDataKeyRequest {
/// Create a new generate data key request
///
/// # Arguments
/// * `key_id` - Key ID to use for encryption
/// * `key_spec` - Key specification
///
/// # Returns
/// A new `GenerateDataKeyRequest` instance
///
pub fn new(key_id: String, key_spec: KeySpec) -> Self {
Self {
key_id,
@@ -658,6 +830,10 @@ pub struct GenerateDataKeyResponse {
impl EncryptionAlgorithm {
/// Get the algorithm name as a string
///
/// # Returns
/// Algorithm name as a string
///
pub fn as_str(&self) -> &'static str {
match self {
Self::Aes256 => "AES256",