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 {