mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-08 14:23:13 +00:00
fix(sse): rewrite data when a same-key copy changes encryption (#5618)
A same-name CopyObject marks the operation `metadata_only`, which lets the store layer rewrite `xl.meta` in place and leave the data blocks untouched. The handler independently strips the source encryption metadata and calls `sse_encryption`, which mints a *fresh* DEK. On an unversioned bucket both happen at once, so the object ends up with a new DEK sitting beside ciphertext sealed under the old one, and can never be decrypted again. The mirror case is silent: an encrypted source copied without any destination SSE keeps its ciphertext while losing the key metadata, so GET returns raw ciphertext as if it were plaintext, with HTTP 200 and no error anywhere. Keep `metadata_only` off whenever either side of the copy is encrypted, so the store layer performs a full read/write rewrite through `put_object`. This is the same resolution the versioned historical-restore path already uses for this risk (issue #4238), and it matches MinIO's `isSourceEncrypted || isTargetEncrypted -> metadataOnly = false` guard in CopyObjectHandler. The target half of the predicate deliberately tests `effective_sse` rather than the request headers MinIO inspects: `effective_sse` also resolves the bucket default-encryption rule, and `sse_encryption` mints a DEK from that resolved value. A header-only check would miss a same-key copy performed under a bucket default rule. The source half reuses `ObjectInfo::is_encrypted` so a future encryption flavour is covered here as soon as it is recognised there. Versioned buckets were already safe: that path falls through to `put_object` regardless of `metadata_only`. RestoreObject also sets `metadata_only` but only appends restore keys and never re-derives a DEK, so it is unaffected.
This commit is contained in:
@@ -969,6 +969,15 @@ impl ECStore {
|
||||
|
||||
if !dst_opts.versioned && src_opts.version_id.is_none() {
|
||||
if src_info.metadata_only {
|
||||
// Zero-copy update: only xl.meta is rewritten, the data blocks stay as they
|
||||
// are. The caller must therefore guarantee that the destination metadata
|
||||
// still describes the stored bytes. In particular a copy that re-derives
|
||||
// encryption material may NOT set metadata_only — that would leave a fresh
|
||||
// DEK beside ciphertext sealed under the old one, permanently destroying the
|
||||
// object. The S3 handler enforces this before calling in (see the
|
||||
// metadata_only decision in rustfs/src/app/object_usecase.rs); the sibling
|
||||
// versioned branch below resolves the same risk by rewriting through
|
||||
// put_object (issue #4238).
|
||||
return self.pools[pool_idx]
|
||||
.copy_object(src_bucket, &src_object, dst_bucket, &dst_object, src_info, src_opts, &dst_opts)
|
||||
.await;
|
||||
|
||||
Reference in New Issue
Block a user