Refactoring the SSE layer encryption function

This commit is contained in:
reatang
2026-01-14 22:24:15 +08:00
parent 6bbf8b8650
commit 680a017759
2 changed files with 173 additions and 132 deletions

View File

@@ -146,6 +146,8 @@ use tokio_util::io::{ReaderStream, StreamReader};
use tracing::{debug, error, info, instrument, warn};
use urlencoding::encode;
use uuid::Uuid;
use crate::storage::sse::{EncryptionRequest, apply_encryption};
macro_rules! try_ {
($result:expr) => {
@@ -3334,40 +3336,6 @@ impl S3 for FS {
let store = get_validated_store(&bucket).await?;
// TDD: Get bucket default encryption configuration
let bucket_sse_config = metadata_sys::get_sse_config(&bucket).await.ok();
debug!("TDD: bucket_sse_config={:?}", bucket_sse_config);
// TDD: Determine effective encryption configuration (request overrides bucket default)
let original_sse = server_side_encryption.clone();
let effective_sse = server_side_encryption.or_else(|| {
bucket_sse_config.as_ref().and_then(|(config, _timestamp)| {
debug!("TDD: Processing bucket SSE config: {:?}", config);
config.rules.first().and_then(|rule| {
debug!("TDD: Processing SSE rule: {:?}", rule);
rule.apply_server_side_encryption_by_default.as_ref().map(|sse| {
debug!("TDD: 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
}
})
})
})
});
debug!("TDD: effective_sse={:?} (original={:?})", effective_sse, original_sse);
let mut effective_kms_key_id = ssekms_key_id.or_else(|| {
bucket_sse_config.as_ref().and_then(|(config, _timestamp)| {
config.rules.first().and_then(|rule| {
rule.apply_server_side_encryption_by_default
.as_ref()
.and_then(|sse| sse.kms_master_key_id.clone())
})
})
});
let mut metadata = metadata.unwrap_or_default();
if let Some(content_type) = content_type {
@@ -3380,24 +3348,6 @@ impl S3 for FS {
metadata.insert(AMZ_OBJECT_TAGGING.to_owned(), tags.to_string());
}
// TDD: Store effective SSE information in metadata for GET responses
if let Some(sse_alg) = &sse_customer_algorithm {
metadata.insert(
"x-amz-server-side-encryption-customer-algorithm".to_string(),
sse_alg.as_str().to_string(),
);
}
if let Some(sse_md5) = &sse_customer_key_md5 {
metadata.insert("x-amz-server-side-encryption-customer-key-md5".to_string(), sse_md5.clone());
}
if let Some(sse) = &effective_sse {
metadata.insert("x-amz-server-side-encryption".to_string(), sse.as_str().to_string());
}
if let Some(kms_key_id) = &effective_kms_key_id {
metadata.insert("x-amz-server-side-encryption-aws-kms-key-id".to_string(), kms_key_id.clone());
}
let mut opts: ObjectOptions = put_opts(&bucket, &key, version_id.clone(), &req.headers, metadata.clone())
.await
.map_err(ApiError::from)?;
@@ -3448,49 +3398,35 @@ impl S3 for FS {
opts.want_checksum = reader.checksum();
}
// Apply SSE-C encryption if customer provided key
if let (Some(sse_alg), Some(sse_key), Some(sse_key_md5)) =
(&sse_customer_algorithm, &sse_customer_key, &sse_customer_key_md5)
{
let params = SsecParams {
algorithm: sse_alg.as_str().to_string(),
key: sse_key.clone(),
key_md5: sse_key_md5.clone(),
};
// Apply encryption using unified SSE API
let encryption_request = EncryptionRequest {
bucket: &bucket,
key: &key,
server_side_encryption: server_side_encryption,
ssekms_key_id: ssekms_key_id,
sse_customer_algorithm: sse_customer_algorithm.clone(),
sse_customer_key: sse_customer_key,
sse_customer_key_md5: sse_customer_key_md5.clone(),
content_size: actual_size,
part_number: None,
};
let validated = validate_ssec_params(&params)?;
// Store original size for later retrieval during decryption
let original_size = if size >= 0 { size } else { actual_size };
store_ssec_metadata(&mut metadata, &validated, original_size);
// Apply encryption
let encrypted_reader = apply_ssec_encryption(reader, &validated, &bucket, &key);
let mut effective_sse = None;
let mut effective_kms_key_id = None;
if let Some(material) = apply_encryption(encryption_request).await? {
// Apply encryption wrapper
let encrypted_reader = material.wrap_reader(reader);
reader = HashReader::new(encrypted_reader, -1, actual_size, None, None, false).map_err(ApiError::from)?;
}
// Apply managed SSE (SSE-S3 or SSE-KMS) when requested
if sse_customer_algorithm.is_none()
&& let Some(sse_alg) = &effective_sse
&& is_managed_sse(sse_alg)
{
let material =
create_managed_encryption_material(&bucket, &key, sse_alg, effective_kms_key_id.clone(), actual_size).await?;
// Merge encryption metadata
metadata.extend(material.metadata);
let ManagedEncryptionMaterial {
data_key,
headers,
kms_key_id: kms_key_used,
} = material;
// Update effective_kms_key_id if managed SSE was used
if let Some(kms_key_id) = material.kms_key_id {
effective_kms_key_id = Some(kms_key_id);
}
let key_bytes = data_key.plaintext_key;
let nonce = data_key.nonce;
metadata.extend(headers);
effective_kms_key_id = Some(kms_key_used.clone());
let encrypt_reader = EncryptReader::new(reader, key_bytes, nonce);
reader = HashReader::new(Box::new(encrypt_reader), -1, actual_size, None, None, false).map_err(ApiError::from)?;
effective_sse = Some(material.algorithm);
}
let mut reader = PutObjReader::new(reader);

View File

@@ -93,6 +93,82 @@ use tokio::io::{AsyncRead, AsyncSeek};
use tracing::error;
use crate::error::ApiError;
use rustfs_ecstore::bucket::metadata_sys;
use s3s::dto::{SSECustomerAlgorithm, SSECustomerKey, SSECustomerKeyMD5, SSEKMSKeyId};
// ============================================================================
// High-Level SSE Configuration
// ============================================================================
/// SSE configuration resolved from request and bucket defaults
#[derive(Debug)]
pub struct SseConfiguration {
/// Effective server-side encryption algorithm (after considering bucket defaults)
pub effective_sse: Option<ServerSideEncryption>,
/// Effective KMS key ID (after considering bucket defaults)
pub effective_kms_key_id: Option<SSEKMSKeyId>,
}
/// Prepare SSE configuration by resolving request parameters with bucket defaults
///
/// This function:
/// 1. Queries bucket default encryption configuration
/// 2. Resolves effective encryption (request overrides bucket default)
/// 3. Prepares metadata headers for managed SSE
///
/// # Arguments
/// * `bucket` - Bucket name
/// * `server_side_encryption` - SSE algorithm from request (SSE-S3 or SSE-KMS)
/// * `ssekms_key_id` - KMS key ID from request
/// * `sse_customer_algorithm` - SSE-C algorithm from request
///
/// # Returns
/// `SseConfiguration` with resolved encryption parameters and metadata headers
pub async fn prepare_sse_configuration(
bucket: &str,
server_side_encryption: Option<ServerSideEncryption>,
ssekms_key_id: Option<SSEKMSKeyId>,
) -> Result<SseConfiguration, ApiError> {
use tracing::debug;
// Get bucket default encryption configuration
let bucket_sse_config = metadata_sys::get_sse_config(bucket).await.ok();
debug!("bucket_sse_config={:?}", bucket_sse_config);
// Determine effective encryption configuration (request overrides bucket default)
let effective_sse = server_side_encryption.clone().or_else(|| {
bucket_sse_config.as_ref().and_then(|(config, _timestamp)| {
debug!("Processing bucket SSE config: {:?}", config);
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
}
})
})
})
});
debug!("effective_sse={:?} (original={:?})", effective_sse, server_side_encryption);
let effective_kms_key_id = ssekms_key_id.or_else(|| {
bucket_sse_config.as_ref().and_then(|(config, _timestamp)| {
config.rules.first().and_then(|rule| {
rule.apply_server_side_encryption_by_default
.as_ref()
.and_then(|sse| sse.kms_master_key_id.clone())
})
})
});
Ok(SseConfiguration {
effective_sse,
effective_kms_key_id,
})
}
// ============================================================================
// Core Types - Unified Encryption/Decryption API
@@ -106,15 +182,15 @@ pub struct EncryptionRequest<'a> {
/// Object key
pub key: &'a str,
/// Server-side encryption algorithm (SSE-S3 or SSE-KMS)
pub server_side_encryption: Option<&'a ServerSideEncryption>,
pub server_side_encryption: Option<ServerSideEncryption>,
/// KMS key ID (for SSE-KMS)
pub ssekms_key_id: Option<&'a str>,
pub ssekms_key_id: Option<SSEKMSKeyId>,
/// SSE-C algorithm (customer-provided key)
pub sse_customer_algorithm: Option<&'a ServerSideEncryption>,
pub sse_customer_algorithm: Option<SSECustomerAlgorithm>,
/// SSE-C key (Base64-encoded)
pub sse_customer_key: Option<&'a str>,
pub sse_customer_key: Option<SSECustomerKey>,
/// SSE-C key MD5 (Base64-encoded)
pub sse_customer_key_md5: Option<&'a str>,
pub sse_customer_key_md5: Option<SSECustomerKeyMD5>,
/// Content size (for metadata)
pub content_size: i64,
/// Part number (for multipart upload, None for single-part)
@@ -131,9 +207,9 @@ pub struct DecryptionRequest<'a> {
/// Object metadata containing encryption headers
pub metadata: &'a HashMap<String, String>,
/// SSE-C key (Base64-encoded) - required if object was encrypted with SSE-C
pub sse_customer_key: Option<&'a str>,
pub sse_customer_key: Option<&'a SSECustomerKey>,
/// SSE-C key MD5 (Base64-encoded) - required if object was encrypted with SSE-C
pub sse_customer_key_md5: Option<&'a str>,
pub sse_customer_key_md5: Option<&'a SSECustomerKeyMD5>,
/// Part number (for multipart upload, None for single-part)
pub part_number: Option<usize>,
}
@@ -147,10 +223,12 @@ pub struct EncryptionMaterial {
pub nonce: [u8; 12],
/// Metadata to store with the object
pub metadata: HashMap<String, String>,
/// Server-side encryption algorithm
pub algorithm: ServerSideEncryption,
/// Encryption type for logging/debugging
pub encryption_type: EncryptionType,
/// KMS key ID (for managed SSE only)
pub kms_key_id: Option<String>,
pub kms_key_id: Option<SSEKMSKeyId>,
}
/// Unified decryption material returned by `apply_decryption()`
@@ -237,15 +315,21 @@ impl DecryptionMaterial {
/// ```
pub async fn apply_encryption(request: EncryptionRequest<'_>) -> Result<Option<EncryptionMaterial>, ApiError> {
// Priority 1: SSE-C (customer-provided key)
if let (Some(algorithm), Some(key), Some(key_md5)) =
(request.sse_customer_algorithm, request.sse_customer_key, request.sse_customer_key_md5)
{
if let (
Some(algorithm),
Some(key),
Some(key_md5),
) = (
request.sse_customer_algorithm,
request.sse_customer_key,
request.sse_customer_key_md5,
) {
return apply_ssec_encryption_material(
request.bucket,
request.key,
&request.bucket,
&request.key,
algorithm,
key,
key_md5,
&key,
&key_md5,
request.content_size,
request.part_number,
)
@@ -254,18 +338,34 @@ pub async fn apply_encryption(request: EncryptionRequest<'_>) -> Result<Option<E
}
// Priority 2: Managed SSE (SSE-S3 or SSE-KMS)
if let Some(sse_algorithm) = request.server_side_encryption {
if is_managed_sse(sse_algorithm) {
return apply_managed_encryption_material(
let sse_config = prepare_sse_configuration(
request.bucket,
request.server_side_encryption,
request.ssekms_key_id,
)
.await?;
if let Some(sse_algorithm) = sse_config.effective_sse {
if is_managed_sse(&sse_algorithm) {
let mut metadata = HashMap::new();
metadata.insert("x-amz-server-side-encryption".to_string(), sse_algorithm.as_str().to_string());
let material = apply_managed_encryption_material(
request.bucket,
request.key,
sse_algorithm,
request.ssekms_key_id,
sse_config.effective_kms_key_id,
request.content_size,
request.part_number,
)
.await
.map(Some);
.map(Some)?;
if let Some(mut material) = material {
material.metadata.extend(metadata);
return Ok(Some(material));
}
}
}
@@ -341,14 +441,14 @@ pub async fn apply_decryption(request: DecryptionRequest<'_>) -> Result<Option<D
async fn apply_ssec_encryption_material(
bucket: &str,
key: &str,
algorithm: &ServerSideEncryption,
sse_key: &str,
sse_key_md5: &str,
algorithm: SSECustomerAlgorithm,
sse_key: &SSECustomerKey,
sse_key_md5: &SSECustomerKeyMD5,
content_size: i64,
part_number: Option<usize>,
) -> Result<EncryptionMaterial, ApiError> {
let params = SsecParams {
algorithm: algorithm.as_str().to_string(),
algorithm: algorithm.clone(),
key: sse_key.to_string(),
key_md5: sse_key_md5.to_string(),
};
@@ -372,11 +472,14 @@ async fn apply_ssec_encryption_material(
content_size.to_string(),
);
let algorithm = ServerSideEncryption::from(validated.algorithm);
Ok(EncryptionMaterial {
key_bytes: validated.key_bytes,
nonce,
metadata,
encryption_type: EncryptionType::SseC,
algorithm,
kms_key_id: None,
})
}
@@ -434,8 +537,8 @@ async fn apply_ssec_decryption_material(
async fn apply_managed_encryption_material(
bucket: &str,
key: &str,
algorithm: &ServerSideEncryption,
kms_key_id: Option<&str>,
algorithm: ServerSideEncryption,
kms_key_id: Option<SSEKMSKeyId>,
content_size: i64,
part_number: Option<usize>,
) -> Result<EncryptionMaterial, ApiError> {
@@ -447,7 +550,7 @@ async fn apply_managed_encryption_material(
return Err(ApiError::from(StorageError::other("KMS encryption service is not initialized")));
};
if !is_managed_sse(algorithm) {
if !is_managed_sse(&algorithm) {
return Err(ApiError::from(StorageError::other(format!(
"Unsupported server-side encryption algorithm: {}",
algorithm.as_str()
@@ -466,7 +569,7 @@ async fn apply_managed_encryption_material(
context = context.with_size(content_size as u64);
}
let mut kms_key_candidate = kms_key_id.map(|s| s.to_string());
let mut kms_key_candidate = kms_key_id.clone().map(|s| s.to_string());
if kms_key_candidate.is_none() {
kms_key_candidate = service.get_default_key_id().cloned();
}
@@ -493,10 +596,12 @@ async fn apply_managed_encryption_material(
};
let mut metadata = service.metadata_to_headers(&encryption_metadata);
metadata.insert(
"x-rustfs-encryption-original-size".to_string(),
encryption_metadata.original_size.to_string(),
);
metadata.insert("x-rustfs-encryption-original-size".to_string(), encryption_metadata.original_size.to_string());
// if kms_key is changed, we need to update the metadata
if kms_key_id.is_none() {
metadata.insert("x-amz-server-side-encryption-aws-kms-key-id".to_string(), kms_key_to_use.clone());
}
// Handle part-specific nonce if needed
let nonce = if let Some(part_num) = part_number {
@@ -509,6 +614,7 @@ async fn apply_managed_encryption_material(
key_bytes: data_key.plaintext_key,
nonce,
metadata,
algorithm,
encryption_type,
kms_key_id: Some(kms_key_to_use),
})
@@ -582,29 +688,29 @@ pub struct ManagedEncryptionMaterial {
/// Metadata headers to store with the object
pub headers: HashMap<String, String>,
/// KMS key ID used for encryption
pub kms_key_id: String,
pub kms_key_id: SSEKMSKeyId,
}
/// Validated SSE-C parameters
#[derive(Debug, Clone)]
pub struct ValidatedSsecParams {
/// Encryption algorithm (always "AES256" for SSE-C)
pub algorithm: String,
pub algorithm: SSECustomerAlgorithm,
/// Decoded encryption key bytes (32 bytes for AES-256)
pub key_bytes: [u8; 32],
/// Base64-encoded MD5 of the key
pub key_md5: String,
pub key_md5: SSECustomerKeyMD5,
}
/// SSE-C parameters from client request
#[derive(Debug, Clone)]
pub struct SsecParams {
/// Encryption algorithm
pub algorithm: String,
pub algorithm: SSECustomerAlgorithm,
/// Base64-encoded encryption key
pub key: String,
pub key: SSECustomerKey,
/// Base64-encoded MD5 of the key
pub key_md5: String,
pub key_md5: SSECustomerKeyMD5,
}
// ============================================================================
@@ -771,10 +877,10 @@ impl SseDekProvider for SimpleSseDekProvider {
// Legacy Functions (SSE-S3 / SSE-KMS)
// ============================================================================
/// Check if the algorithm is a managed SSE type (SSE-S3 or SSE-KMS)
/// Check if the server_side_encryption is a managed SSE type (SSE-S3 or SSE-KMS)
#[inline]
pub fn is_managed_sse(algorithm: &ServerSideEncryption) -> bool {
matches!(algorithm.as_str(), "AES256" | "aws:kms")
pub fn is_managed_sse(server_side_encryption: &ServerSideEncryption) -> bool {
matches!(server_side_encryption.as_str(), "AES256" | "aws:kms")
}
/// Create managed encryption material for SSE-S3 or SSE-KMS
@@ -1498,4 +1604,3 @@ mod tests {
assert!(debug_str.contains("SseKms"));
}
}