Summary
On a cross-deployment CopyObject in legacy etcd bucket federation, when the proxy injects destination SSE headers into a request that supplied none, those headers suppress the destination bucket's default encryption configuration. With MINIO_KMS_AUTO_ENCRYPTION=on on the proxy:
- the proxy adds
x-amz-server-side-encryption: aws:kms (no key id) to a request that had no SSE header;
- the remote
PutObjectHandler sees an SSE header and skips its own bucket-default Apply;
- if the remote bucket's default is SSE-KMS with a specific key, the object is sealed with the remote KMS default key instead; if the remote has no KMS, the copy fails.
A client-specified SSE header keeps winning, as it should. With auto-encryption off and no local configuration for the remote bucket, nothing is injected and the destination default applies today.
Pre-existing behaviour; #163 deliberately left sseConfig.Apply in place to keep that fix minimal. Found during the Codex post-merge review of #163 and confirmed with an experiment.
Mechanism
cmd/object-handlers.go line 1409: sseConfig.Apply(r.Header, sse.ApplyOptions{AutoEncrypt: globalAutoEncryption}) runs before the federation decision (remoteCallRequired is only computed around line 1529). globalBucketSSEConfigSys.Get(dstBucket) finds no local config for a remote bucket, so Apply acts on AutoEncrypt only.
internal/bucket/encryption/bucket-sse-config.go Apply: returns early if crypto.Requested(headers); with a nil config and AutoEncrypt it sets aws:kms.
- The remote's
PutObjectHandler runs the same Apply and returns early because the header is present.
Decision
Whose default governs a federated copy: the proxy's or the destination bucket owner's? Recommendation: the destination's. The proxy has no knowledge of the remote bucket's SSE configuration or KMS, the remote already enforces its defaults for every direct PutObject, and S3 CopyObject applies the destination bucket's default encryption when the request names none.
Proposed fix
- Move the existing remote-copy decision ahead of the destination-default application, and for remote copies skip the local destination SSE-config lookup and
Apply. Explicit destination SSE options and source decryption are unchanged. Do not change the shared BucketSSEConfig.Apply, the replication receiver PUT, source KMS decryption, or local copies.
- Behaviour change to document: a proxy with auto-encryption on and a remote without any default encryption will store the copy unencrypted at the remote, which is what a direct PutObject to that remote does today.
- No matching change in the multipart handlers:
CopyObjectPartHandler forwards parts without applying bucket SSE defaults, and CreateMultipartUpload is routed to the bucket owner by the bucket-forwarding middleware (cmd/generic-handlers.go around line 474) before its handler applies defaults; multipart encryption is fixed at initiation and cannot be re-chosen per part. Site-replication upload forwarding is a different middleware and is out of scope.
Test plan
The shared-backend fixture cannot express "the proxy sees no destination SSE config while the destination sees its configured key": installing the remote bucket's config also makes it visible to the proxy, which would then inject the correct key id and mask the defect. Two layers:
- A sequential unit-style test of the pipeline
local Apply → SDK header construction → remote Apply with separate configuration views, asserting the header set that reaches the remote handler.
- An integration test with the configuration view isolated per side (fixture change), using a KMS backend that distinguishes the default key from a bucket-configured key. Cover: no client SSE with remote default KMS key; explicit
AES256; explicit KMS with and without key id; SSE-C; remote without KMS. Do not flip process-global switches from inside concurrent handlers.
Priority
P2: reproducible wrong key selection or copy failure under a common proxy configuration; no data loss.
Summary
On a cross-deployment
CopyObjectin legacy etcd bucket federation, when the proxy injects destination SSE headers into a request that supplied none, those headers suppress the destination bucket's default encryption configuration. WithMINIO_KMS_AUTO_ENCRYPTION=onon the proxy:x-amz-server-side-encryption: aws:kms(no key id) to a request that had no SSE header;PutObjectHandlersees an SSE header and skips its own bucket-defaultApply;A client-specified SSE header keeps winning, as it should. With auto-encryption off and no local configuration for the remote bucket, nothing is injected and the destination default applies today.
Pre-existing behaviour; #163 deliberately left
sseConfig.Applyin place to keep that fix minimal. Found during the Codex post-merge review of #163 and confirmed with an experiment.Mechanism
cmd/object-handlers.goline 1409:sseConfig.Apply(r.Header, sse.ApplyOptions{AutoEncrypt: globalAutoEncryption})runs before the federation decision (remoteCallRequiredis only computed around line 1529).globalBucketSSEConfigSys.Get(dstBucket)finds no local config for a remote bucket, soApplyacts onAutoEncryptonly.internal/bucket/encryption/bucket-sse-config.goApply: returns early ifcrypto.Requested(headers); with a nil config andAutoEncryptit setsaws:kms.PutObjectHandlerruns the sameApplyand returns early because the header is present.Decision
Whose default governs a federated copy: the proxy's or the destination bucket owner's? Recommendation: the destination's. The proxy has no knowledge of the remote bucket's SSE configuration or KMS, the remote already enforces its defaults for every direct PutObject, and S3
CopyObjectapplies the destination bucket's default encryption when the request names none.Proposed fix
Apply. Explicit destination SSE options and source decryption are unchanged. Do not change the sharedBucketSSEConfig.Apply, the replication receiver PUT, source KMS decryption, or local copies.CopyObjectPartHandlerforwards parts without applying bucket SSE defaults, andCreateMultipartUploadis routed to the bucket owner by the bucket-forwarding middleware (cmd/generic-handlers.goaround line 474) before its handler applies defaults; multipart encryption is fixed at initiation and cannot be re-chosen per part. Site-replication upload forwarding is a different middleware and is out of scope.Test plan
The shared-backend fixture cannot express "the proxy sees no destination SSE config while the destination sees its configured key": installing the remote bucket's config also makes it visible to the proxy, which would then inject the correct key id and mask the defect. Two layers:
local Apply → SDK header construction → remote Applywith separate configuration views, asserting the header set that reaches the remote handler.AES256; explicit KMS with and without key id; SSE-C; remote without KMS. Do not flip process-global switches from inside concurrent handlers.Priority
P2: reproducible wrong key selection or copy failure under a common proxy configuration; no data loss.