fix(s3): apply Object Lock to CopyObject and multipart uploads
Summary
Follow-up to #2718. CopyObject and multipart uploads ignored Object Lock entirely: explicit x-amz-object-lock-* headers were dropped and the bucket default retention was never applied, so in a lock-enabled bucket a copied or multipart-uploaded object could be deleted immediately.
- PutObject's lock resolution is now shared (
ObjectLockSettings) by PutObject, CopyObject, CreateMultipartUpload and CompleteMultipartUpload. CopyObject applies the request's headers or the destination bucket default, and never inherits the source object's lock. - Lock headers are validated as S3 does, with 400
InvalidArgumentwhen the mode and retain-until date are not supplied together, the date is not ISO 8601 or not in the future, the mode is not GOVERNANCE or COMPLIANCE, or the legal hold is not ON or OFF. Lock headers on a bucket without Object Lock keep returningInvalidRequest. - PutObject with lock parameters requires
Content-MD5or anx-amz-checksum-*header (InvalidRequestotherwise). - Under IAM enforcement, lock headers also require
s3:PutObjectRetention, and the legal-hold header requiress3:PutObjectLegalHold, on the destination object. - The bucket default retention is computed at commit time under the write guard, so a slow copy or upload is retained for the full configured period.
- Multipart uploads persist the requested lock settings. Older
init.tomlfiles still load. - Docs: the S3 service page describes Object Lock enforcement (this PR plus #2718).
Objects written by other services through delivery::put_object_via_store (CloudWatch Logs exports, Firehose, ELB logs, Athena results) are handled in a separate PR, since that path also ignores versioning.
Surfaces: no new endpoint or field, so no SDK change; no service/op/variant count change.
Test plan
- Unit: default and explicit lock on copy and multipart; no lock inherited from the source; every validation error on all three operations; the integrity-header requirement; the IAM action lists; multipart upload persistence round-trip and older-format load.
- E2E (
s3.rs): copied and multipart objects in a default-retention bucket are retained and need bypass to delete; explicit COMPLIANCE plus legal hold is honored. Wholes3E2E file passes (99). - E2E (
iam_enforcement.rs): denyings3:PutObjectRetention/s3:PutObjectLegalHoldblocks lock headers on PutObject, CopyObject and CreateMultipartUpload. - S3 conformance: no variants lost for PutObject, CopyObject, CreateMultipartUpload or CompleteMultipartUpload.
Summary by cubic
Fixes Object Lock for CopyObject and multipart uploads, which previously dropped lock headers and never applied the bucket default retention — objects created those ways in a lock-enabled bucket could be deleted immediately.
- PutObject, CopyObject, CreateMultipartUpload and CompleteMultipartUpload now share the same lock resolution; a copy never inherits the source's lock and applies the destination's default instead.
- Lock headers are validated like S3 (400
InvalidArgumentwhen mode and retain-until date are not paired, the date is not future ISO 8601, the mode is not GOVERNANCE/COMPLIANCE, or legal hold is not ON/OFF). - PutObject with lock headers now requires
Content-MD5or anx-amz-checksum-*header. - Under IAM enforcement, lock headers require
s3:PutObjectRetentionands3:PutObjectLegalHoldon the destination object. - Bucket default retention is computed at commit time under the write guard, so a slow copy or upload is retained for the full configured period.
- Multipart uploads persist the requested lock settings; older
init.tomlfiles still load.