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:
唐小鸭
2026-08-09 11:05:11 +08:00
committed by GitHub
parent 9996d567d9
commit 10c7476883
7 changed files with 506 additions and 68 deletions
+29 -1
View File
@@ -278,7 +278,11 @@ pub struct FileInfo {
fn is_sensitive_metadata_key(key: &str) -> bool {
// `is_encryption_metadata_key` covers the x-minio-internal- SSE prefix but not
// its x-rustfs-internal- twin, which the dual-key invariant writes alongside it.
is_encryption_metadata_key(key) || starts_with_ignore_ascii_case(key, "x-rustfs-internal-server-side-encryption-")
is_encryption_metadata_key(key)
|| starts_with_ignore_ascii_case(key, "x-rustfs-internal-server-side-encryption-")
|| rustfs_utils::http::REPLICATION_SSE_TRANSPORT_PREFIXES
.iter()
.any(|prefix| starts_with_ignore_ascii_case(key, prefix))
}
struct RedactedMetadata<'a>(&'a HashMap<String, String>);
@@ -2559,6 +2563,30 @@ mod tests {
assert!(dump.contains("text/plain"));
}
#[test]
fn debug_redacts_replication_sse_transport_metadata_values() {
let sealed_key = "IAAfANqt7wIJfVSgFAG3f5S6HuC2eyM5DdJlx7RSJKw2ZakSb3d5";
let mut fi = FileInfo::default();
for key in [
"X-Rustfs-Replication-Server-Side-Encryption-Sealed-Key",
"X-Rustfs-Replication-Server-Side-Encryption-Iv",
"X-Rustfs-Replication-Encryption-Iv",
"X-Rustfs-Replication-Ssec-Key-Md5",
] {
fi.metadata.insert(key.to_string(), sealed_key.to_string());
}
fi.metadata.insert("content-type".to_string(), "text/plain".to_string());
let dump = format!("{fi:?}");
assert!(
!dump.contains(sealed_key),
"replication SSE transport value leaked into Debug output: {dump}"
);
assert!(dump.contains("X-Rustfs-Replication-Server-Side-Encryption-Sealed-Key"));
assert!(dump.contains(&format!("<redacted {} bytes>", sealed_key.len())));
assert!(dump.contains("text/plain"));
}
#[test]
fn debug_elides_inline_data_bytes() {
let fi = FileInfo {