Australian cloud for business software — built and run in Sydney since 2003

NetLeverage
NetLeverage

S3 Object Lock and versioning: designing backups that an admin cannot delete

Technical article
Cloud

About this article: AI-assisted writing, reviewed against the cited sources. This is source research, not a lab validation of the procedures. Check the stated versions and your own recovery requirements before making changes.

Only compliance-mode Object Lock stops every user, including the root user, from permanently deleting an object version before its retain-until date. Versioning, MFA delete, legal holds and governance mode each leave an identity that can undo them.

Compliance mode still protects only the versions that were locked, and only until their dates pass. It does not stop the account being closed or an encryption key being deleted. A design that holds up needs a separate account, a retention period worked out from the backup chain, and guards on the bucket settings.

Commands below come from the AWS references, except where marked as written for this article. None was run in a lab.

Why it matters

Consider a hypothetical attacker who holds an administrator role in the account that stores the backups. Removing the copies would block recovery, so the protection has to assume that the person issuing the delete has valid credentials and broad permissions.

Permissions alone cannot answer that threat, because the attacker can often change the permissions. The useful question for each control is therefore: which identity can remove this protection, and what does it need to do so?

How a delete behaves on a versioned bucket

Object Lock works only in buckets that have S3 Versioning enabled, and the lock is held in the metadata of one object version. A lock protects only the version named in the request. It does not prevent new versions or delete markers being added on top.

A delete marker is a placeholder that makes S3 behave as if the object is deleted. The two kinds of delete give very different results on a locked object.

Request Result on a locked version What the client sees
Simple delete, no version ID Marker added, data kept 200 OK
Permanent delete, version ID given Refused 403 Access Denied
GET, no version ID, marker is current Nothing returned 404 and x-amz-delete-marker: true
GET naming the marker's version ID Nothing returned 405 and x-amz-delete-marker: true

Two consequences matter for backups.

A 200 on delete does not show that data has gone. A backup catalogue or clean-up script can report success and the bucket can look empty in an ordinary listing while every locked version remains. ListObjectsV2 does not return objects whose current version is a delete marker, and only ListObjectVersions lists the markers.

Delete markers themselves are not WORM-protected, whatever lock sits on the object beneath. WORM means write once, read many. An attacker can add markers freely, but cannot use them to destroy locked data.

What each control stops

Control Who can undo it Holds against a stolen admin role?
Versioning alone Anyone allowed s3:DeleteObjectVersion. Versioning can be suspended on a bucket without Object Lock No
MFA delete Root user with the MFA device Against non-root roles, yes. Not against a compromised root user and device
Legal hold Any user with s3:PutObjectLegalHold No, if the role has that permission
Governance retention A user with s3:BypassGovernanceRetention who sends the bypass header No, if the role has that permission
Compliance retention No user, including root, until the date passes Yes, for the locked version

The last column is an inference from the permission rules, not a vendor statement.

Governance bypass is easy to trigger. The console adds the bypass header by default, so a console delete by a user holding the permission succeeds without any extra step. In the CLI, the equivalent is --bypass-governance-retention.

A legal hold is not a retention control. It has no end date and is independent of retention periods. It keeps a version protected after its retention expires, until someone with the permission removes it. Bypassing governance does not remove a legal hold.

MFA delete has operating costs. Only the bucket owner (root account) can enable it, it cannot be enabled from the console, and it cannot be used with lifecycle configurations. A backup bucket that relies on lifecycle rules to remove aged versions cannot use it.

Compliance mode cannot be walked back. A locked version's mode cannot be changed and its period cannot be shortened. AWS states that the only way to delete such an object early is to delete the associated AWS account.

How retention dates are set

Retention is per version, so different versions of one key can carry different modes and dates. The rules that affect design are below.

Variable retention

S3 also offers variable retention, where an event hold defers the date. While the hold is on, S3 computes the retain-until date as the current time plus the duration, so it keeps moving forward. Releasing the hold fixes the date at release time plus the duration. AWS names a configurable recovery window against ransomware as a use.

The duration can be 1 to 36,500 days or 1 to 100 years. Releasing a hold does not end protection, because the version stays WORM-protected for the full duration after release.

Variable retention has a cost trap. A version under an active hold has no end date until something releases it, so the backup process must release holds reliably or the storage grows without limit. That is an inference from the mechanism.

Working out the period

This hypothetical calculation was written for this article.

Suppose weekly full backups and six daily incrementals, with a requirement that any restore point stays restorable for 35 days. The sixth incremental depends on a full backup written six days before it. If that incremental must survive 35 days, the full must be locked for at least 41 days.

