Skip to content

S3: SSE resolution — CopyObject, multipart, bucket defaults, and the invalid-combination 400s #493

Description

@scttfrdmn

Split from #475. The record-and-echo half is #492 and is milestoned; this carries items
2–5 of #475's own priority order, which interact with each other and are worth designing
together rather than piecemeal.

Depends on #492 — every item here is a resolution rule over the fields #492 stores.

1. CopyObject does not inherit the source's encryption

The sharpest case, and the one #475's reporter found two live bugs behind. S3 encrypts the
destination according to the request; a copy naming nothing gets the bucket default,
not the source's encryption. So an in-place CopyObject for a metadata change, or an
automatic storage-tier transition, silently moves an SSE-KMS object off the customer
managed key.

Same asymmetry family as the MetadataDirective behaviour already documented in
s3_copy_metadata.go:73 — worth following that precedent, since a reader who understands
one will expect the other.

2. Multipart records it on the create only

CreateMultipartUpload carries the encryption for the upload as a whole; UploadPart
restating it is rejected; CompleteMultipartUpload surfaces it on the assembled
object.

S3SystemMetadata is embedded in both S3Object and S3MultipartUpload precisely so the
two write paths cannot disagree (#406), so this may fall out naturally. But #475 flags a
real design question: the resolution rules differ enough that SSE may want its own struct
rather than joining that family — the way Content-Type and the storage class deliberately
stayed out. Decide that explicitly.

3. Bucket default encryption

PutBucketEncryption / GetBucketEncryption / DeleteBucketEncryption, so a request
naming nothing resolves to the bucket's setting rather than to absent.

This is what makes "off" testable as distinct from "unencrypted": S3 has applied SSE-S3
to all new objects unconditionally since January 2023, so a real bucket never stores an
object with no encryption at all. Note the interaction with #492's criterion that an object
written with no headers echoes none — that criterion exists to keep "no header sent"
observable, and this item changes it, so the change has to be deliberate and documented
rather than a side effect.

4. The invalid combinations

Each is a combination a caller can produce and currently gets silent success for — the
tracker's recurring shape, where the consumer's error branch is unreachable:

  • x-amz-server-side-encryption-aws-kms-key-id sent with AES256InvalidArgument
  • x-amz-server-side-encryption-bucket-key-enabled without aws:kmsInvalidArgument
  • an unrecognized algorithm token → InvalidArgument
  • UploadPart carrying encryption headers → 400

Per #487, each message needs its own corroborating source — a capture or a parity-tested
reimplementation — or it ships as substrate's own text and says so in the comment.

Acceptance criteria

  • A CopyObject naming no encryption does not inherit the source's; it takes the
    bucket default, and there is a test for the in-place metadata-change case
    specifically.
  • CreateMultipartUpload records it, UploadPart restating it is refused,
    CompleteMultipartUpload surfaces it on the assembled object.
  • The three bucket-encryption operations round-trip, and a PutObject naming nothing
    resolves to the bucket's default.
  • Each of the four rejections returns its code and a 400, with provenance stated.
  • docs/services.md covers the resolution order — request, then bucket default — and
    the CopyObject asymmetry alongside the existing MetadataDirective note.

Not milestoned: this wants a design pass on the resolution rules before it is scheduled.
SSE-C remains out of scope.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: s3Issues relating to the s3 componentfidelitytype: featureNew feature or improvement

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions