test(e2e): require exact SSE-C errors (#6546)

This commit is contained in:
Zhengchao An
2026-08-25 04:32:30 +08:00
committed by GitHub
parent 116119d93a
commit 40e6decc93
6 changed files with 154 additions and 41 deletions
+29 -1
View File
@@ -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<T, E>(result: Result<T, SdkError<E>>, 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");
@@ -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<dyn std::error::Er
.send()
.await;
assert!(wrong_read_result.is_err(), "The encrypted file should not be readable with the wrong key");
assert_s3_error(
wrong_read_result,
400,
"InvalidRequest",
SSE_C_KEY_MISMATCH_MESSAGE,
"multipart SSE-C object GET with a wrong key must be rejected",
);
info!("✅ Confirm that key isolation is working correctly");
kms_env.base_env.delete_test_bucket(TEST_BUCKET).await?;
+66 -15
View File
@@ -21,21 +21,14 @@
//! - Concurrent encryption operations
//! - Security validation tests
use super::common::{LocalKMSTestEnvironment, sse_customer_key_md5_base64};
use super::common::{LocalKMSTestEnvironment, SSE_C_KEY_MISMATCH_MESSAGE, assert_s3_error, sse_customer_key_md5_base64};
use crate::common::{TEST_BUCKET, init_logging};
use aws_sdk_s3::types::ServerSideEncryption;
use base64::Engine;
use md5::{Digest as Md5Digest, Md5};
use std::sync::Arc;
use tokio::sync::Semaphore;
use tracing::{info, warn};
fn md5_hex(input: impl AsRef<[u8]>) -> 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<dyn std::error::Error + Send + Sync>> {
@@ -295,7 +288,7 @@ async fn test_kms_invalid_key_scenarios() -> Result<(), Box<dyn std::error::Erro
info!("🔍 Testing invalid SSE-C key length");
let invalid_short_key = "short"; // Too short
let invalid_key_b64 = base64::engine::general_purpose::STANDARD.encode(invalid_short_key);
let invalid_key_md5 = md5_hex(invalid_short_key);
let invalid_key_md5 = sse_customer_key_md5_base64(invalid_short_key);
let invalid_key_result = s3_client
.put_object()
@@ -308,14 +301,32 @@ async fn test_kms_invalid_key_scenarios() -> Result<(), Box<dyn std::error::Erro
.send()
.await;
assert!(invalid_key_result.is_err(), "Should reject invalid key length");
assert_s3_error(
invalid_key_result,
400,
"InvalidRequest",
"SSE-C key must be 32 bytes (256 bits), got 5 bytes.",
"invalid SSE-C key length must be rejected",
);
assert_s3_error(
s3_client
.get_object()
.bucket(TEST_BUCKET)
.key("test-invalid-key-length")
.send()
.await,
404,
"NoSuchKey",
"The specified key does not exist.",
"rejected invalid-key PUT must not create an object",
);
info!("✅ Correctly rejected invalid key length");
// Test 2: Mismatched MD5 for SSE-C
info!("🔍 Testing mismatched MD5 for SSE-C key");
let valid_key = "01234567890123456789012345678901";
let valid_key_b64 = base64::engine::general_purpose::STANDARD.encode(valid_key);
let wrong_md5 = "wrongmd5hash12345678901234567890"; // Wrong MD5
let wrong_md5 = sse_customer_key_md5_base64("98765432109876543210987654321098");
let wrong_md5_result = s3_client
.put_object()
@@ -324,11 +335,24 @@ async fn test_kms_invalid_key_scenarios() -> Result<(), Box<dyn std::error::Erro
.body(aws_sdk_s3::primitives::ByteStream::from(test_data.to_vec()))
.sse_customer_algorithm("AES256")
.sse_customer_key(&valid_key_b64)
.sse_customer_key_md5(wrong_md5)
.sse_customer_key_md5(&wrong_md5)
.send()
.await;
assert!(wrong_md5_result.is_err(), "Should reject mismatched MD5");
assert_s3_error(
wrong_md5_result,
400,
"InvalidRequest",
"The calculated MD5 hash of the key did not match the hash that was provided.",
"mismatched SSE-C key MD5 must be rejected",
);
assert_s3_error(
s3_client.get_object().bucket(TEST_BUCKET).key("test-wrong-md5").send().await,
404,
"NoSuchKey",
"The specified key does not exist.",
"rejected mismatched-MD5 PUT must not create an object",
);
info!("✅ Correctly rejected mismatched MD5");
// Test 3: Try to access SSE-C object without providing key
@@ -355,7 +379,28 @@ async fn test_kms_invalid_key_scenarios() -> Result<(), Box<dyn std::error::Erro
.send()
.await;
assert!(no_key_result.is_err(), "Should require SSE-C key for access");
assert_s3_error(
no_key_result,
400,
"InvalidRequest",
"The object was stored using a form of Server Side Encryption. The correct parameters must be provided to retrieve the object.",
"SSE-C object GET without a customer key must be rejected",
);
let recovered = s3_client
.get_object()
.bucket(TEST_BUCKET)
.key("test-sse-c-no-key-access")
.sse_customer_algorithm("AES256")
.sse_customer_key(&valid_key_b64)
.sse_customer_key_md5(&valid_key_md5)
.send()
.await?
.body
.collect()
.await?
.into_bytes();
assert_eq!(recovered.as_ref(), test_data, "failed GET must not corrupt the SSE-C object");
info!("✅ Correctly required SSE-C key for access");
kms_env.base_env.delete_test_bucket(TEST_BUCKET).await?;
@@ -563,7 +608,13 @@ async fn test_kms_key_validation_security() -> Result<(), Box<dyn std::error::Er
.send()
.await;
assert!(wrong_key_result.is_err(), "Should not be able to decrypt with wrong key");
assert_s3_error(
wrong_key_result,
400,
"InvalidRequest",
SSE_C_KEY_MISMATCH_MESSAGE,
"SSE-C object GET with the wrong customer key must be rejected",
);
info!("✅ Key isolation verified - wrong key cannot decrypt data");
kms_env.base_env.delete_test_bucket(TEST_BUCKET).await?;
+9 -2
View File
@@ -20,7 +20,8 @@
//! - Complete encryption/decryption lifecycle
use super::common::{
LocalKMSTestEnvironment, get_kms_status, sse_customer_key_md5_base64, test_kms_key_management, test_sse_c_encryption,
LocalKMSTestEnvironment, SSE_C_KEY_MISMATCH_MESSAGE, assert_s3_error, get_kms_status, sse_customer_key_md5_base64,
test_kms_key_management, test_sse_c_encryption,
};
use crate::common::{TEST_BUCKET, init_logging};
use tracing::{error, info};
@@ -196,7 +197,13 @@ async fn test_local_kms_key_isolation() {
.send()
.await;
assert!(wrong_key_result.is_err(), "Should not be able to decrypt object1 with key2");
assert_s3_error(
wrong_key_result,
400,
"InvalidRequest",
SSE_C_KEY_MISMATCH_MESSAGE,
"local SSE-C object GET with a wrong key must be rejected",
);
kms_env
.base_env
+10 -4
View File
@@ -22,9 +22,9 @@ use crate::common::{TEST_BUCKET, init_logging};
use tracing::{error, info};
use super::common::{
VAULT_KEY_NAME, VaultTestEnvironment, get_kms_status, sse_customer_key_md5_base64, start_kms,
test_all_multipart_encryption_types, test_error_scenarios, test_kms_key_management, test_sse_c_encryption,
test_sse_kms_encryption, test_sse_s3_encryption,
SSE_C_KEY_MISMATCH_MESSAGE, VAULT_KEY_NAME, VaultTestEnvironment, assert_s3_error, get_kms_status,
sse_customer_key_md5_base64, start_kms, test_all_multipart_encryption_types, test_error_scenarios, test_kms_key_management,
test_sse_c_encryption, test_sse_kms_encryption, test_sse_s3_encryption,
};
/// Helper that brings up Vault, configures RustFS, and starts the KMS service.
@@ -182,7 +182,13 @@ async fn test_vault_kms_key_isolation() -> Result<(), Box<dyn std::error::Error
.sse_customer_key_md5(&key2_md5)
.send()
.await;
assert!(wrong_key.is_err(), "Object1 should not decrypt with key2");
assert_s3_error(
wrong_key,
400,
"InvalidRequest",
SSE_C_KEY_MISMATCH_MESSAGE,
"Vault-backed SSE-C object GET with a wrong key must be rejected",
);
context
.base_env()