Input Days
Required restore window 35
Longest dependency from a restore point back to its full 6
Minimum lock on the full 41

A flat 41-day default covers this chain. The window should also be longer than the time an intrusion could stay undetected, because a backup that expires before the intrusion is found gives no protection.

Deduplicating backup software needs separate treatment. A new backup may refer to an old stored block, so that block must stay locked for as long as any restore point uses it. Check how the software handles Object Lock in its own documentation, and do not assume a flat default suits it.

Build order

The steps below set up one vault bucket. Bucket names and account layout are placeholders.

1. Use a separate account

The editorial recommendation is a dedicated AWS account for the vault, outside the workload account, and not the Organizations management account. Service control policies do not affect users or roles in the management account, so step 5 would not protect a bucket held there.

2. Create the bucket and confirm the settings

Region and name are placeholders. The command combines the Object Lock option and the LocationConstraint required outside us-east-1, and was assembled for this article.

aws s3api create-bucket \
  --bucket example-backup-vault \
  --region ap-southeast-2 \
  --create-bucket-configuration LocationConstraint=ap-southeast-2 \
  --object-lock-enabled-for-bucket

aws s3api get-bucket-versioning --bucket example-backup-vault
aws s3api get-object-lock-configuration --bucket example-backup-vault

Expect "Status": "Enabled" from the first check and "ObjectLockEnabled": "Enabled" from the second. The AWS command example shows only the Object Lock flag, while the console steps turn versioning on separately, so check the result and do not assume it. Object Lock can also be enabled on an existing bucket. After enabling versioning for the first time, AWS recommends waiting 15 minutes before writing.

Warning: Once enabled, Object Lock cannot be disabled and versioning cannot be suspended on that bucket. Do this on a new bucket until the design is proven.

3. Rehearse in governance mode

AWS notes that governance mode can be used to test retention settings before a compliance period is applied. Run the whole backup job against a throwaway bucket with a short governance default, one day in this example, and confirm that the software writes, reads and expires correctly.

aws s3api put-object-lock-configuration \
  --bucket example-backup-rehearsal \
  --object-lock-configuration '{ "ObjectLockEnabled": "Enabled", "Rule": { "DefaultRetention": { "Mode": "GOVERNANCE", "Days": 1 }}}'

4. Review the compliance default and a ceiling

Warning: A version protected in compliance mode is undeletable until its retain-until date passes, and you pay to store it for that whole time. A mistaken value cannot be shortened on versions already written.

For the hypothetical chain above, the proposed compliance default is 41 days. Document that value, any explicit per-object overrides and the expected storage cost. A qualified owner must verify the actual chain and approve the irreversible choice after isolated testing; this article does not provide a production compliance-mode activation command.

A bucket policy can cap explicit retention with the s3:object-lock-remaining-retention-days condition. This is the AWS example with the bucket name and limit changed for this article.

{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "SetRetentionPeriod",
    "Effect": "Deny",
    "Principal": "*",
    "Action": ["s3:PutObjectRetention"],
    "Resource": "arn:aws:s3:::example-backup-vault/*",
    "Condition": {
      "NumericGreaterThan": { "s3:object-lock-remaining-retention-days": "400" }
    }
  }]
}

The cap limits how far a PutObjectRetention call can push a date out, whether by mistake or by an attacker. The example names only that action, so do not read it as a limit on other write paths. If the bucket is a replication destination, AWS notes that the policy must allow s3:ReplicateObject.

The event-hold keys do not apply to values from a bucket default. To restrict defaults, AWS says to deny s3:PutBucketObjectLockConfiguration. Under an active event hold, S3 recalculates the date on the service side, so it can move past a limit enforced at request time.

5. Guard the settings around the lock

The default can be removed by anyone allowed to write the configuration without a rule. New versions would then have no lock, while existing ones keep theirs. A service control policy (SCP) in AWS Organizations can close this gap, because it applies to users and roles in member accounts, including the member account's root user.

This policy was written for this article from the S3 action names and SCP syntax.

{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "FreezeBackupVaultSettings",
    "Effect": "Deny",
    "Action": [
      "s3:BypassGovernanceRetention",
      "s3:PutBucketObjectLockConfiguration",
      "s3:PutLifecycleConfiguration",
      "s3:PutBucketVersioning",
      "s3:PutBucketPolicy",
      "s3:DeleteBucketPolicy",
      "s3:PutReplicationConfiguration"
    ],
    "Resource": [
      "arn:aws:s3:::example-backup-vault",
      "arn:aws:s3:::example-backup-vault/*"
    ]
  }]
}

