refactor(app): resolve the bucket default SSE in one place (#6236)

This commit is contained in:
Zhengchao An
2026-08-19 11:02:18 +08:00
committed by GitHub
parent 13a9d19505
commit 19cbba7ec8
+121 -71
View File
@@ -169,8 +169,8 @@ use s3s::dto::{
ObjectLockLegalHoldStatus, ObjectLockMode, ObjectLockRetention, ObjectLockRetentionMode, ObjectPart, PutObjectInput,
PutObjectOutput, Range, RequestCharged, RestoreObjectInput, RestoreObjectOutput, RestoreStatus, SSECustomerAlgorithm,
SSECustomerKeyMD5, SSEKMSKeyId, SelectObjectContentInput, SelectObjectContentOutput, ServerSideEncryption,
ServerSideEncryptionByDefault, StorageClass, StreamingBlob, TaggingDirective, TaggingHeader, Timestamp, TimestampFormat,
WebsiteRedirectLocation,
ServerSideEncryptionByDefault, ServerSideEncryptionConfiguration, StorageClass, StreamingBlob, TaggingDirective,
TaggingHeader, Timestamp, TimestampFormat, WebsiteRedirectLocation,
};
use s3s::header::{X_AMZ_RESTORE, X_AMZ_RESTORE_OUTPUT_PATH};
use s3s::stream::{ByteStream, DynByteStream, RemainingLength};
@@ -2695,6 +2695,39 @@ fn bucket_default_write_sse(sse: &ServerSideEncryptionByDefault) -> ServerSideEn
}
}
/// Resolve the effective server-side encryption for a write against the bucket's
/// default encryption configuration.
///
/// A request-level value always wins; the bucket default only fills a gap, and
/// the unknown-algorithm fallback lives once in [`bucket_default_write_sse`].
///
/// `has_explicit_ssec` suppresses the default entirely. Only COPY passes `true`
/// today: its destination may carry SSE-C, which must not also be given managed
/// encryption. PUT and extract pass `false`, matching their current behaviour —
/// see backlog#1826 for the divergence that leaves.
///
/// Callers layering further overrides (PUT's `ciphertext_passthrough`) apply
/// them to the returned pair.
fn resolve_bucket_default_sse(
bucket_sse_config: Option<&ServerSideEncryptionConfiguration>,
requested_sse: Option<ServerSideEncryption>,
requested_kms_key_id: Option<SSEKMSKeyId>,
has_explicit_ssec: bool,
) -> (Option<ServerSideEncryption>, Option<SSEKMSKeyId>) {
let bucket_default = || {
if has_explicit_ssec {
return None;
}
bucket_sse_config
.and_then(|config| config.rules.first())
.and_then(|rule| rule.apply_server_side_encryption_by_default.as_ref())
};
let effective_sse = requested_sse.or_else(|| bucket_default().map(bucket_default_write_sse));
let effective_kms_key_id = requested_kms_key_id.or_else(|| bucket_default().and_then(|sse| sse.kms_master_key_id.clone()));
(effective_sse, effective_kms_key_id)
}
fn should_use_small_eager_put_path(
size: i64,
headers: &HeaderMap,
@@ -5823,19 +5856,12 @@ impl DefaultObjectUsecase {
);
let original_sse = server_side_encryption.clone();
let mut effective_sse = server_side_encryption.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().map(|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
}
})
})
})
});
let (mut effective_sse, mut effective_kms_key_id) = resolve_bucket_default_sse(
bucket_sse_config.as_ref().map(|(config, _timestamp)| config),
server_side_encryption,
ssekms_key_id,
false,
);
debug!(
target: "rustfs::app::object_usecase",
component = "app",
@@ -5847,16 +5873,6 @@ impl DefaultObjectUsecase {
"Resolved effective SSE configuration"
);
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())
})
})
});
if ciphertext_passthrough {
// The replica keeps the source's SSE-C metadata; the bucket
// default must not claim managed encryption on it.
@@ -7658,30 +7674,12 @@ impl DefaultObjectUsecase {
}
};
let mut effective_sse = requested_sse.or_else(|| {
if has_explicit_ssec {
return None;
}
bucket_sse_config.as_ref().and_then(|(config, _)| {
config.rules.first().and_then(|rule| {
rule.apply_server_side_encryption_by_default
.as_ref()
.map(bucket_default_write_sse)
})
})
});
let mut effective_kms_key_id = requested_kms_key_id.or_else(|| {
if has_explicit_ssec {
return None;
}
bucket_sse_config.as_ref().and_then(|(config, _)| {
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 effective_sse, mut effective_kms_key_id) = resolve_bucket_default_sse(
bucket_sse_config.as_ref().map(|(config, _)| config),
requested_sse,
requested_kms_key_id,
has_explicit_ssec,
);
let h = build_ssec_read_headers(
copy_source_sse_customer_algorithm.as_ref(),
@@ -9587,28 +9585,12 @@ impl DefaultObjectUsecase {
let original_sse = server_side_encryption.or(extract_server_side_encryption_from_headers(&req.headers)?);
let bucket_sse_config = metadata_sys::get_sse_config(&bucket).await.ok();
let mut effective_sse = original_sse.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()
.map(|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),
})
})
})
});
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 effective_sse, mut effective_kms_key_id) = resolve_bucket_default_sse(
bucket_sse_config.as_ref().map(|(config, _timestamp)| config),
original_sse,
ssekms_key_id,
false,
);
if effective_sse
.as_ref()
.is_some_and(|sse| sse.as_str().eq_ignore_ascii_case(ServerSideEncryption::AWS_KMS))
@@ -10397,6 +10379,74 @@ mod tests {
}
}
fn bucket_sse_config_with(algorithm: &str, kms_key_id: Option<&str>) -> ServerSideEncryptionConfiguration {
ServerSideEncryptionConfiguration {
rules: vec![ServerSideEncryptionRule {
apply_server_side_encryption_by_default: Some(ServerSideEncryptionByDefault {
sse_algorithm: ServerSideEncryption::from(String::from(algorithm)),
kms_master_key_id: kms_key_id.map(|id| SSEKMSKeyId::from(id.to_string())),
}),
bucket_key_enabled: None,
}],
}
}
#[test]
fn resolve_bucket_default_sse_prefers_the_request_over_the_bucket_default() {
let config = bucket_sse_config_with(ServerSideEncryption::AWS_KMS, Some("bucket-key"));
let (sse, kms_key_id) = resolve_bucket_default_sse(
Some(&config),
Some(ServerSideEncryption::from_static(ServerSideEncryption::AES256)),
Some(SSEKMSKeyId::from("request-key".to_string())),
false,
);
assert_eq!(sse.as_ref().map(|sse| sse.as_str()), Some(ServerSideEncryption::AES256));
assert_eq!(kms_key_id.as_deref(), Some("request-key"));
}
#[test]
fn resolve_bucket_default_sse_fills_gaps_from_the_bucket_default() {
let config = bucket_sse_config_with(ServerSideEncryption::AWS_KMS, Some("bucket-key"));
let (sse, kms_key_id) = resolve_bucket_default_sse(Some(&config), None, None, false);
assert_eq!(sse.as_ref().map(|sse| sse.as_str()), Some(ServerSideEncryption::AWS_KMS));
assert_eq!(kms_key_id.as_deref(), Some("bucket-key"));
}
#[test]
fn resolve_bucket_default_sse_falls_back_to_aes256_for_an_unknown_algorithm() {
// Reachable only through corrupt or hand-edited bucket metadata;
// PutBucketEncryption rejects unknown algorithms. All three call sites
// now share this single decision (backlog#1826).
let config = bucket_sse_config_with("garbage", None);
let (sse, kms_key_id) = resolve_bucket_default_sse(Some(&config), None, None, false);
assert_eq!(sse.as_ref().map(|sse| sse.as_str()), Some(ServerSideEncryption::AES256));
assert!(kms_key_id.is_none());
}
#[test]
fn resolve_bucket_default_sse_suppresses_the_default_for_explicit_ssec() {
let config = bucket_sse_config_with(ServerSideEncryption::AES256, Some("bucket-key"));
let (sse, kms_key_id) = resolve_bucket_default_sse(Some(&config), None, None, true);
assert!(sse.is_none(), "an SSE-C destination must not also get managed encryption");
assert!(kms_key_id.is_none());
}
#[test]
fn resolve_bucket_default_sse_returns_nothing_without_a_bucket_default() {
let (sse, kms_key_id) = resolve_bucket_default_sse(None, None, None, false);
assert!(sse.is_none());
assert!(kms_key_id.is_none());
}
#[test]
fn put_request_user_metadata_cannot_suppress_bucket_default_retention() {
let mut metadata =