mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-13 08:36:54 +00:00
fix(replication): rebuild SSE metadata boundary for encrypted objects (#5872)
Groundwork for encrypted-object replication (backlog#1783, PR-A of 3): - classify_replication_source_encryption: accept the AES256 marker that every stored SSE-C object carries; the SseC arm was unreachable. - Fail closed on sealed material without an SSE marker (MinIO-written objects) instead of replicating ciphertext as plaintext. - Replace the dead VALID_SSE_REPLICATION_HEADERS table with a transport map keyed by the metadata keys the SSE writer actually persists, shared via the new rustfs_utils::http::object_encryption_keys module. - Structurally strip all encryption metadata from outbound replication (x-rustfs-encryption-* envelopes previously passed the filters). - Skip decrypt_checksums for encrypted objects at the boundary so its is_multipart=false (a response-path contract) cannot misroute encrypted multipart objects once managed replication opens. - Redact X-Rustfs-Replication-* SSE transport values in FileInfo Debug. A reconciliation test pins that every key encryption_material_to_metadata produces is either transport-mapped or stripped. All four SSE replication e2e contracts still assert FAILED unchanged.
This commit is contained in:
@@ -1041,7 +1041,10 @@ impl ObjectInfo {
|
||||
if let Some(data) = &self.checksum {
|
||||
if self.is_encrypted() {
|
||||
// Object-level encrypted checksum bytes require SSE decrypt material,
|
||||
// so do not expose them as plaintext checksum headers here.
|
||||
// so do not expose them as plaintext checksum headers here. The
|
||||
// `false` multipart flag feeds the response-path COMPOSITE
|
||||
// fallback; callers that need accurate multipart routing must
|
||||
// consult `is_multipart()` instead of this value.
|
||||
return Ok((HashMap::new(), false));
|
||||
}
|
||||
|
||||
@@ -1712,6 +1715,34 @@ mod tests {
|
||||
assert!(checksums.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decrypt_checksums_keeps_encrypted_multipart_flag_false_for_response_paths() {
|
||||
let checksum = rustfs_rio::Checksum::new_from_data(rustfs_rio::ChecksumType::CRC32, b"encrypted-object")
|
||||
.expect("test checksum should be valid");
|
||||
let info = ObjectInfo {
|
||||
checksum: Some(checksum.to_bytes(&[])),
|
||||
// Multipart ETag shape: md5-of-md5s with a part-count suffix.
|
||||
etag: Some("0123456789abcdef0123456789abcdef-3".to_string()),
|
||||
user_defined: Arc::new(HashMap::from([(
|
||||
rustfs_utils::http::headers::AMZ_SERVER_SIDE_ENCRYPTION.to_string(),
|
||||
"AES256".to_string(),
|
||||
)])),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let (checksums, is_multipart) = info
|
||||
.decrypt_checksums(0, &HeaderMap::new())
|
||||
.expect("encrypted checksum should fail closed");
|
||||
|
||||
// The response path infers COMPOSITE from is_multipart=true when the
|
||||
// checksum type is unreadable, so encrypted objects must keep the
|
||||
// flag false here even when the object itself is multipart. Callers
|
||||
// that need routing (replication) consult is_multipart() directly.
|
||||
assert!(checksums.is_empty());
|
||||
assert!(!is_multipart);
|
||||
assert!(info.is_multipart());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decrypt_checksums_keeps_encrypted_part_checksum_metadata() {
|
||||
let checksum = rustfs_rio::Checksum::new_from_data(rustfs_rio::ChecksumType::CRC32, b"encrypted-object")
|
||||
|
||||
Reference in New Issue
Block a user