Treat this policy as an unvalidated design example requiring a security review and isolated test. Applying it after configuration blocks those changes for every member-account identity. A later change means someone in the management account must adjust the policy, which is the separation of duties this design wants. An SCP does not restrict service-linked roles, and it limits only identities in the accounts it covers, so give outside accounts no rights to change the vault.

6. Prove the lock on a real backup object

Check an object written by the backup job, not a hand-made test file.

aws s3api get-object-retention \
  --bucket example-backup-vault \
  --key path/to/backup-file

The output shows Mode and RetainUntilDate. Confirm the mode is COMPLIANCE and the date meets the intended protection period. With no explicit override or event hold, the bucket default adds its duration to the version creation time; otherwise validate the effective per-version setting. For the whole bucket, S3 Inventory can report retain-until date, mode and legal hold status. Inventory can take up to 48 hours to generate, so for a current value query the version directly.

7. Test the delete paths

Both delete forms are in the delete-object reference.

Warning: Use a test object in the vault that you are content to pay for until its date. Never run a version-specific delete against production backups, because if the lock is missing or expired the version is gone.

The rehearsal must distinguish a simple delete, which creates a marker, from a version-specific delete, which should be refused while the version is protected. Keep the exact test version and its retention evidence in the rehearsal record. Use disposable test data under an approved test procedure; no delete command is supplied here.

The first should return success and create a marker. The second should fail with Access Denied. Any other result means the lock is not working as designed.

Recovering from a mass delete

After a marker flood, the versions are still present. To undelete an object, delete its marker by giving the marker's version ID. A simple delete on a key whose current version is a marker removes nothing and only adds another marker, so a retry loop without version IDs makes things worse.

This query was written for this article, on the list-object-versions output. It lists markers that are currently hiding data under a prefix.

aws s3api list-object-versions \
  --bucket example-backup-vault \
  --prefix path/to/backups/ \
  --query 'DeleteMarkers[?IsLatest==`true`].[Key,VersionId]' \
  --output text

Review that list before removing anything, and rotate the compromised credentials first so that the markers do not return.

Lifecycle and replication

Lifecycle rules keep working on protected objects, including placing delete markers, but a lifecycle rule cannot delete a locked version. Once the date passes, NoncurrentVersionExpiration permanently removes noncurrent versions, and those cannot be recovered. The lock also survives transitions between storage classes. A version whose lock has passed stays and is billed until a rule or a person removes it.

Replication adds a second copy with useful default behaviour for this threat.

What to choose and why

These are editorial recommendations.

Use compliance mode for the vault copy when the requirement is that nobody can delete it. Governance mode suits rehearsal and data where a named administrator must be able to remove objects early.

Keep the retention period as short as the backup chain and the detection window allow. Compliance mistakes are paid for in full.

Hold s3:PutObjectLegalHold tightly, and treat a legal hold as an addition to retention.

Prefer two accounts with replication where the budget allows, and leave delete marker replication off on the vault rule. Alert on s3:ObjectRetention:Put event notifications and on CloudTrail changes to the vault's configuration.

Risks and limits

Account closure. AWS names account deletion as the way compliance objects can be removed early. A closed account can be reopened within 90 days, after which it is permanently closed. Protect the vault account's root user accordingly.

Encryption keys. Object Lock does not protect against losing or deleting encryption keys. If a KMS key is deleted, the objects may become unreadable. KMS requires a waiting period of 7 to 30 days and logs the scheduling to CloudTrail, so alert on that event and keep the key under the same control as the vault.

Read access. Object Lock addresses deletion and overwriting. The inference is that it does not guarantee someone can read the data, since policies and keys can still block access. Test restores.

Cost. Each version is a whole object and is charged as one. A delete marker carries a small storage charge based on its key name.

Scale. Objects with millions of versions can cause 503 responses as S3 throttles the bucket. A backup design that rewrites one fixed key many times should be checked against this.

Scope. Object Lock operations such as PutObjectLockConfiguration are not supported on directory buckets. A locked bucket cannot be a destination for server access logs.

Regulation. AWS reports that Object Lock has been assessed by Cohasset Associates for SEC 17a-4, CFTC and FINRA environments. That assessment does not establish that a particular configuration meets any obligation in Australia, so take advice for your own retention duties.

Sources

Opened 2026-11-10. Sources behind the corrections reopened 10 November 2026.