diff --git a/crates/e2e_test/src/kms/common.rs b/crates/e2e_test/src/kms/common.rs index 3a6c2d364..b551a9849 100644 --- a/crates/e2e_test/src/kms/common.rs +++ b/crates/e2e_test/src/kms/common.rs @@ -24,6 +24,7 @@ use crate::common::{RustFSTestEnvironment, awscurl_get, awscurl_post, init_logging as common_init_logging, local_http_client}; use aws_sdk_s3::Client; +use aws_sdk_s3::error::{ProvideErrorMetadata, SdkError}; use aws_sdk_s3::primitives::ByteStream; use aws_sdk_s3::types::ServerSideEncryption; use base64::{Engine, engine::general_purpose::STANDARD as BASE64}; @@ -50,6 +51,9 @@ pub const VAULT_TOKEN: &str = "dev-root-token"; pub const VAULT_TRANSIT_PATH: &str = "transit"; pub const VAULT_KEY_NAME: &str = "rustfs-master-key"; pub const ENV_TEST_VAULT_BIN: &str = "RUSTFS_TEST_VAULT_BIN"; +pub const SSE_C_KEY_MISMATCH_MESSAGE: &str = + "The provided encryption parameters did not match the ones used originally to encrypt the object."; +pub const SSE_C_MISSING_PARAMETERS_MESSAGE: &str = "The object was stored using a form of Server Side Encryption. The correct parameters must be provided to retrieve the object."; /// Initialize tracing for KMS tests with KMS-specific log levels pub fn init_logging() { @@ -63,6 +67,24 @@ pub fn sse_customer_key_md5_base64(key: &str) -> String { BASE64.encode(hasher.finalize()) } +pub fn assert_s3_error(result: Result>, status: u16, code: &str, message: &str, context: &str) +where + T: std::fmt::Debug, + E: ProvideErrorMetadata + std::fmt::Debug, +{ + let error = result.expect_err(context); + assert_eq!( + error.raw_response().map(|response| response.status().as_u16()), + Some(status), + "{context}: unexpected HTTP status: {error:?}" + ); + let service_error = error + .as_service_error() + .expect("request failure should retain an S3 service error"); + assert_eq!(service_error.code(), Some(code), "{context}: unexpected error code: {error:?}"); + assert_eq!(service_error.message(), Some(message), "{context}: unexpected error message: {error:?}"); +} + pub async fn kms_admin_request( base_url: &str, method: http::Method, @@ -559,7 +581,13 @@ pub async fn test_error_scenarios(s3_client: &Client, bucket: &str) -> Result<() .send() .await; - assert!(wrong_key_result.is_err(), "Download with wrong SSE-C key should fail"); + assert_s3_error( + wrong_key_result, + 400, + "InvalidRequest", + SSE_C_KEY_MISMATCH_MESSAGE, + "download with a wrong SSE-C key must be rejected", + ); info!("✅ Correctly rejected download with wrong SSE-C key"); info!("Error scenario tests completed successfully"); diff --git a/crates/e2e_test/src/kms/kms_comprehensive_test.rs b/crates/e2e_test/src/kms/kms_comprehensive_test.rs index 9ec1087a3..d4bbf604a 100644 --- a/crates/e2e_test/src/kms/kms_comprehensive_test.rs +++ b/crates/e2e_test/src/kms/kms_comprehensive_test.rs @@ -19,9 +19,9 @@ //! complex workflows. use super::common::{ - EncryptionType, LocalKMSTestEnvironment, MultipartTestConfig, create_sse_c_config, sse_customer_key_md5_base64, - test_all_multipart_encryption_types, test_kms_key_management, test_multipart_upload_with_config, test_sse_c_encryption, - test_sse_kms_encryption, test_sse_s3_encryption, + EncryptionType, LocalKMSTestEnvironment, MultipartTestConfig, SSE_C_KEY_MISMATCH_MESSAGE, assert_s3_error, + create_sse_c_config, sse_customer_key_md5_base64, test_all_multipart_encryption_types, test_kms_key_management, + test_multipart_upload_with_config, test_sse_c_encryption, test_sse_kms_encryption, test_sse_s3_encryption, }; use crate::common::{TEST_BUCKET, init_logging}; use tracing::info; @@ -191,7 +191,13 @@ async fn test_comprehensive_key_isolation() -> Result<(), Box) -> String { - let mut hasher = Md5::new(); - hasher.update(input.as_ref()); - hex::encode(hasher.finalize()) -} - /// Test encryption of zero-byte files (empty files) #[tokio::test] async fn test_kms_zero_byte_file_encryption() -> Result<(), Box> { @@ -295,7 +288,7 @@ async fn test_kms_invalid_key_scenarios() -> Result<(), Box Result<(), Box Result<(), Box Result<(), Box Result<(), Box Result<(), Box TestResult { // Without the customer key the replica must not be readable — the direct // detection point for a silent-plaintext replica (backlog#1291). let plain_read = target_client.get_object().bucket(&target_bucket).key(key).send().await; - assert!(plain_read.is_err(), "SSE-C replica must not be readable without the customer key"); + assert_s3_error( + plain_read, + 400, + "InvalidRequest", + SSE_C_MISSING_PARAMETERS_MESSAGE, + "SSE-C replica must not be readable without the customer key", + ); // A wrong customer key must fail too. let wrong_key = BASE64_STANDARD.encode("99999999999999999999999999999999"); @@ -4389,7 +4398,13 @@ async fn test_bucket_replication_sse_c_contract() -> TestResult { .sse_customer_key_md5(&wrong_key_md5) .send() .await; - assert!(wrong_read.is_err(), "SSE-C replica must reject a wrong customer key"); + assert_s3_error( + wrong_read, + 400, + "InvalidRequest", + SSE_C_KEY_MISMATCH_MESSAGE, + "SSE-C replica must reject a wrong customer key", + ); Ok(()) } @@ -4486,9 +4501,12 @@ async fn test_bucket_replication_sse_c_multipart_passthrough() -> TestResult { assert_eq!(replica.body.collect().await?.into_bytes().as_ref(), payload.as_slice()); let plain_read = target_client.get_object().bucket(&target_bucket).key(key).send().await; - assert!( - plain_read.is_err(), - "SSE-C multipart replica must not be readable without the customer key" + assert_s3_error( + plain_read, + 400, + "InvalidRequest", + SSE_C_MISSING_PARAMETERS_MESSAGE, + "SSE-C multipart replica must not be readable without the customer key", ); // Stability across scanner cycles: convergence must hold for passthrough. @@ -4892,15 +4910,12 @@ async fn test_bucket_replication_sse_c_existing_object_resync() -> TestResult { assert_eq!(replica.body.collect().await?.into_bytes().as_ref(), body.as_slice()); // No plaintext leak: the replica stays unreadable without the key. - assert!( - target_client - .get_object() - .bucket(target_bucket) - .key(key) - .send() - .await - .is_err(), - "SSE-C replica must not be readable without the customer key" + assert_s3_error( + target_client.get_object().bucket(target_bucket).key(key).send().await, + 400, + "InvalidRequest", + SSE_C_MISSING_PARAMETERS_MESSAGE, + "SSE-C resynced replica must not be readable without the customer key", ); Ok(())