From 60f14cb0f1dfe1a974358f2aac7338dc0daa2864 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=89=E6=AD=A3=E8=B6=85?= Date: Sat, 6 Jun 2026 10:57:48 +0800 Subject: [PATCH] fix(bucket-encryption): populate default KMS key for SSE-KMS without key ID (#3225) * fix(bucket-encryption): populate default KMS key for SSE-KMS without key ID When PutBucketEncryption receives SSE-KMS configuration without a specific KMS key ID, query the KMS service for the default key and populate KMSMasterKeyID before storing. This ensures GetBucketEncryption responses include the key ID, which S3 clients like mc rely on to distinguish SSE-KMS from SSE-S3 in their display logic. Fixes #3039 * test(kms): cover empty bucket encryption key ID * fix(bucket-encryption): require default KMS key --- .../src/kms/bucket_default_encryption_test.rs | 72 +++++++++++++++++++ rustfs/src/app/bucket_usecase.rs | 21 +++++- 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/crates/e2e_test/src/kms/bucket_default_encryption_test.rs b/crates/e2e_test/src/kms/bucket_default_encryption_test.rs index 8a1a70fc5..c0648c1ca 100644 --- a/crates/e2e_test/src/kms/bucket_default_encryption_test.rs +++ b/crates/e2e_test/src/kms/bucket_default_encryption_test.rs @@ -520,3 +520,75 @@ async fn test_explicit_encryption_overrides_bucket_default() -> Result<(), Box Result<(), Box> { + init_logging(); + info!("Testing SSE-KMS without explicit key ID populates default key"); + + let mut kms_env = LocalKMSTestEnvironment::new().await?; + let default_key_id = kms_env.start_rustfs_for_local_kms().await?; + tokio::time::sleep(tokio::time::Duration::from_secs(3)).await; + + let s3_client = kms_env.base_env.create_s3_client(); + kms_env.base_env.create_test_bucket(TEST_BUCKET).await?; + + // Set bucket encryption to SSE-KMS WITHOUT specifying a key ID + info!("Setting bucket default encryption to SSE-KMS without key ID"); + let encryption_config = ServerSideEncryptionConfiguration::builder() + .rules( + ServerSideEncryptionRule::builder() + .apply_server_side_encryption_by_default( + ServerSideEncryptionByDefault::builder() + .sse_algorithm(ServerSideEncryption::AwsKms) + .kms_master_key_id("") + .build() + .unwrap(), + ) + .build(), + ) + .build() + .unwrap(); + + s3_client + .put_bucket_encryption() + .bucket(TEST_BUCKET) + .server_side_encryption_configuration(encryption_config) + .send() + .await + .expect("Failed to set bucket SSE-KMS encryption without key ID"); + + // GetBucketEncryption should return the default KMS key ID + info!("Verifying GetBucketEncryption returns default KMS key ID"); + let get_response = s3_client + .get_bucket_encryption() + .bucket(TEST_BUCKET) + .send() + .await + .expect("Failed to get bucket encryption"); + + let config = get_response.server_side_encryption_configuration(); + let rule = config + .and_then(|c| c.rules().first()) + .expect("Should have at least one encryption rule"); + let by_default = rule + .apply_server_side_encryption_by_default() + .expect("Should have ApplyServerSideEncryptionByDefault"); + + assert_eq!(by_default.sse_algorithm(), &ServerSideEncryption::AwsKms, "Algorithm should be aws:kms"); + assert!( + by_default.kms_master_key_id().is_some(), + "KMS key ID should be populated with the default key (was None)" + ); + assert_eq!( + by_default.kms_master_key_id().unwrap(), + &default_key_id, + "KMS key ID should match the configured default key" + ); + + info!("Test passed: SSE-KMS without key ID correctly populates default key '{}'", default_key_id); + Ok(()) +} diff --git a/rustfs/src/app/bucket_usecase.rs b/rustfs/src/app/bucket_usecase.rs index f16220392..716b48d15 100644 --- a/rustfs/src/app/bucket_usecase.rs +++ b/rustfs/src/app/bucket_usecase.rs @@ -1501,10 +1501,29 @@ impl DefaultBucketUsecase { ) -> S3Result> { let PutBucketEncryptionInput { bucket, - server_side_encryption_configuration, + mut server_side_encryption_configuration, .. } = req.input; + // When SSE-KMS is set without a specific key ID, populate the default + // KMS key so that GetBucketEncryption responses include it. Clients like + // mc rely on the presence of KMSMasterKeyID to distinguish SSE-KMS from + // SSE-S3 in their display logic. + if let Some(rule) = server_side_encryption_configuration.rules.first_mut() + && let Some(ref mut by_default) = rule.apply_server_side_encryption_by_default + && by_default.sse_algorithm.as_str() == ServerSideEncryption::AWS_KMS + && by_default.kms_master_key_id.as_deref().is_none_or(str::is_empty) + { + let service = rustfs_kms::service_manager::get_global_encryption_service() + .await + .ok_or_else(|| S3Error::with_message(S3ErrorCode::InternalError, "KMS service not initialized".to_string()))?; + let default_key = service + .get_default_key_id() + .filter(|key| !key.is_empty()) + .ok_or_else(|| S3Error::with_message(S3ErrorCode::InternalError, "KMS default key not configured".to_string()))?; + by_default.kms_master_key_id = Some(default_key.clone()); + } + info!("sse_config {:?}", &server_side_encryption_configuration); let Some(store) = new_object_layer_fn() else {