mirror of
https://github.com/rustfs/rustfs.git
synced 2026-01-17 09:40:32 +00:00
Refactor and unify SSE encryption and decryption processes
This commit is contained in:
@@ -26,9 +26,8 @@ use crate::storage::entity;
|
||||
use crate::storage::helper::OperationHelper;
|
||||
use crate::storage::options::{filter_object_metadata, get_content_sha256};
|
||||
use crate::storage::sse::{
|
||||
DecryptionRequest, InMemoryAsyncReader, ManagedEncryptionMaterial, SsecParams, apply_decryption, apply_ssec_encryption,
|
||||
create_managed_encryption_material, decrypt_managed_encryption_key, derive_part_nonce, generate_ssec_nonce, is_managed_sse,
|
||||
store_ssec_metadata, strip_managed_encryption_metadata, validate_ssec_params,
|
||||
DecryptionRequest, EncryptionRequest, InMemoryAsyncReader, apply_decryption, apply_encryption,
|
||||
decrypt_managed_encryption_key, derive_part_nonce, prepare_sse_configuration, strip_managed_encryption_metadata,
|
||||
};
|
||||
use crate::storage::{
|
||||
access::{ReqInfo, authorize_request},
|
||||
@@ -102,7 +101,7 @@ use rustfs_policy::policy::{
|
||||
action::{Action, S3Action},
|
||||
{BucketPolicy, BucketPolicyArgs, Validator},
|
||||
};
|
||||
use rustfs_rio::{CompressReader, DecryptReader, EncryptReader, EtagReader, HashReader, Reader, WarpReader};
|
||||
use rustfs_rio::{CompressReader, EncryptReader, EtagReader, HashReader, Reader, WarpReader};
|
||||
use rustfs_s3select_api::{
|
||||
object_store::bytes_stream,
|
||||
query::{Context, Query},
|
||||
@@ -138,8 +137,6 @@ use std::{
|
||||
};
|
||||
use time::{OffsetDateTime, format_description::well_known::Rfc3339};
|
||||
use tokio::sync::mpsc;
|
||||
// AsyncRead and AsyncSeek moved to sse module
|
||||
use crate::storage::sse::{EncryptionRequest, apply_encryption};
|
||||
use tokio_stream::wrappers::ReceiverStream;
|
||||
use tokio_tar::Archive;
|
||||
use tokio_util::io::{ReaderStream, StreamReader};
|
||||
@@ -992,11 +989,24 @@ impl S3 for FS {
|
||||
|
||||
let mut reader: Box<dyn Reader> = Box::new(WarpReader::new(gr.stream));
|
||||
|
||||
if let Some((key_bytes, nonce, original_size_opt)) =
|
||||
decrypt_managed_encryption_key(&src_bucket, &src_key, &src_info.user_defined).await?
|
||||
{
|
||||
reader = Box::new(DecryptReader::new(reader, key_bytes, nonce));
|
||||
if let Some(original) = original_size_opt {
|
||||
// Apply unified SSE decryption for source object if encrypted
|
||||
// Note: SSE-C for copy source is handled via copy_source_sse_customer_* headers
|
||||
let copy_source_sse_customer_key = req.input.copy_source_sse_customer_key.as_ref();
|
||||
let copy_source_sse_customer_key_md5 = req.input.copy_source_sse_customer_key_md5.as_ref();
|
||||
|
||||
let decryption_request = DecryptionRequest {
|
||||
bucket: &src_bucket,
|
||||
key: &src_key,
|
||||
metadata: &src_info.user_defined,
|
||||
sse_customer_key: copy_source_sse_customer_key,
|
||||
sse_customer_key_md5: copy_source_sse_customer_key_md5,
|
||||
part_number: None,
|
||||
parts: &src_info.parts,
|
||||
};
|
||||
|
||||
if let Some(material) = apply_decryption(decryption_request).await? {
|
||||
reader = material.wrap_single_reader(reader);
|
||||
if let Some(original) = material.original_size {
|
||||
src_info.actual_size = original;
|
||||
}
|
||||
}
|
||||
@@ -1059,43 +1069,31 @@ impl S3 for FS {
|
||||
|
||||
let mut reader = HashReader::new(reader, length, actual_size, None, None, false).map_err(ApiError::from)?;
|
||||
|
||||
if let Some(ref 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?;
|
||||
|
||||
let ManagedEncryptionMaterial {
|
||||
data_key,
|
||||
headers,
|
||||
kms_key_id: kms_key_used,
|
||||
} = material;
|
||||
|
||||
let key_bytes = data_key.plaintext_key;
|
||||
let nonce = data_key.nonce;
|
||||
|
||||
src_info.user_defined.extend(headers.into_iter());
|
||||
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)?;
|
||||
}
|
||||
|
||||
// Apply SSE-C encryption if customer-provided key is specified
|
||||
if let (Some(sse_alg), Some(sse_key), Some(sse_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_md5.clone(),
|
||||
};
|
||||
|
||||
let validated = validate_ssec_params(¶ms)?;
|
||||
let encrypted_reader = apply_ssec_encryption(reader, &validated, &bucket, &key);
|
||||
// Apply unified SSE encryption for destination object
|
||||
let encryption_request = EncryptionRequest {
|
||||
bucket: &bucket,
|
||||
key: &key,
|
||||
server_side_encryption: effective_sse.clone(),
|
||||
ssekms_key_id: effective_kms_key_id.clone(),
|
||||
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,
|
||||
};
|
||||
|
||||
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)?;
|
||||
|
||||
// Store SSE-C metadata for GET responses
|
||||
store_ssec_metadata(&mut src_info.user_defined, &validated, actual_size);
|
||||
// Merge encryption metadata
|
||||
src_info.user_defined.extend(material.metadata);
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
src_info.put_object_reader = Some(PutObjReader::new(reader));
|
||||
@@ -3496,42 +3494,15 @@ impl S3 for FS {
|
||||
metadata.insert(AMZ_OBJECT_TAGGING.to_owned(), tags);
|
||||
}
|
||||
|
||||
// TDD: Get bucket SSE configuration for multipart upload
|
||||
let bucket_sse_config = metadata_sys::get_sse_config(&bucket).await.ok();
|
||||
debug!("TDD: Got bucket SSE config for multipart: {:?}", bucket_sse_config);
|
||||
// Prepare SSE configuration using unified API
|
||||
let sse_config = prepare_sse_configuration(&bucket, server_side_encryption, ssekms_key_id).await?;
|
||||
|
||||
let effective_sse = sse_config.effective_sse.clone();
|
||||
let mut effective_kms_key_id = sse_config.effective_kms_key_id.clone();
|
||||
|
||||
// TDD: Determine effective encryption (request parameters override bucket defaults)
|
||||
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 for multipart: {:?}", config);
|
||||
config.rules.first().and_then(|rule| {
|
||||
debug!("TDD: Processing SSE rule for multipart: {:?}", rule);
|
||||
rule.apply_server_side_encryption_by_default.as_ref().map(|sse| {
|
||||
debug!("TDD: Found SSE default for multipart: {:?}", 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 for multipart={:?} (original={:?})", effective_sse, original_sse);
|
||||
|
||||
let _original_kms_key_id = ssekms_key_id.clone();
|
||||
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())
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
// Store effective SSE information in metadata for multipart upload
|
||||
// For multipart upload, generate encryption material if managed SSE is used
|
||||
// This material will be stored in metadata and used by upload_part and complete_multipart_upload
|
||||
// Note: SSE-C doesn't need key generation here, just store algorithm and key MD5
|
||||
if let Some(sse_alg) = &sse_customer_algorithm {
|
||||
metadata.insert(
|
||||
"x-amz-server-side-encryption-customer-algorithm".to_string(),
|
||||
@@ -3542,27 +3513,30 @@ impl S3 for FS {
|
||||
metadata.insert("x-amz-server-side-encryption-customer-key-md5".to_string(), sse_md5.clone());
|
||||
}
|
||||
|
||||
if let Some(sse) = &effective_sse {
|
||||
if is_managed_sse(sse) {
|
||||
let material = create_managed_encryption_material(&bucket, &key, sse, effective_kms_key_id.clone(), 0).await?;
|
||||
|
||||
let ManagedEncryptionMaterial {
|
||||
data_key: _,
|
||||
headers,
|
||||
kms_key_id: kms_key_used,
|
||||
} = material;
|
||||
|
||||
metadata.extend(headers.into_iter());
|
||||
effective_kms_key_id = Some(kms_key_used.clone());
|
||||
} else {
|
||||
metadata.insert("x-amz-server-side-encryption".to_string(), sse.as_str().to_string());
|
||||
// Generate encryption material for managed SSE (SSE-S3/SSE-KMS)
|
||||
// The encryption material (data key + nonce) will be used by upload_part and complete_multipart_upload
|
||||
let encryption_request = EncryptionRequest {
|
||||
bucket: &bucket,
|
||||
key: &key,
|
||||
server_side_encryption: effective_sse.clone(),
|
||||
ssekms_key_id: effective_kms_key_id.clone(),
|
||||
sse_customer_algorithm: None, // SSE-C is handled separately
|
||||
sse_customer_key: None,
|
||||
sse_customer_key_md5: None,
|
||||
content_size: 0, // Size unknown at multipart creation
|
||||
part_number: None,
|
||||
};
|
||||
|
||||
if let Some(material) = apply_encryption(encryption_request).await? {
|
||||
// Store encryption metadata for later use by upload_part and complete_multipart_upload
|
||||
metadata.extend(material.metadata);
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
if is_compressible(&req.headers, &key) {
|
||||
metadata.insert(
|
||||
format!("{RESERVED_METADATA_PREFIX_LOWER}compression"),
|
||||
@@ -3719,31 +3693,30 @@ impl S3 for FS {
|
||||
|
||||
let actual_size = size;
|
||||
|
||||
// Apply SSE-C encryption for upload_part 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(),
|
||||
};
|
||||
|
||||
let validated = validate_ssec_params(¶ms)?;
|
||||
|
||||
// For multipart upload, derive a unique nonce for each part
|
||||
// This ensures each part has a different nonce while maintaining determinism
|
||||
let base_nonce = generate_ssec_nonce(&bucket, &key);
|
||||
let part_nonce = derive_part_nonce(base_nonce, part_id);
|
||||
|
||||
// Apply encryption with part-specific nonce
|
||||
let encrypted_reader = EncryptReader::new(reader, validated.key_bytes, part_nonce);
|
||||
reader = Box::new(encrypted_reader);
|
||||
// Apply unified SSE encryption for upload_part
|
||||
// Note: For SSE-C, the key is provided with each part upload
|
||||
// For managed SSE, the encryption material was generated in create_multipart_upload
|
||||
let encryption_request = EncryptionRequest {
|
||||
bucket: &bucket,
|
||||
key: &key,
|
||||
server_side_encryption: None, // Managed SSE handled below
|
||||
ssekms_key_id: None,
|
||||
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: Some(part_id),
|
||||
};
|
||||
|
||||
if let Some(material) = apply_encryption(encryption_request).await? {
|
||||
// Apply encryption wrapper
|
||||
let encrypted_reader = material.wrap_reader(reader);
|
||||
reader = encrypted_reader;
|
||||
|
||||
// When encrypting, size becomes unknown since encryption adds authentication tags
|
||||
size = -1;
|
||||
|
||||
debug!("Applied SSE-C encryption to part {} with derived nonce for {}/{}", part_id, bucket, key);
|
||||
debug!("Applied {:?} encryption to part {} for {}/{}", material.encryption_type, part_id, bucket, key);
|
||||
}
|
||||
|
||||
let mut md5hex = if let Some(base64_md5) = input.content_md5 {
|
||||
@@ -3777,9 +3750,23 @@ impl S3 for FS {
|
||||
return Err(ApiError::from(StorageError::other(format!("add_checksum error={err:?}"))).into());
|
||||
}
|
||||
|
||||
if let Some((key_bytes, base_nonce, _)) = decrypt_managed_encryption_key(&bucket, &key, &fi.user_defined).await? {
|
||||
let part_nonce = derive_part_nonce(base_nonce, part_id);
|
||||
let encrypt_reader = EncryptReader::new(reader, key_bytes, part_nonce);
|
||||
// Apply managed SSE encryption if configured in multipart upload metadata
|
||||
// The encryption material (data key + nonce) was generated in create_multipart_upload
|
||||
let decryption_request = DecryptionRequest {
|
||||
bucket: &bucket,
|
||||
key: &key,
|
||||
metadata: &fi.user_defined,
|
||||
sse_customer_key: None, // SSE-C already handled above
|
||||
sse_customer_key_md5: None,
|
||||
part_number: Some(part_id),
|
||||
parts: &[],
|
||||
};
|
||||
|
||||
if let Some(material) = apply_decryption(decryption_request).await? {
|
||||
// For upload_part, we use the decryption material to get the key/nonce,
|
||||
// then apply encryption (since we're encrypting the part data)
|
||||
let part_nonce = derive_part_nonce(material.nonce, part_id);
|
||||
let encrypt_reader = EncryptReader::new(reader, material.key_bytes, part_nonce);
|
||||
reader = HashReader::new(Box::new(encrypt_reader), -1, actual_size, None, None, false).map_err(ApiError::from)?;
|
||||
}
|
||||
|
||||
@@ -3979,11 +3966,24 @@ impl S3 for FS {
|
||||
|
||||
let mut reader: Box<dyn Reader> = Box::new(WarpReader::new(src_stream));
|
||||
|
||||
if let Some((key_bytes, nonce, original_size_opt)) =
|
||||
decrypt_managed_encryption_key(&src_bucket, &src_key, &src_info.user_defined).await?
|
||||
{
|
||||
reader = Box::new(DecryptReader::new(reader, key_bytes, nonce));
|
||||
if let Some(original) = original_size_opt {
|
||||
// Apply unified SSE decryption for source object if encrypted
|
||||
// Note: SSE-C for copy source is handled via copy_source_sse_customer_* headers
|
||||
let copy_source_sse_customer_key = req.input.copy_source_sse_customer_key.as_ref();
|
||||
let copy_source_sse_customer_key_md5 = req.input.copy_source_sse_customer_key_md5.as_ref();
|
||||
|
||||
let src_decryption_request = DecryptionRequest {
|
||||
bucket: &src_bucket,
|
||||
key: &src_key,
|
||||
metadata: &src_info.user_defined,
|
||||
sse_customer_key: copy_source_sse_customer_key,
|
||||
sse_customer_key_md5: copy_source_sse_customer_key_md5,
|
||||
part_number: None,
|
||||
parts: &src_info.parts,
|
||||
};
|
||||
|
||||
if let Some(material) = apply_decryption(src_decryption_request).await? {
|
||||
reader = material.wrap_single_reader(reader);
|
||||
if let Some(original) = material.original_size {
|
||||
src_info.actual_size = original;
|
||||
}
|
||||
}
|
||||
@@ -3999,9 +3999,23 @@ impl S3 for FS {
|
||||
|
||||
let mut reader = HashReader::new(reader, size, actual_size, None, None, false).map_err(ApiError::from)?;
|
||||
|
||||
if let Some((key_bytes, base_nonce, _)) = decrypt_managed_encryption_key(&bucket, &key, &mp_info.user_defined).await? {
|
||||
let part_nonce = derive_part_nonce(base_nonce, part_id);
|
||||
let encrypt_reader = EncryptReader::new(reader, key_bytes, part_nonce);
|
||||
// Apply managed SSE encryption for destination multipart upload if configured
|
||||
// The encryption material (data key + nonce) was generated in create_multipart_upload
|
||||
let dst_decryption_request = DecryptionRequest {
|
||||
bucket: &bucket,
|
||||
key: &key,
|
||||
metadata: &mp_info.user_defined,
|
||||
sse_customer_key: None, // SSE-C not supported for upload_part_copy destination
|
||||
sse_customer_key_md5: None,
|
||||
part_number: Some(part_id),
|
||||
parts: &[],
|
||||
};
|
||||
|
||||
if let Some(material) = apply_decryption(dst_decryption_request).await? {
|
||||
// For upload_part_copy, we use the decryption material to get the key/nonce,
|
||||
// then apply encryption (since we're encrypting the part data)
|
||||
let part_nonce = derive_part_nonce(material.nonce, part_id);
|
||||
let encrypt_reader = EncryptReader::new(reader, material.key_bytes, part_nonce);
|
||||
reader = HashReader::new(Box::new(encrypt_reader), -1, actual_size, None, None, false).map_err(ApiError::from)?;
|
||||
}
|
||||
|
||||
@@ -4247,39 +4261,38 @@ impl S3 for FS {
|
||||
return Err(S3Error::with_message(S3ErrorCode::InternalError, "Not init".to_string()));
|
||||
};
|
||||
|
||||
// TDD: Get multipart info to extract encryption configuration before completing
|
||||
info!(
|
||||
"TDD: Attempting to get multipart info for bucket={}, key={}, upload_id={}",
|
||||
bucket, key, upload_id
|
||||
);
|
||||
|
||||
// Get multipart info to extract encryption configuration before completing
|
||||
let multipart_info = store
|
||||
.get_multipart_info(&bucket, &key, &upload_id, &ObjectOptions::default())
|
||||
.await
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
info!("TDD: Got multipart info successfully");
|
||||
info!("TDD: Multipart info metadata: {:?}", multipart_info.user_defined);
|
||||
|
||||
// TDD: Extract encryption information from multipart upload metadata
|
||||
// Extract encryption information from multipart upload metadata
|
||||
// For managed SSE (SSE-S3/SSE-KMS), the encryption material was generated in create_multipart_upload
|
||||
// For SSE-C, the algorithm and key MD5 were stored in metadata
|
||||
let server_side_encryption = multipart_info
|
||||
.user_defined
|
||||
.get("x-amz-server-side-encryption")
|
||||
.map(|s| ServerSideEncryption::from(s.clone()));
|
||||
info!(
|
||||
"TDD: Raw encryption from metadata: {:?} -> parsed: {:?}",
|
||||
multipart_info.user_defined.get("x-amz-server-side-encryption"),
|
||||
server_side_encryption
|
||||
);
|
||||
|
||||
let ssekms_key_id = multipart_info
|
||||
.user_defined
|
||||
.get("x-amz-server-side-encryption-aws-kms-key-id")
|
||||
.cloned();
|
||||
|
||||
info!(
|
||||
"TDD: Extracted encryption info - SSE: {:?}, KMS Key: {:?}",
|
||||
server_side_encryption, ssekms_key_id
|
||||
let _sse_customer_algorithm = multipart_info
|
||||
.user_defined
|
||||
.get("x-amz-server-side-encryption-customer-algorithm")
|
||||
.map(|s| SSECustomerAlgorithm::from(s.clone()));
|
||||
|
||||
let _sse_customer_key_md5 = multipart_info
|
||||
.user_defined
|
||||
.get("x-amz-server-side-encryption-customer-key-md5")
|
||||
.cloned();
|
||||
|
||||
debug!(
|
||||
"Extracted encryption info from multipart - SSE: {:?}, KMS Key: {:?}, SSE-C: {:?}",
|
||||
server_side_encryption, ssekms_key_id, _sse_customer_algorithm
|
||||
);
|
||||
|
||||
let obj_info = store
|
||||
@@ -4370,8 +4383,8 @@ impl S3 for FS {
|
||||
key: Some(key.clone()),
|
||||
e_tag: obj_info.etag.clone().map(|etag| to_s3s_etag(&etag)),
|
||||
location: Some("us-east-1".to_string()),
|
||||
server_side_encryption: server_side_encryption.clone(), // TDD: Return encryption info
|
||||
ssekms_key_id: ssekms_key_id.clone(), // TDD: Return KMS key ID if present
|
||||
server_side_encryption: server_side_encryption.clone(),
|
||||
ssekms_key_id: ssekms_key_id.clone(),
|
||||
checksum_crc32: checksum_crc32.clone(),
|
||||
checksum_crc32c: checksum_crc32c.clone(),
|
||||
checksum_sha1: checksum_sha1.clone(),
|
||||
@@ -4381,18 +4394,14 @@ impl S3 for FS {
|
||||
version_id: mpu_version,
|
||||
..Default::default()
|
||||
};
|
||||
info!(
|
||||
"TDD: Created output: SSE={:?}, KMS={:?}",
|
||||
output.server_side_encryption, output.ssekms_key_id
|
||||
);
|
||||
|
||||
let helper_output = entity::CompleteMultipartUploadOutput {
|
||||
bucket: Some(bucket.clone()),
|
||||
key: Some(key.clone()),
|
||||
e_tag: obj_info.etag.clone().map(|etag| to_s3s_etag(&etag)),
|
||||
location: Some("us-east-1".to_string()),
|
||||
server_side_encryption, // TDD: Return encryption info
|
||||
ssekms_key_id, // TDD: Return KMS key ID if present
|
||||
server_side_encryption,
|
||||
ssekms_key_id,
|
||||
checksum_crc32,
|
||||
checksum_crc32c,
|
||||
checksum_sha1,
|
||||
|
||||
@@ -655,13 +655,16 @@ async fn apply_managed_encryption_material(
|
||||
|
||||
// Build metadata headers
|
||||
let mut metadata = HashMap::new();
|
||||
|
||||
|
||||
// Try to use service for metadata formatting if available, otherwise build manually
|
||||
if let Some(service) = get_global_encryption_service().await {
|
||||
metadata = service.metadata_to_headers(&encryption_metadata);
|
||||
} else {
|
||||
// Manual metadata building for test mode
|
||||
metadata.insert("x-rustfs-encryption-key".to_string(), BASE64_STANDARD.encode(&encryption_metadata.encrypted_data_key));
|
||||
metadata.insert(
|
||||
"x-rustfs-encryption-key".to_string(),
|
||||
BASE64_STANDARD.encode(&encryption_metadata.encrypted_data_key),
|
||||
);
|
||||
metadata.insert("x-rustfs-encryption-iv".to_string(), BASE64_STANDARD.encode(&encryption_metadata.iv));
|
||||
metadata.insert("x-rustfs-encryption-algorithm".to_string(), encryption_metadata.algorithm.clone());
|
||||
metadata.insert("x-amz-server-side-encryption".to_string(), algorithm_str.to_string());
|
||||
@@ -671,7 +674,7 @@ async fn apply_managed_encryption_material(
|
||||
metadata.insert("x-amz-server-side-encryption-aws-kms-key-id".to_string(), kms_key_to_use.clone());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
metadata.insert(
|
||||
"x-rustfs-encryption-original-size".to_string(),
|
||||
encryption_metadata.original_size.to_string(),
|
||||
@@ -695,8 +698,8 @@ async fn apply_managed_encryption_material(
|
||||
}
|
||||
|
||||
async fn apply_managed_decryption_material(
|
||||
bucket: &str,
|
||||
key: &str,
|
||||
_bucket: &str,
|
||||
_key: &str,
|
||||
metadata: &HashMap<String, String>,
|
||||
part_number: Option<usize>,
|
||||
) -> Result<Option<DecryptionMaterial>, ApiError> {
|
||||
@@ -853,51 +856,35 @@ impl KmsSseDekProvider {
|
||||
pub async fn new() -> Result<Self, ApiError> {
|
||||
let service = get_global_encryption_service()
|
||||
.await
|
||||
.ok_or_else(|| ApiError::from(StorageError::other(
|
||||
"KMS encryption service is not initialized"
|
||||
)))?;
|
||||
.ok_or_else(|| ApiError::from(StorageError::other("KMS encryption service is not initialized")))?;
|
||||
Ok(Self { service })
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl SseDekProvider for KmsSseDekProvider {
|
||||
async fn generate_sse_dek(
|
||||
&self,
|
||||
bucket: &str,
|
||||
key: &str,
|
||||
kms_key_id: &str,
|
||||
) -> Result<(DataKey, Vec<u8>), ApiError> {
|
||||
let context = ObjectEncryptionContext::new(
|
||||
bucket.to_string(),
|
||||
key.to_string()
|
||||
);
|
||||
|
||||
async fn generate_sse_dek(&self, bucket: &str, key: &str, kms_key_id: &str) -> Result<(DataKey, Vec<u8>), ApiError> {
|
||||
let context = ObjectEncryptionContext::new(bucket.to_string(), key.to_string());
|
||||
|
||||
let kms_key_option = Some(kms_key_id.to_string());
|
||||
let (data_key, encrypted_data_key) = self.service
|
||||
let (data_key, encrypted_data_key) = self
|
||||
.service
|
||||
.create_data_key(&kms_key_option, &context)
|
||||
.await
|
||||
.map_err(|e| ApiError::from(StorageError::other(
|
||||
format!("Failed to create data key: {}", e)
|
||||
)))?;
|
||||
|
||||
.map_err(|e| ApiError::from(StorageError::other(format!("Failed to create data key: {}", e))))?;
|
||||
|
||||
Ok((data_key, encrypted_data_key))
|
||||
}
|
||||
|
||||
async fn decrypt_sse_dek(
|
||||
&self,
|
||||
encrypted_dek: &[u8],
|
||||
_kms_key_id: &str,
|
||||
) -> Result<[u8; 32], ApiError> {
|
||||
|
||||
async fn decrypt_sse_dek(&self, encrypted_dek: &[u8], _kms_key_id: &str) -> Result<[u8; 32], ApiError> {
|
||||
// Create a minimal context for decryption
|
||||
let context = ObjectEncryptionContext::new("".to_string(), "".to_string());
|
||||
let data_key = self.service
|
||||
let data_key = self
|
||||
.service
|
||||
.decrypt_data_key(encrypted_dek, &context)
|
||||
.await
|
||||
.map_err(|e| ApiError::from(StorageError::other(
|
||||
format!("Failed to decrypt data key: {}", e)
|
||||
)))?;
|
||||
|
||||
.map_err(|e| ApiError::from(StorageError::other(format!("Failed to decrypt data key: {}", e))))?;
|
||||
|
||||
Ok(data_key.plaintext_key)
|
||||
}
|
||||
}
|
||||
@@ -907,28 +894,28 @@ impl SseDekProvider for KmsSseDekProvider {
|
||||
// ============================================================================
|
||||
|
||||
/// Simple SSE DEK provider for testing purposes
|
||||
///
|
||||
///
|
||||
/// This provider reads a single 32-byte customer master key (CMK) from the
|
||||
/// `__RUSTFS_SSE_SIMPLE_CMK` environment variable. The key must be base64-encoded.
|
||||
///
|
||||
///
|
||||
/// # Environment Variable Format
|
||||
///
|
||||
///
|
||||
/// ```text
|
||||
/// __RUSTFS_SSE_SIMPLE_CMK=<base64_encoded_32_byte_key>
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
/// Example:
|
||||
/// ```bash
|
||||
/// export __RUSTFS_SSE_SIMPLE_CMK="AKHul86TBMMJ3+VrGlh9X3dHJsOtSXOXHOODPwmAnOo="
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
/// # Key Generation
|
||||
///
|
||||
///
|
||||
/// Use the provided script to generate a valid key:
|
||||
/// ```bash
|
||||
/// # Windows
|
||||
/// .\scripts\generate-sse-keys.ps1
|
||||
///
|
||||
///
|
||||
/// # Linux/Unix/macOS
|
||||
/// ./scripts/generate-sse-keys.sh
|
||||
/// ```
|
||||
@@ -956,10 +943,7 @@ impl SimpleSseDekProvider {
|
||||
arr
|
||||
}
|
||||
Err(_) => {
|
||||
eprintln!(
|
||||
"✗ Failed to load master key: decoded key is not 32 bytes (got {} bytes)",
|
||||
decoded_len
|
||||
);
|
||||
eprintln!("✗ Failed to load master key: decoded key is not 32 bytes (got {} bytes)", decoded_len);
|
||||
[0u8; 32]
|
||||
}
|
||||
}
|
||||
@@ -1092,7 +1076,7 @@ pub async fn get_sse_dek_provider() -> Result<Arc<dyn SseDekProvider>, ApiError>
|
||||
if let Some(provider) = GLOBAL_SSE_DEK_PROVIDER.get() {
|
||||
return Ok(provider.clone());
|
||||
}
|
||||
|
||||
|
||||
// Determine provider based on environment variable
|
||||
let provider: Arc<dyn SseDekProvider> = if std::env::var("__RUSTFS_SSE_SIMPLE_CMK").is_ok() {
|
||||
debug!("Using SimpleSseDekProvider (test mode) based on __RUSTFS_SSE_SIMPLE_CMK");
|
||||
@@ -1101,14 +1085,12 @@ pub async fn get_sse_dek_provider() -> Result<Arc<dyn SseDekProvider>, ApiError>
|
||||
debug!("Using KmsSseDekProvider (production mode)");
|
||||
Arc::new(KmsSseDekProvider::new().await?)
|
||||
};
|
||||
|
||||
|
||||
// Store in global cache
|
||||
GLOBAL_SSE_DEK_PROVIDER
|
||||
.set(provider.clone())
|
||||
.map_err(|_| ApiError::from(StorageError::other(
|
||||
"Failed to initialize global SSE DEK provider (already set)"
|
||||
)))?;
|
||||
|
||||
.map_err(|_| ApiError::from(StorageError::other("Failed to initialize global SSE DEK provider (already set)")))?;
|
||||
|
||||
Ok(provider)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user