fix(replication): keep Object Lock on a replicated delete

A trusted replica DELETE skipped the destination's retention check for
every request, not only for the delete marker it is meant to create. A
caller allowed s3:ReplicateDelete (root, or the replication account)
could purge a version under legal hold or unexpired retention, or name
a stored version in a delete marker, which replaces that version.

Skip the check only for a delete-marker creation, whose version is not
yet stored here, and make that request refuse to replace a protected
version with RefuseLockedOverwrite, as batch replication pulls do; the
pools layer applies it to the version in any pool. A replicated purge
goes through the normal retention check. A source purge that bypassed
GOVERNANCE retention is now refused by a target whose copy still
carries it; the source records the purge as failed and retries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Feng Ruohang <rh@vonng.com>
This commit is contained in:
Feng Ruohang
2026-11-09 19:13:21 +08:00
parent 47275042f6
commit 7e9ee6befc
3 changed files with 131 additions and 3 deletions
+5 -2
View File
@@ -3193,9 +3193,12 @@ func (api objectAPIHandlers) DeleteObjectHandler(w http.ResponseWriter, r *http.
vID := opts.VersionID
if replica {
opts.SetReplicaStatus(replication.Replica)
if opts.VersionPurgeStatus().Empty() {
// opts.VersionID holds delete marker version ID to replicate and not yet present on disk
if opts.DeleteMarker && opts.VersionPurgeStatus().Empty() {
// opts.VersionID names the delete marker to replicate, not yet
// present here. It must not replace a stored version this site
// protects; a replicated purge keeps the retention check below.
vID = ""
opts.RefuseLockedOverwrite = true
}
}
opts.SetEvalRetentionBypassFn(func(goi ObjectInfo, gerr error) (err error) {