From f17725a2eada96fde2674818dc8786921341cba9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=94=90=E5=B0=8F=E9=B8=AD?= Date: Tue, 3 Mar 2026 00:44:23 +0800 Subject: [PATCH] fix(sse): allow PUT/GET without KMS when no SSE or bucket default (#2054) Co-authored-by: houseme --- rustfs/src/storage/sse.rs | 184 +++++++++++++++++++++++++++++++++----- 1 file changed, 162 insertions(+), 22 deletions(-) diff --git a/rustfs/src/storage/sse.rs b/rustfs/src/storage/sse.rs index 4e23f920f..870d60952 100644 --- a/rustfs/src/storage/sse.rs +++ b/rustfs/src/storage/sse.rs @@ -167,27 +167,32 @@ async fn prepare_sse_configuration( })); } - // Get bucket default encryption configuration + // Get bucket default encryption configuration. + // Only buckets with explicit SSE config return Ok; unconfigured buckets return ConfigNotFound. let bucket_sse_config_result = metadata_sys::get_sse_config(bucket).await; debug!("bucket_sse_config_result={:?}", bucket_sse_config_result); if let Ok((bucket_sse_config, _timestamp)) = bucket_sse_config_result { - let effective_sse = server_side_encryption - .clone() - .or_else(|| { - bucket_sse_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 - } - }) + // Use request SSE, or bucket's apply_server_side_encryption_by_default. Do NOT default to + // AES256 when neither is set (avoids forcing encryption for buckets without default SSE). + let effective_sse_opt = server_side_encryption.clone().or_else(|| { + bucket_sse_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 + } }) }) - .unwrap_or_else(|| ServerSideEncryption::from_static(ServerSideEncryption::AES256)); + }); + + let Some(effective_sse) = effective_sse_opt else { + debug!("no request SSE and no bucket default SSE; skip encryption"); + return Ok(None); + }; debug!("effective_sse={:?} (original={:?})", effective_sse, server_side_encryption); let effective_kms_key_id = ssekms_key_id.or_else(|| { @@ -1028,9 +1033,11 @@ async fn apply_managed_encryption_material( } } - let kms_key_to_use = kms_key_candidate - .clone() - .ok_or_else(|| ApiError::from(StorageError::other("No KMS key available for managed server-side encryption")))?; + let kms_key_to_use = kms_key_candidate.clone().ok_or_else(|| { + ApiError::from(StorageError::other( + "No KMS key available for managed server-side encryption (required for SSE-KMS)", + )) + })?; let provider = get_sse_dek_provider().await?; @@ -1390,6 +1397,33 @@ impl TestSseDekProvider { Self { master_key } } + /// Create a local SSE DEK provider for SSE-S3 when KMS is not configured. + /// Uses RUSTFS_SSE_S3_MASTER_KEY (base64 32-byte) if set; otherwise a built-in default. + /// Allows PUT/GET to work without KMS (backward compatible). + pub fn new_for_local_sse() -> Self { + let master_key = match std::env::var("RUSTFS_SSE_S3_MASTER_KEY") { + Ok(v) if !v.trim().is_empty() => match BASE64_STANDARD.decode(v.trim()) { + Ok(decoded) if decoded.len() == 32 => { + let mut arr = [0u8; 32]; + arr.copy_from_slice(&decoded[..32]); + tracing::info!("Using RUSTFS_SSE_S3_MASTER_KEY for SSE-S3 (KMS not configured)"); + arr + } + _ => { + tracing::warn!("RUSTFS_SSE_S3_MASTER_KEY invalid (expected base64 32 bytes); using default for SSE-S3"); + [0u8; 32] + } + }, + _ => { + tracing::debug!( + "KMS not configured; using built-in default key for SSE-S3 (set RUSTFS_SSE_S3_MASTER_KEY for production)" + ); + [0u8; 32] + } + }; + Self { master_key } + } + // Simple encryption of DEK pub(crate) fn encrypt_dek(dek: [u8; 32], cmk_value: [u8; 32]) -> Result { // Use AES-256-GCM to encrypt DEK @@ -1501,13 +1535,16 @@ pub async fn get_sse_dek_provider() -> Result, ApiError> return Ok(provider.clone()); } - // Determine provider based on environment variable - let provider: Arc = if std::env::var("__RUSTFS_SSE_SIMPLE_CMK").is_ok() { + // Determine provider: KMS when available, else test env, else local SSE-S3 fallback (no KMS) + let provider: Arc = if get_global_encryption_service().await.is_some() { + debug!("Using KmsSseDekProvider (KMS configured)"); + Arc::new(KmsSseDekProvider::new().await?) + } else if std::env::var("__RUSTFS_SSE_SIMPLE_CMK").is_ok() { debug!("Using SimpleSseDekProvider (test mode) based on __RUSTFS_SSE_SIMPLE_CMK"); Arc::new(TestSseDekProvider::new()) } else { - debug!("Using KmsSseDekProvider (production mode)"); - Arc::new(KmsSseDekProvider::new().await?) + debug!("Using local SSE-S3 provider (KMS not configured)"); + Arc::new(TestSseDekProvider::new_for_local_sse()) }; // Store in global cache @@ -2654,4 +2691,107 @@ mod tests { let err = validate_ssec_params(params).unwrap_err(); assert_eq!(err.code, S3ErrorCode::InvalidRequest); } + + // ======================================================================== + // Unit tests for issue #2041: no mandatory KMS when encryption not used + // ======================================================================== + + /// When SSE-C params are not present and no managed SSE is requested, + /// encryption should be skipped (Ok(None)). Ensures we do not require KMS + /// when the client sends no encryption headers. + #[tokio::test] + async fn test_sse_encryption_skip_when_no_ssec_and_no_managed_sse_requested() { + let request = EncryptionRequest { + bucket: "test-bucket", + key: "test-key", + server_side_encryption: None, + ssekms_key_id: None, + sse_customer_algorithm: None, + sse_customer_key: None, + sse_customer_key_md5: None, + content_size: 1024, + part_number: None, + part_key: None, + part_nonce: None, + }; + let result = sse_encryption(request).await; + match &result { + Ok(None) => {} + Ok(Some(_)) => panic!("expected no encryption material when no SSE params provided"), + Err(e) => { + assert!( + !e.message.contains("No KMS key"), + "must not require KMS when no encryption requested; got: {}", + e.message + ); + } + } + } + + /// When SSE-C params are partial or invalid, sse_encryption must return an error. + #[tokio::test] + async fn test_sse_encryption_errors_on_invalid_ssec_params() { + let bucket = "test-bucket"; + let key = "test-key"; + let sse_key = BASE64_STANDARD.encode([42u8; 32]); + let wrong_md5 = BASE64_STANDARD.encode([99u8; 16]); + + let request_wrong_md5 = EncryptionRequest { + bucket, + key, + server_side_encryption: None, + ssekms_key_id: None, + sse_customer_algorithm: Some("AES256".to_string()), + sse_customer_key: Some(sse_key.clone()), + sse_customer_key_md5: Some(wrong_md5), + content_size: 1024, + part_number: None, + part_key: None, + part_nonce: None, + }; + let err = sse_encryption(request_wrong_md5).await.unwrap_err(); + assert_eq!(err.code, S3ErrorCode::InvalidRequest); + + let request_unsupported_algorithm = EncryptionRequest { + bucket, + key, + server_side_encryption: None, + ssekms_key_id: None, + sse_customer_algorithm: Some("unsupported-algo".to_string()), + sse_customer_key: Some(sse_key), + sse_customer_key_md5: Some(BASE64_STANDARD.encode(md5::compute([42u8; 32]).0)), + content_size: 1024, + part_number: None, + part_key: None, + part_nonce: None, + }; + let err = sse_encryption(request_unsupported_algorithm).await.unwrap_err(); + assert!(err.code == S3ErrorCode::InvalidRequest || err.code == S3ErrorCode::InvalidArgument); + } + + /// When bucket has no SSE-S3/aws:kms setting and request has no SSE headers, + /// encryption should be skipped (Ok(None)). Ensures no mandatory bucket default SSE. + #[tokio::test] + async fn test_sse_prepare_encryption_skip_when_no_params_and_no_bucket_sse() { + let request = PrepareEncryptionRequest { + bucket: "test-bucket-no-sse-config", + key: "test-key", + server_side_encryption: None, + ssekms_key_id: None, + sse_customer_algorithm: None, + sse_customer_key_md5: None, + }; + let result = sse_prepare_encryption(request).await; + match &result { + Ok(None) => {} + Ok(Some(_)) => panic!("expected no encryption when bucket has no SSE config and no request SSE"), + Err(e) => { + assert!( + !e.message.contains("No KMS key"), + "must not require KMS when no bucket SSE and no request SSE; got: {}", + e.message + ); + } + } + } }