DEV Community

Cover image for Object Lock Isn't Absolute: What WORM Really Blocks on S3 and Self-Hosted RustFS
Ethan Carter
Ethan Carter

Posted on

Object Lock Isn't Absolute: What WORM Really Blocks on S3 and Self-Hosted RustFS

Data preservation is keeping data exact on purpose

Data preservation is the practice of storing objects so they stay exact, recoverable, and unchangeable on purpose. Backups answer "can I get the data back?" Preservation answers a harder question: "can anyone, including me, alter or delete this before a date I chose?" Three primitives do the work. Immutability (WORM) stops overwrite and delete. Versioning keeps every revision so an edit is never a destruction. Retention enforces a minimum keep time. On AWS these come from S3 Object Lock and bucket versioning. On self-hosted S3-compatible storage like RustFS, the same S3 API now exposes all three. This article walks the concepts, the AWS commands, and a real lock-and-try-to-delete run on RustFS 1.0.3.

Capability RustFS (self-hosted, Apache 2.0) AWS S3 (managed)
Object Lock (WORM) ✅ Available ✅ Available
Bucket versioning ✅ Available ✅ Available (required for Object Lock)
Governance + Compliance modes ✅ Available (S3 API) ✅ Both
Legal hold (indefinite) ✅ Available (S3 API) ✅ Available
Default bucket retention ✅ Available (S3 API) ✅ Available
Lifecycle / ILM tie-in ✅ Available ✅ Available
Compliance assessment — Cohasset: SEC 17a-4, CFTC, FINRA

All four S3 API lock behaviors in the RustFS column (governance mode, compliance mode, legal hold, default bucket retention) were also exercised hands-on in this article on RustFS 1.0.3 — see the hands-on section below.

Why immutability beats a backup against ransomware

A backup protects against loss. It does not protect against a credential that deletes the backup too. WORM protection closes that gap: once an object version is locked, deletes and overwrites that target that version return an error until the retention period ends — a plain delete without a version id still adds a delete marker on top of the history. Ransomware operators and a careless script both hit the same wall. The second reason is regulatory. Rules like SEC 17a-4, FINRA, and CFTC expect records to be non-rewriteable and non-erasable for a set window, and they expect the storage to enforce it rather than trust a policy document. A snapshot you can delete by accident is not the same control. Immutability is the primitive that turns "we try not to delete it" into "the system refuses to delete it."

WORM and Object Lock: what governance and compliance modes actually do

Object Lock gives an object version a lock that blocks overwrite and deletion. You pick one of two modes. Governance mode blocks deletes for most users but can be overridden by anyone holding s3:BypassGovernanceRetention who also sends the x-amz-bypass-governance-retention: true header. That makes it good for testing retention rules before you commit to harder enforcement. Compliance mode is stricter: no user, including the account root, can shorten the period or delete the object until it expires. The only escape AWS documents is deleting the entire account. On top of modes, a legal hold gives the same protection with no expiration date; it stays until someone with s3:PutObjectLegalHold removes it. Legal holds and retention periods are independent, so an object can be protected by both at once.

Versioning: the safety net Object Lock depends on

Object Lock only works on buckets that have versioning enabled, because the lock is stored against a specific object version. Versioning is also a preservation tool on its own. Without it, an overwrite replaces bytes in place; with it, every PUT creates a new version and the old one survives. A delete becomes a delete marker on top of the history rather than a wipe. That matters for ransomware recovery too: if an attacker encrypts a file, the prior clean version is still there to restore. The cost is storage growth and list noise, which is exactly what lifecycle rules exist to manage. The takeaway is simple: turn versioning on first, then layer Object Lock on top. The two are not alternatives; Object Lock is enforced per version, and there is no version without versioning.

Retention periods vs legal holds: fixed dates or indefinite?

A fixed retention period sets a retain-until-date. You can specify it directly on an object, or set a bucket default (a mode plus a number of days or years) so every new object is protected automatically. When you PUT an object with its own explicit retention, that explicit setting overrides the bucket default. A legal hold is different: it has no clock. It protects an object version until an authorized user removes it, and it keeps protecting the object even after a retention period expires. The practical split is operational vs legal. Use fixed retention for "keep audits 7 years." Use legal hold for "do not touch this until counsel says so." Both can sit on the same object version, and removing one does not remove the other.

