mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-21 11:56:38 +00:00
fix(sse): read MinIO single-part encrypted objects
Two independent defects kept them unreadable, both reached only once the fixture harness could load an inlined object at all. The stream was keyed as if the object were multipart. is_multipart_encrypted_object falls back to an ETag length test when there is a single part, and MinIO stores an *encrypted* ETag for SSE objects — 96 characters for a single-part upload, not the 32 of a plain MD5 — so every such object read as multipart and had a per-part key derived for a stream sealed with the object key itself. MinIO records the answer outright in X-Minio-Internal-Encrypted-Multipart, so that marker is now consulted first, and on a MinIO-written object its absence is treated as just as informative as its presence. The size was then reported as the ciphertext's. MinIO writes an explicit plaintext size only for multipart uploads, deriving it from the DARE stream otherwise, so both the read plan and ObjectInfo::decrypted_size fell back to the physical size — the reader waited for the encoding overhead as if it were payload, and a client would have seen a Content-Length overstated by exactly that much. dare_v2_decrypted_size inverts the package layout the way MinIO's sio.DecryptedSize does. It lives in rustfs-utils so the read plan and the size accessor cannot drift apart, and it returns None for a length no DARE stream can have rather than assigning a malformed object a plausible one. The interop suite is 9/9 with the two single-part cases added, and reverting the classification to the ETag heuristic turns both red. rustfs-ecstore's object_api tests and rustfs-utils are unaffected. Refs rustfs/backlog#1638.
This commit is contained in:
@@ -25,14 +25,16 @@ Support is stated per shape below because that is how far it has been *measured*
|
||||
| SSE-S3, multipart | **Yes** | `reads_minio_generated_sse_s3_multipart_fixture` |
|
||||
| SSE-KMS, multipart | **Yes** | `reads_minio_generated_sse_kms_multipart_fixture` |
|
||||
| SSE-C, multipart | **Yes** | `reads_minio_generated_sse_c_multipart_fixture` |
|
||||
| SSE-S3 / SSE-KMS / SSE-C, single-part | **Unverified** | No fixture coverage — see below |
|
||||
| SSE-S3, single-part | **Yes** | `reads_minio_generated_sse_s3_singlepart_fixture` |
|
||||
| SSE-KMS, single-part | **Yes** | `reads_minio_generated_sse_kms_singlepart_fixture` |
|
||||
| SSE-C, single-part | **Unverified** | No fixture coverage |
|
||||
| Sealed by KES, a KMS plugin, or MinKMS | **No**, and not planned | Re-encrypt at the source before migrating |
|
||||
|
||||
SSE-C needs no KMS at all: the customer supplies the key on each request, exactly as against MinIO. Note that a MinIO SSE-C object stores no customer-key MD5, so the usual early "these parameters do not match" rejection cannot fire for it — a wrong key is refused by the decryption itself instead, which is a different error but the same outcome.
|
||||
|
||||
Reading a supported *managed* object (SSE-S3, SSE-KMS) requires RustFS to hold the same master key MinIO used, supplied through `RUSTFS_SSE_S3_MASTER_KEY` (the production entry point, exercised by `reads_minio_generated_sse_s3_fixture_through_production_master_key_env`). MinIO's builtin KMS derives a per-ciphertext sealing key from that master secret, so the *same* secret is required — not merely an equivalently configured backend.
|
||||
|
||||
**"Unverified" means unknown, not broken.** Single-part objects below MinIO's small-file threshold carry their data inline in `xl.meta`, sharded across disks, and the interop fixture harness cannot yet load that shape — so those objects have never been read in a test either way. Do not read the table's "Yes" rows as covering them.
|
||||
**"Unverified" means unknown, not broken.** The remaining row has no fixture coverage, so it has never been read in a test either way. Do not read the table's "Yes" rows as covering it.
|
||||
|
||||
Whatever the table says, verify before you commit: **read a sample of encrypted objects, not just their listings.** A read that is not supported fails closed — ciphertext is never served as plaintext — but two properties still make it easy to discover late:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user