From 680a017759f2599fb30313d1556a416ca6213636 Mon Sep 17 00:00:00 2001 From: reatang Date: Wed, 14 Jan 2026 22:24:15 +0800 Subject: [PATCH] Refactoring the SSE layer encryption function --- rustfs/src/storage/ecfs.rs | 116 +++++------------------ rustfs/src/storage/sse.rs | 189 ++++++++++++++++++++++++++++--------- 2 files changed, 173 insertions(+), 132 deletions(-) diff --git a/rustfs/src/storage/ecfs.rs b/rustfs/src/storage/ecfs.rs index 09295f73..479d7148 100644 --- a/rustfs/src/storage/ecfs.rs +++ b/rustfs/src/storage/ecfs.rs @@ -146,6 +146,8 @@ use tokio_util::io::{ReaderStream, StreamReader}; use tracing::{debug, error, info, instrument, warn}; use urlencoding::encode; use uuid::Uuid; +use crate::storage::sse::{EncryptionRequest, apply_encryption}; + macro_rules! try_ { ($result:expr) => { @@ -3334,40 +3336,6 @@ impl S3 for FS { let store = get_validated_store(&bucket).await?; - // TDD: Get bucket default encryption configuration - let bucket_sse_config = metadata_sys::get_sse_config(&bucket).await.ok(); - debug!("TDD: bucket_sse_config={:?}", bucket_sse_config); - - // TDD: Determine effective encryption configuration (request overrides bucket default) - let original_sse = server_side_encryption.clone(); - let effective_sse = server_side_encryption.or_else(|| { - bucket_sse_config.as_ref().and_then(|(config, _timestamp)| { - debug!("TDD: Processing bucket SSE config: {:?}", config); - config.rules.first().and_then(|rule| { - debug!("TDD: Processing SSE rule: {:?}", rule); - rule.apply_server_side_encryption_by_default.as_ref().map(|sse| { - debug!("TDD: Found SSE default: {:?}", sse); - match sse.sse_algorithm.as_str() { - "AES256" => ServerSideEncryption::from_static(ServerSideEncryption::AES256), - "aws:kms" => ServerSideEncryption::from_static(ServerSideEncryption::AWS_KMS), - _ => ServerSideEncryption::from_static(ServerSideEncryption::AES256), // fallback to AES256 - } - }) - }) - }) - }); - debug!("TDD: effective_sse={:?} (original={:?})", effective_sse, original_sse); - - let mut effective_kms_key_id = ssekms_key_id.or_else(|| { - bucket_sse_config.as_ref().and_then(|(config, _timestamp)| { - config.rules.first().and_then(|rule| { - rule.apply_server_side_encryption_by_default - .as_ref() - .and_then(|sse| sse.kms_master_key_id.clone()) - }) - }) - }); - let mut metadata = metadata.unwrap_or_default(); if let Some(content_type) = content_type { @@ -3380,24 +3348,6 @@ impl S3 for FS { metadata.insert(AMZ_OBJECT_TAGGING.to_owned(), tags.to_string()); } - // TDD: Store effective SSE information in metadata for GET responses - if let Some(sse_alg) = &sse_customer_algorithm { - metadata.insert( - "x-amz-server-side-encryption-customer-algorithm".to_string(), - sse_alg.as_str().to_string(), - ); - } - if let Some(sse_md5) = &sse_customer_key_md5 { - metadata.insert("x-amz-server-side-encryption-customer-key-md5".to_string(), sse_md5.clone()); - } - if let Some(sse) = &effective_sse { - metadata.insert("x-amz-server-side-encryption".to_string(), sse.as_str().to_string()); - } - - if let Some(kms_key_id) = &effective_kms_key_id { - metadata.insert("x-amz-server-side-encryption-aws-kms-key-id".to_string(), kms_key_id.clone()); - } - let mut opts: ObjectOptions = put_opts(&bucket, &key, version_id.clone(), &req.headers, metadata.clone()) .await .map_err(ApiError::from)?; @@ -3448,49 +3398,35 @@ impl S3 for FS { opts.want_checksum = reader.checksum(); } - // Apply SSE-C encryption if customer provided key - if let (Some(sse_alg), Some(sse_key), Some(sse_key_md5)) = - (&sse_customer_algorithm, &sse_customer_key, &sse_customer_key_md5) - { - let params = SsecParams { - algorithm: sse_alg.as_str().to_string(), - key: sse_key.clone(), - key_md5: sse_key_md5.clone(), - }; + // Apply encryption using unified SSE API + let encryption_request = EncryptionRequest { + bucket: &bucket, + key: &key, + server_side_encryption: server_side_encryption, + ssekms_key_id: ssekms_key_id, + sse_customer_algorithm: sse_customer_algorithm.clone(), + sse_customer_key: sse_customer_key, + sse_customer_key_md5: sse_customer_key_md5.clone(), + content_size: actual_size, + part_number: None, + }; - let validated = validate_ssec_params(¶ms)?; - - // Store original size for later retrieval during decryption - let original_size = if size >= 0 { size } else { actual_size }; - store_ssec_metadata(&mut metadata, &validated, original_size); - - // Apply encryption - let encrypted_reader = apply_ssec_encryption(reader, &validated, &bucket, &key); + let mut effective_sse = None; + let mut effective_kms_key_id = None; + if let Some(material) = apply_encryption(encryption_request).await? { + // Apply encryption wrapper + let encrypted_reader = material.wrap_reader(reader); reader = HashReader::new(encrypted_reader, -1, actual_size, None, None, false).map_err(ApiError::from)?; - } - // Apply managed SSE (SSE-S3 or SSE-KMS) when requested - if sse_customer_algorithm.is_none() - && let Some(sse_alg) = &effective_sse - && is_managed_sse(sse_alg) - { - let material = - create_managed_encryption_material(&bucket, &key, sse_alg, effective_kms_key_id.clone(), actual_size).await?; + // Merge encryption metadata + metadata.extend(material.metadata); - let ManagedEncryptionMaterial { - data_key, - headers, - kms_key_id: kms_key_used, - } = material; + // Update effective_kms_key_id if managed SSE was used + if let Some(kms_key_id) = material.kms_key_id { + effective_kms_key_id = Some(kms_key_id); + } - let key_bytes = data_key.plaintext_key; - let nonce = data_key.nonce; - - metadata.extend(headers); - effective_kms_key_id = Some(kms_key_used.clone()); - - let encrypt_reader = EncryptReader::new(reader, key_bytes, nonce); - reader = HashReader::new(Box::new(encrypt_reader), -1, actual_size, None, None, false).map_err(ApiError::from)?; + effective_sse = Some(material.algorithm); } let mut reader = PutObjReader::new(reader); diff --git a/rustfs/src/storage/sse.rs b/rustfs/src/storage/sse.rs index 98828026..5b2dabe8 100644 --- a/rustfs/src/storage/sse.rs +++ b/rustfs/src/storage/sse.rs @@ -93,6 +93,82 @@ use tokio::io::{AsyncRead, AsyncSeek}; use tracing::error; use crate::error::ApiError; +use rustfs_ecstore::bucket::metadata_sys; +use s3s::dto::{SSECustomerAlgorithm, SSECustomerKey, SSECustomerKeyMD5, SSEKMSKeyId}; + +// ============================================================================ +// High-Level SSE Configuration +// ============================================================================ + +/// SSE configuration resolved from request and bucket defaults +#[derive(Debug)] +pub struct SseConfiguration { + /// Effective server-side encryption algorithm (after considering bucket defaults) + pub effective_sse: Option, + /// Effective KMS key ID (after considering bucket defaults) + pub effective_kms_key_id: Option, +} + +/// Prepare SSE configuration by resolving request parameters with bucket defaults +/// +/// This function: +/// 1. Queries bucket default encryption configuration +/// 2. Resolves effective encryption (request overrides bucket default) +/// 3. Prepares metadata headers for managed SSE +/// +/// # Arguments +/// * `bucket` - Bucket name +/// * `server_side_encryption` - SSE algorithm from request (SSE-S3 or SSE-KMS) +/// * `ssekms_key_id` - KMS key ID from request +/// * `sse_customer_algorithm` - SSE-C algorithm from request +/// +/// # Returns +/// `SseConfiguration` with resolved encryption parameters and metadata headers +pub async fn prepare_sse_configuration( + bucket: &str, + server_side_encryption: Option, + ssekms_key_id: Option, +) -> Result { + use tracing::debug; + + // Get bucket default encryption configuration + let bucket_sse_config = metadata_sys::get_sse_config(bucket).await.ok(); + debug!("bucket_sse_config={:?}", bucket_sse_config); + + // Determine effective encryption configuration (request overrides bucket default) + let effective_sse = server_side_encryption.clone().or_else(|| { + bucket_sse_config.as_ref().and_then(|(config, _timestamp)| { + debug!("Processing bucket SSE config: {:?}", config); + config.rules.first().and_then(|rule| { + debug!("Processing SSE rule: {:?}", rule); + rule.apply_server_side_encryption_by_default.as_ref().map(|sse| { + debug!("Found SSE default: {:?}", sse); + match sse.sse_algorithm.as_str() { + "AES256" => ServerSideEncryption::from_static(ServerSideEncryption::AES256), + "aws:kms" => ServerSideEncryption::from_static(ServerSideEncryption::AWS_KMS), + _ => ServerSideEncryption::from_static(ServerSideEncryption::AES256), // fallback to AES256 + } + }) + }) + }) + }); + debug!("effective_sse={:?} (original={:?})", effective_sse, server_side_encryption); + + let effective_kms_key_id = ssekms_key_id.or_else(|| { + bucket_sse_config.as_ref().and_then(|(config, _timestamp)| { + config.rules.first().and_then(|rule| { + rule.apply_server_side_encryption_by_default + .as_ref() + .and_then(|sse| sse.kms_master_key_id.clone()) + }) + }) + }); + + Ok(SseConfiguration { + effective_sse, + effective_kms_key_id, + }) +} // ============================================================================ // Core Types - Unified Encryption/Decryption API @@ -106,15 +182,15 @@ pub struct EncryptionRequest<'a> { /// Object key pub key: &'a str, /// Server-side encryption algorithm (SSE-S3 or SSE-KMS) - pub server_side_encryption: Option<&'a ServerSideEncryption>, + pub server_side_encryption: Option, /// KMS key ID (for SSE-KMS) - pub ssekms_key_id: Option<&'a str>, + pub ssekms_key_id: Option, /// SSE-C algorithm (customer-provided key) - pub sse_customer_algorithm: Option<&'a ServerSideEncryption>, + pub sse_customer_algorithm: Option, /// SSE-C key (Base64-encoded) - pub sse_customer_key: Option<&'a str>, + pub sse_customer_key: Option, /// SSE-C key MD5 (Base64-encoded) - pub sse_customer_key_md5: Option<&'a str>, + pub sse_customer_key_md5: Option, /// Content size (for metadata) pub content_size: i64, /// Part number (for multipart upload, None for single-part) @@ -131,9 +207,9 @@ pub struct DecryptionRequest<'a> { /// Object metadata containing encryption headers pub metadata: &'a HashMap, /// SSE-C key (Base64-encoded) - required if object was encrypted with SSE-C - pub sse_customer_key: Option<&'a str>, + pub sse_customer_key: Option<&'a SSECustomerKey>, /// SSE-C key MD5 (Base64-encoded) - required if object was encrypted with SSE-C - pub sse_customer_key_md5: Option<&'a str>, + pub sse_customer_key_md5: Option<&'a SSECustomerKeyMD5>, /// Part number (for multipart upload, None for single-part) pub part_number: Option, } @@ -147,10 +223,12 @@ pub struct EncryptionMaterial { pub nonce: [u8; 12], /// Metadata to store with the object pub metadata: HashMap, + /// Server-side encryption algorithm + pub algorithm: ServerSideEncryption, /// Encryption type for logging/debugging pub encryption_type: EncryptionType, /// KMS key ID (for managed SSE only) - pub kms_key_id: Option, + pub kms_key_id: Option, } /// Unified decryption material returned by `apply_decryption()` @@ -237,15 +315,21 @@ impl DecryptionMaterial { /// ``` pub async fn apply_encryption(request: EncryptionRequest<'_>) -> Result, ApiError> { // Priority 1: SSE-C (customer-provided key) - if let (Some(algorithm), Some(key), Some(key_md5)) = - (request.sse_customer_algorithm, request.sse_customer_key, request.sse_customer_key_md5) - { + if let ( + Some(algorithm), + Some(key), + Some(key_md5), + ) = ( + request.sse_customer_algorithm, + request.sse_customer_key, + request.sse_customer_key_md5, + ) { return apply_ssec_encryption_material( - request.bucket, - request.key, + &request.bucket, + &request.key, algorithm, - key, - key_md5, + &key, + &key_md5, request.content_size, request.part_number, ) @@ -254,18 +338,34 @@ pub async fn apply_encryption(request: EncryptionRequest<'_>) -> Result) -> Result, ) -> Result { let params = SsecParams { - algorithm: algorithm.as_str().to_string(), + algorithm: algorithm.clone(), key: sse_key.to_string(), key_md5: sse_key_md5.to_string(), }; @@ -372,11 +472,14 @@ async fn apply_ssec_encryption_material( content_size.to_string(), ); + let algorithm = ServerSideEncryption::from(validated.algorithm); + Ok(EncryptionMaterial { key_bytes: validated.key_bytes, nonce, metadata, encryption_type: EncryptionType::SseC, + algorithm, kms_key_id: None, }) } @@ -434,8 +537,8 @@ async fn apply_ssec_decryption_material( async fn apply_managed_encryption_material( bucket: &str, key: &str, - algorithm: &ServerSideEncryption, - kms_key_id: Option<&str>, + algorithm: ServerSideEncryption, + kms_key_id: Option, content_size: i64, part_number: Option, ) -> Result { @@ -447,7 +550,7 @@ async fn apply_managed_encryption_material( return Err(ApiError::from(StorageError::other("KMS encryption service is not initialized"))); }; - if !is_managed_sse(algorithm) { + if !is_managed_sse(&algorithm) { return Err(ApiError::from(StorageError::other(format!( "Unsupported server-side encryption algorithm: {}", algorithm.as_str() @@ -466,7 +569,7 @@ async fn apply_managed_encryption_material( context = context.with_size(content_size as u64); } - let mut kms_key_candidate = kms_key_id.map(|s| s.to_string()); + let mut kms_key_candidate = kms_key_id.clone().map(|s| s.to_string()); if kms_key_candidate.is_none() { kms_key_candidate = service.get_default_key_id().cloned(); } @@ -493,10 +596,12 @@ async fn apply_managed_encryption_material( }; let mut metadata = service.metadata_to_headers(&encryption_metadata); - metadata.insert( - "x-rustfs-encryption-original-size".to_string(), - encryption_metadata.original_size.to_string(), - ); + metadata.insert("x-rustfs-encryption-original-size".to_string(), encryption_metadata.original_size.to_string()); + + // if kms_key is changed, we need to update the metadata + if kms_key_id.is_none() { + metadata.insert("x-amz-server-side-encryption-aws-kms-key-id".to_string(), kms_key_to_use.clone()); + } // Handle part-specific nonce if needed let nonce = if let Some(part_num) = part_number { @@ -509,6 +614,7 @@ async fn apply_managed_encryption_material( key_bytes: data_key.plaintext_key, nonce, metadata, + algorithm, encryption_type, kms_key_id: Some(kms_key_to_use), }) @@ -582,29 +688,29 @@ pub struct ManagedEncryptionMaterial { /// Metadata headers to store with the object pub headers: HashMap, /// KMS key ID used for encryption - pub kms_key_id: String, + pub kms_key_id: SSEKMSKeyId, } /// Validated SSE-C parameters #[derive(Debug, Clone)] pub struct ValidatedSsecParams { /// Encryption algorithm (always "AES256" for SSE-C) - pub algorithm: String, + pub algorithm: SSECustomerAlgorithm, /// Decoded encryption key bytes (32 bytes for AES-256) pub key_bytes: [u8; 32], /// Base64-encoded MD5 of the key - pub key_md5: String, + pub key_md5: SSECustomerKeyMD5, } /// SSE-C parameters from client request #[derive(Debug, Clone)] pub struct SsecParams { /// Encryption algorithm - pub algorithm: String, + pub algorithm: SSECustomerAlgorithm, /// Base64-encoded encryption key - pub key: String, + pub key: SSECustomerKey, /// Base64-encoded MD5 of the key - pub key_md5: String, + pub key_md5: SSECustomerKeyMD5, } // ============================================================================ @@ -771,10 +877,10 @@ impl SseDekProvider for SimpleSseDekProvider { // Legacy Functions (SSE-S3 / SSE-KMS) // ============================================================================ -/// Check if the algorithm is a managed SSE type (SSE-S3 or SSE-KMS) +/// Check if the server_side_encryption is a managed SSE type (SSE-S3 or SSE-KMS) #[inline] -pub fn is_managed_sse(algorithm: &ServerSideEncryption) -> bool { - matches!(algorithm.as_str(), "AES256" | "aws:kms") +pub fn is_managed_sse(server_side_encryption: &ServerSideEncryption) -> bool { + matches!(server_side_encryption.as_str(), "AES256" | "aws:kms") } /// Create managed encryption material for SSE-S3 or SSE-KMS @@ -1498,4 +1604,3 @@ mod tests { assert!(debug_str.contains("SseKms")); } } -