AWS also documents a third shape, variable retention: instead of picking a date up front, you turn on an event hold with an event hold duration, and the object stays protected while the hold is on. When the hold is released, Amazon S3 fixes the retain-until-date at the release time plus the duration. This fits cases where you cannot know the keep-time at write time — for example, a configurable ransomware recovery window. A bucket default can turn on an event hold too, so every new object starts with variable retention instead of a fixed date.

How AWS S3 handles immutability

On AWS the primitives are managed, so the work is configuration rather than operations. Object Lock works only on buckets with S3 Versioning enabled, and you can enable it two ways: at bucket creation, or on an existing bucket (via the console, AWS CLI, SDKs, or the REST API). Note that once Object Lock is enabled on a bucket, you cannot disable it or suspend versioning on that bucket:

# Enable Object Lock at bucket creation (implies versioning requirement)
aws s3api create-bucket --bucket amzn-s3-demo-bucket1 --object-lock-enabled-for-bucket

# Set a 50-day COMPLIANCE default retention on the bucket
aws s3api put-object-lock-configuration --bucket amzn-s3-demo-bucket1 \
  --object-lock-configuration='{ "ObjectLockEnabled": "Enabled", "Rule": { "DefaultRetention": { "Mode": "COMPLIANCE", "Days": 50 }}}'

# Place or remove a legal hold on an object
aws s3api put-object-legal-hold --bucket amzn-s3-demo-bucket1 \
  --key my-image.fs --legal-hold="Status=ON"
Enter fullscreen mode Exit fullscreen mode

AWS states its Object Lock has been assessed by Cohasset Associates for SEC 17a-4, CFTC, and FINRA use. The durability and availability numbers AWS publishes are AWS's own claims; they do not transfer to a self-hosted deployment.

Running WORM on self-hosted RustFS

RustFS is Apache 2.0 and speaks the S3 API, so the same lock concepts apply to a server you run yourself. The quick start maps the API ports and writes data and logs to local volumes:

# Create data and logs directories; the container runs as a non-root
# user (rustfs, UID/GID 10001:10001), so the mounts must be writable by it
mkdir -p data logs
chown -R 10001:10001 data logs

docker run -d -p 9000:9000 -p 9001:9001 \
  -v $(pwd)/data:/data -v $(pwd)/logs:/logs \
  rustfs/rustfs:latest
Enter fullscreen mode Exit fullscreen mode

Default credentials are rustfsadmin / rustfsadmin — change them before exposing the server beyond localhost. Point a MinIO Client (mc) or the AWS SDK at it, then configure locking the same way you would against any S3 endpoint. The next section is a real run on the Windows build of RustFS 1.0.3 with the AWS SDK (boto3), showing what happens when you try to delete a locked object.

Hands-on: lock a bucket and try to delete a protected object

This is a real session against RustFS 1.0.3, started from the native Windows binary (rustfs.exe server <dir> --access-key rustfsadmin --secret-key rustfsadmin). The client is the AWS SDK for Python (boto3) pointed at the local endpoint; the equivalent mc calls are noted inline.

# 1. Create a bucket with Object Lock enabled (mc: mc mb --with-lock rustfs/preserve-demo)
create_bucket(ObjectLockEnabled=True)                         -> 200 OK

# 2. Set a 30-day GOVERNANCE default retention (mc: mc retention set --default GOVERNANCE "30d" rustfs/preserve-demo)
put_object_lock_configuration(Mode=GOVERNANCE, Days=30)      -> 200 OK
get_object_lock_configuration()
  -> {'ObjectLockEnabled': 'Enabled',
      'Rule': {'DefaultRetention': {'Mode': 'GOVERNANCE', 'Days': 30}}}

# 3. Upload a file; it inherits the 30-day default
put_object(KEY)                                               -> VersionId v1
get_object_retention(KEY)
  -> {'Mode': 'GOVERNANCE', 'RetainUntilDate': '2026-11-18T04:00:50+00:00'}   # 30 days out

