fix(s3): apply Object Lock to CopyObject and multipart uploads

Merged
VI
6d ago
+1278-66

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 InvalidArgument when 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 returning InvalidRequest.
  • PutObject with lock parameters requires Content-MD5 or an x-amz-checksum-* header (InvalidRequest otherwise).
  • Under IAM enforcement, lock headers also require s3:PutObjectRetention, and the legal-hold header requires s3: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.toml files 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. Whole s3 E2E file passes (99).
  • E2E (iam_enforcement.rs): denying s3:PutObjectRetention / s3:PutObjectLegalHold blocks 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 InvalidArgument when 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-MD5 or an x-amz-checksum-* header.
  • Under IAM enforcement, lock headers require s3:PutObjectRetention and s3:PutObjectLegalHold on 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.toml files still load.