# 4. A plain delete adds a delete marker and SUCCEEDS (S3 semantics)
#    the locked version is untouched (mc: mc rm rustfs/preserve-demo/file)
delete_object(KEY)                                            -> 200 OK

# 5. Deleting the LOCKED VERSION is what WORM blocks (mc: mc rm --version-id v1 ...)
delete_object(KEY, VersionId=v1)
  -> AccessDenied: Object is under GOVERNANCE retention and cannot be deleted
     until 2026-11-18 04:00:50 +00:00

# 6. A legal hold (indefinite) hardens the same version
put_object_legal_hold(KEY, VersionId=v1, Status='ON')         -> 200 OK
delete_object(KEY, VersionId=v1)
  -> AccessDenied: Object has a legal hold and cannot be deleted. Remove the legal hold first.
Enter fullscreen mode Exit fullscreen mode

The refusal in step 5 is the control working: the object version stays put until 2026-11-18. Step 4 is the subtlety worth internalizing. A delete without a version id only drops a delete marker on top of the history; the protected version survives, which is exactly why Object Lock requires versioning. Repeating step 5 against a COMPLIANCE default returns the same refusal, and a governance-bypass header does not lift it — the behavior compliance-mode users depend on.

Lifecycle management: where retention meets automation

Retention keeps objects; lifecycle (ILM) cleans them up once retention passes. On RustFS, Lifecycle Management is a ✅ Available feature, and ILM Tiering can move objects to a remote S3 target. On AWS, lifecycle rules transition objects between storage classes and expire them. On AWS, the two features are designed to stack rather than conflict: lifecycle configurations continue to function normally on protected objects, including placing delete markers — but a locked version of an object cannot be deleted by an S3 Lifecycle expiration policy, and Object Lock is maintained regardless of which storage class the object resides in and throughout S3 Lifecycle transitions between storage classes. In other words, the lock does not block transitions, and expiration cannot permanently delete the protected version. (AWS-side behavior, per the Object Lock considerations doc in Sources; not exercised in this article's hands-on run.) One gap to name plainly: RustFS does not ship AWS-style local cold tiers (Glacier Instant/Flexible/Deep Archive). Its ILM tiers to a remote S3 bucket you operate, so "cold storage" is another S3 endpoint, not a magic cheaper shelf inside the same cluster.

The self-hosting trade-off you can't skip

Running this yourself removes the per-gigabyte bill and the vendor lock-in, but it moves responsibility onto you. The lock metadata and the object catalog live inside RustFS, which means RustFS reliability is the catalog's reliability. There is no separate managed metadata service to fall back on, so backups, monitoring, and restore drills are your job, not a checkbox in a console. AWS can publish durability math; a self-hosted cluster ships no such promise, only the primitives to build one. The candid version: self-hosting trades a recurring bill and operational simplicity for control and cost, and it expects you to engineer durability instead of renting it. Own that before you migrate compliance data.

When should you self-host data preservation?

Pick self-hosted RustFS when you need WORM and retention but also need to keep data inside your perimeter, avoid per-GB egress, or escape a proprietary control plane, and you have the ops capacity to back up and monitor the cluster. Pick managed S3 when the compliance attestation, the managed durability, and zero operational load outweigh the bill, and you are comfortable with AWS's pricing model. Mixed setups are common: keep hot, frequently audited records on a self-hosted cluster and replicate to a managed target for offsite durability. The decision is less about features, which now overlap heavily, and more about who carries the durability burden and who controls the keys.

FAQ

What is the difference between Object Lock and bucket versioning?

Versioning keeps every revision of an object so overwrites and deletes are recoverable. Object Lock prevents overwrite and deletion of a specific version for a set time or until a legal hold is cleared. Object Lock requires versioning, but versioning alone does not stop a delete.

Can a compliance-mode lock be removed early?

No. AWS documents that in compliance mode no user, including the root user, can shorten the retention or delete the object until it expires; the only documented path is deleting the AWS account. On self-hosted S3-compatible systems the same strictness is the point of the mode.

Does a legal hold expire?

No. A legal hold has no associated time window. It protects an object version until an authorized user explicitly removes it, and it is independent of any retention period on the same object.

What is variable retention / an event hold?

A newer AWS retention shape where you defer the retain-until-date. You turn on an event hold with an event hold duration, and the object stays protected while the hold is on; when you release it, Amazon S3 fixes the retain-until-date at the release time plus the duration. It is meant for cases where the keep-time is unknown at write time, such as a configurable ransomware recovery window.

What happens if I try to expire a locked object with a lifecycle rule?

Lifecycle rules keep functioning normally on protected objects, including placing delete markers. What the lock prevents is permanent deletion: a locked version cannot be deleted by a lifecycle expiration policy, and the protection persists through transitions between storage classes. Once the retention period ends and any legal hold is removed, the locked version can be expired like any other.

Is RustFS Object Lock production-ready?

RustFS lists Object Lock (WORM), bucket versioning, and lifecycle management as ✅ Available in its Feature & Status table as of this writing. The hands-on run in this article exercised the lock, default retention, and delete-blocking behavior on RustFS 1.0.3. Validate against your own workload and retention requirements before relying on it for regulated records.

A note on how this was verified

Article type: this article contains hands-on verification (Object Lock and versioning exercised on the RustFS 1.0.3 native Windows binary with the AWS SDK, boto3) plus documentation interpretation of AWS S3 Object Lock. It is not a production-scale deployment report.

What was actually executed: starting RustFS 1.0.3 from the native Windows binary, creating buckets with Object Lock enabled, setting 30-day GOVERNANCE and COMPLIANCE default retention, reading the configuration back, uploading objects, and confirming the inherited retention (retain-until-date 30 days out). Confirmed that a plain delete adds a delete marker and succeeds (S3 semantics), while deleting the specific locked version is refused in both GOVERNANCE and COMPLIANCE modes with the exact retain-until-date, that a governance-bypass header does not lift a COMPLIANCE lock, and that a legal hold blocks deletion until removed.

What was not verified: no production-scale or multi-node deployment, no concurrency or throughput testing, no failure-injection or restore drill, and no compliance attestation was performed. Treat the hands-on as a functional smoke test, not a certification.

Verify against your own environment and keep a rollback plan before pointing real compliance data at any storage system, managed or self-hosted.

Sources

  • RustFS README, Feature & Status table (Object Lock WORM, Versioning, Lifecycle Management marked Available; S3 Tables and MinIO On-Disk Compatibility marked Preview) — https://github.com/rustfs/rustfs (checked 2026-10-19)
  • RustFS Quickstart (docker run, default credentials rustfsadmin/rustfsadmin, API port 9000 / console 9001) — https://github.com/rustfs/rustfs (checked 2026-10-19)
  • Amazon S3 Object Lock overview: governance vs compliance modes, legal hold, versioning requirement, Cohasset assessment for SEC 17a-4 / CFTC / FINRA — https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lock.html (checked 2026-10-19)
  • Configuring S3 Object Lock with the AWS CLI: create-bucket --object-lock-enabled-for-bucket, put-object-lock-configuration with COMPLIANCE Days 50, put-object-legal-hold Status=ON; also "Enable Object Lock on an existing S3 bucket" (versioned buckets; cannot be disabled afterwards) and variable retention / event hold sections — https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lock-configure.html (checked 2026-10-19; existing-bucket and variable-retention sections checked 2026-10-27)
  • Amazon S3 Object Lock considerations: "Object lifecycle management configurations continue to function normally on protected objects, including placing delete markers. However, a locked version of an object cannot be deleted by a S3 Lifecycle expiration policy. Object Lock is maintained regardless of which storage class the object resides in and throughout S3 Lifecycle transitions between storage classes." — https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lock-managing.html (checked 2026-10-27)
  • MinIO Client reference, mc retention set --default MODE VALIDITY (e.g. mc retention set --default GOVERNANCE "30d" alias/bucket); the note "Beginning with RELEASE.2025-05-20T20-30-00Z, MinIO AIStor allows setting object locking and retention rules on any existing bucket" appears on both the mc retention set page and the mc mb page (--with-lock) — https://min.io/docs/minio/linux/reference/minio-mc/mc-retention-set.html and https://min.io/docs/minio/linux/reference/minio-mc/mc-mb.html (checked 2026-10-27)

Top comments (0)