# Object Lock

Object Lock makes object versions immutable - write once, read many, often called WORM. A locked
version stays exactly as it was written: nobody can delete or overwrite it until its lock ends.
It exists for the data that has to survive no matter what happens to the keys and scripts around
it. Object Lock is switched on per bucket, and from then on versions are locked either
automatically, through a default retention, or one by one through the S3 API.

## When you'd use it

These are the situations Object Lock is made for:

| | |
| --- | --- |
| Ransomware protection | A stolen access key cannot delete or encrypt away locked backups: every locked version stays readable until its lock ends |
| Protection from mistakes | A script or a person deleting the wrong prefix just writes delete markers over locked versions; the data stays where it was |
| Retention obligations | Records a regulation says must be kept for a fixed period stay in place until that period is over |

**Danger:**

- **Enabling Object Lock is permanent.** Once it is enabled on a bucket, it stays enabled for as
  long as the bucket exists. There is no way to turn it off - in the dashboard or through the S3
  API.
- **Locked object versions cannot be deleted until their retention expires.** In compliance mode
  nobody can unlock one early - not you, not any access key, not STRG support. A retention
  period set too long cannot be corrected; the only way out is to wait.
- **Locked data is billed until the lock expires, even when you no longer need it.** With a default
  retention, every object uploaded is billed for at least the retention period - one deleted the
  day after its upload included. Lifecycle rules cannot remove a locked version either; one that
  comes due while its lock is still running is skipped and removed once the lock has expired.
- **A default retention applies to new objects only.** Objects already in the bucket are not
  affected, and shortening or removing the default releases nothing that is already locked.

## How it works

Every bucket keeps all versions of every object. A delete without a version ID writes a delete
marker, and an upload under an existing key writes a new version - neither touches a locked
version, which stays, and stays billed. Removing data means deleting one specific version, and
that is exactly what a lock prevents.

A version is protected while any one of these applies:

| Protection | Ends | Who can remove it early |
| --- | --- | --- |
| **Governance mode** retention | At its retain-until date | An access key with the *Bypass governance retention* permission, sending `x-amz-bypass-governance-retention: true` - nobody else. Extending it needs no bypass |
| **Compliance mode** retention | At its retain-until date | Nobody. The date can be extended, never shortened |
| **Legal hold** | When the hold is lifted | Any access key with the *Set legal holds* permission. It has no end date and is independent of the two modes |

Governance mode suits the cases where a mistake should still be fixable by whoever holds a
specially trusted key; compliance mode is for the records that must outlast everyone's good
intentions. A legal hold is the tool for an investigation or a dispute, when the end date is not
known yet.

Retention and legal holds are set **per object version**. A bucket's **default retention** is the
retention every object uploaded or copied into the bucket inherits from then on:

| Setting | Values |
| --- | --- |
| Mode | Governance or compliance |
| Period | 1 to 3,000 days, or 1 to 8 years - one or the other, not both |

A legal hold is never a bucket default: it belongs to individual objects.

Retention and legal holds need a bucket with Object Lock enabled.

## Pairing Object Lock with lifecycle rules

Object Lock protects versions, and [lifecycle rules](https://docs.strg.com/lifecycle-policies) keep what is left
over from piling up. We recommend a bucket with Object Lock always has two of them, whatever the
retention period:

| Rule | Suggested period | Why |
| --- | --- | --- |
| *Permanently delete previous versions after* | 14 days - or 1 day behind a backup tool such as Kopia or restic that prunes its own data | Overwritten and deleted versions otherwise stay stored and billed for good, once their lock has ended |
| *Abort incomplete multipart uploads after* | 7 days | A large upload that never finished leaves parts behind that are stored and billed until something clears them away |

The two clocks are independent, which is what makes the pairing safe:

- **A lifecycle rule never removes a locked version, whatever its number of days.** A version still
  under its lock is skipped and removed once the lock has expired. The previous-versions period is
  therefore no protection for the data and no risk to the lock, and it is best not set equal to the
  retention period - cost and recovery needs are what decide it.
- **The two start at different moments.** The lock runs from the upload; the lifecycle clock runs
  from when the version stopped being current. A backup tool that deletes its data only long after
  writing it hands the rule versions whose lock has already lapsed, which is why a short period
  such as 1 day fits there.
- **Locked versions stay billed until their lock expires**, with or without a rule.

For ransomware protection through a backup tool, the tool's own snapshot retention should be
longer than the lock, so that the tool only asks for deletes once the lock has expired.

## Enabling Object Lock in the dashboard

The **Object Lock** card on a bucket's page in the dashboard at `https://app.strg.com` is where
it is switched on and where its default retention lives. On a narrow screen, the card is on the
bucket's **Settings** tab.

1. The card's **Enable** button opens the dialog - or **Edit**, once Object Lock is on.
2. Under **Default retention**, the dialog offers three choices:

   | Choice | What the dialog says |
   | --- | --- |
   | **No default retention** | "Nothing is locked automatically. Objects can still be locked one by one through the S3 API." |
   | **Governance mode** | "Every new object is locked. Only an access key allowed to bypass governance can delete it early or shorten its lock." |
   | **Compliance mode** | "Every new object is locked, and nobody can delete it early or shorten its lock - not you, and not STRG." |

3. With a mode chosen, **Lock each new object for** takes a number of **Days** or **Years**.
4. **Enable Object Lock** (or **Save default retention**) saves the choice.

Because a save that locks anything cannot be taken back - enabling Object Lock at all, or setting
a default retention - the dialog first asks for a tick beside:

> I understand that Object Lock cannot be turned off, and that locked objects cannot be deleted and
> keep being billed until their lock expires.

(When Object Lock is already on, the first clause is left out.) **Compliance mode** also asks for
the bucket's name to be typed, under "Type `my-bucket` to confirm compliance mode". Removing a
default retention needs neither: it only stops locking new objects.

Once enabled, the card shows a **Permanent** tag, and a **Compliance** tag while the default
retention is in compliance mode.

In the object browser, an object's details show its retention and legal hold. While a version is
under retention or a legal hold, the dashboard's permanent delete for it is disabled. The dashboard
never bypasses governance retention: a folder deletion skips locked versions and reports how many
it could not delete.

## Enabling Object Lock through the S3 API

[PutObjectLockConfiguration](https://docs.strg.com/s3-api/PutObjectLockConfiguration) enables Object Lock on an
existing bucket and sets, changes or removes its default retention. It needs the *Manage Object
Lock* permission and *Read Object Lock settings* beside it, because the bucket's current
configuration is read before every change.

This example enables Object Lock with a 30-day governance-mode default retention:

**AWS CLI**

```sh
aws s3api put-object-lock-configuration \
  --bucket my-bucket \
  --object-lock-configuration '{"ObjectLockEnabled":"Enabled","Rule":{"DefaultRetention":{"Mode":"GOVERNANCE","Days":30}}}' \
  --endpoint-url https://s3.eu-central-1.strg.com \
  --region eu-central-1
```

**Python (boto3)**

```python
import boto3

s3 = boto3.client(
    "s3",
    endpoint_url="https://s3.eu-central-1.strg.com",
    region_name="eu-central-1",
)

s3.put_object_lock_configuration(
    Bucket="my-bucket",
    ObjectLockConfiguration={
        "ObjectLockEnabled": "Enabled",
        "Rule": {"DefaultRetention": {"Mode": "GOVERNANCE", "Days": 30}},
    },
)
```

**JavaScript (AWS SDK v3)**

```js
import { PutObjectLockConfigurationCommand, S3Client } from "@aws-sdk/client-s3";

const s3 = new S3Client({
  endpoint: "https://s3.eu-central-1.strg.com",
  region: "eu-central-1",
});

await s3.send(
  new PutObjectLockConfigurationCommand({
    Bucket: "my-bucket",
    ObjectLockConfiguration: {
      ObjectLockEnabled: "Enabled",
      Rule: { DefaultRetention: { Mode: "GOVERNANCE", Days: 30 } },
    },
  }),
);
```

A configuration with `ObjectLockEnabled` set to `Enabled` and no `Rule` removes the default
retention. Object Lock stays enabled.

STRG checks each configuration before it is saved, so a mistake shows up straight away with
one of these answers:

| Request | Answer |
| --- | --- |
| A configuration without `ObjectLockEnabled` set to `Enabled` | `400 InvalidRequest` - Object Lock can be enabled, never disabled |
| A default retention over 3,000 days or 8 years | `400 InvalidArgument` |
| Both `Days` and `Years`, a period below 1, a mode other than `GOVERNANCE` or `COMPLIANCE`, a `Rule` without `ObjectLockEnabled` set to `Enabled`, or an element S3 does not define | `400 MalformedXML` |
| No `Content-MD5` or `x-amz-checksum-*` header | `400 InvalidRequest`. The AWS SDKs and CLI send a checksum by default |

[GetObjectLockConfiguration](https://docs.strg.com/s3-api/GetObjectLockConfiguration) reads the configuration, with
*Read Object Lock settings*. A bucket without Object Lock answers
`404 ObjectLockConfigurationNotFoundError`.

### Object Lock at bucket creation

Object Lock is switched on after a bucket exists rather than as part of creating it:
[CreateBucket](https://docs.strg.com/s3-api/CreateBucket) with `x-amz-bucket-object-lock-enabled: true` answers
`501 NotImplemented`, so a client knows straight away. Creating the bucket and then sending
[PutObjectLockConfiguration](https://docs.strg.com/s3-api/PutObjectLockConfiguration), as above, gives the same
result.

## Locking individual objects

Through the S3 API, a version can be locked as it is uploaded, or an existing version locked
later. Without a version ID, a request applies to the current version.

| To | Operation or header | Permission |
| --- | --- | --- |
| Upload a version with a retention | `x-amz-object-lock-mode` and `x-amz-object-lock-retain-until-date` on [PutObject](https://docs.strg.com/s3-api/PutObject), [CopyObject](https://docs.strg.com/s3-api/CopyObject) or [CreateMultipartUpload](https://docs.strg.com/s3-api/CreateMultipartUpload) | *Set retention*, beside *Write objects* |
| Upload a version under a legal hold | `x-amz-object-lock-legal-hold: ON` on the same operations | *Set legal holds*, beside *Write objects* |
| Set or extend a version's retention | [PutObjectRetention](https://docs.strg.com/s3-api/PutObjectRetention) | *Set retention* |
| Shorten or remove a governance-mode retention | [PutObjectRetention](https://docs.strg.com/s3-api/PutObjectRetention) with `x-amz-bypass-governance-retention: true` | *Set retention* and *Bypass governance retention* |
| Read a version's retention | [GetObjectRetention](https://docs.strg.com/s3-api/GetObjectRetention) | *Read retention* |
| Place or lift a legal hold | [PutObjectLegalHold](https://docs.strg.com/s3-api/PutObjectLegalHold), `ON` or `OFF` | *Set legal holds* |
| Read a version's legal hold | [GetObjectLegalHold](https://docs.strg.com/s3-api/GetObjectLegalHold) | *Read legal holds* |

Locking here is by retention date or legal hold. S3's event holds (`x-amz-object-lock-event-hold`
and related headers) answer `501 NotImplemented`, so a client knows straight away.

This example uploads an object locked in governance mode until 1 January 2027, places a legal hold
on it, and reads both back:

**AWS CLI**

```sh
aws s3api put-object \
  --bucket my-bucket \
  --key photos/2024/cat.jpg \
  --body cat.jpg \
  --object-lock-mode GOVERNANCE \
  --object-lock-retain-until-date 2027-01-01T00:00:00Z \
  --endpoint-url https://s3.eu-central-1.strg.com \
  --region eu-central-1

aws s3api put-object-legal-hold \
  --bucket my-bucket \
  --key photos/2024/cat.jpg \
  --legal-hold Status=ON \
  --endpoint-url https://s3.eu-central-1.strg.com \
  --region eu-central-1

aws s3api get-object-retention \
  --bucket my-bucket \
  --key photos/2024/cat.jpg \
  --endpoint-url https://s3.eu-central-1.strg.com \
  --region eu-central-1

aws s3api get-object-legal-hold \
  --bucket my-bucket \
  --key photos/2024/cat.jpg \
  --endpoint-url https://s3.eu-central-1.strg.com \
  --region eu-central-1
```

**Python (boto3)**

```python
from datetime import datetime, timezone

import boto3

s3 = boto3.client(
    "s3",
    endpoint_url="https://s3.eu-central-1.strg.com",
    region_name="eu-central-1",
)

with open("cat.jpg", "rb") as body:
    s3.put_object(
        Bucket="my-bucket",
        Key="photos/2024/cat.jpg",
        Body=body,
        ObjectLockMode="GOVERNANCE",
        ObjectLockRetainUntilDate=datetime(2027, 1, 1, tzinfo=timezone.utc),
    )

s3.put_object_legal_hold(
    Bucket="my-bucket",
    Key="photos/2024/cat.jpg",
    LegalHold={"Status": "ON"},
)

print(s3.get_object_retention(Bucket="my-bucket", Key="photos/2024/cat.jpg")["Retention"])
print(s3.get_object_legal_hold(Bucket="my-bucket", Key="photos/2024/cat.jpg")["LegalHold"])
```

**JavaScript (AWS SDK v3)**

```js
import { readFile } from "node:fs/promises";
import {
  GetObjectLegalHoldCommand,
  GetObjectRetentionCommand,
  PutObjectCommand,
  PutObjectLegalHoldCommand,
  S3Client,
} from "@aws-sdk/client-s3";

const s3 = new S3Client({
  endpoint: "https://s3.eu-central-1.strg.com",
  region: "eu-central-1",
});

const Bucket = "my-bucket";
const Key = "photos/2024/cat.jpg";

await s3.send(
  new PutObjectCommand({
    Bucket,
    Key,
    Body: await readFile("cat.jpg"),
    ObjectLockMode: "GOVERNANCE",
    ObjectLockRetainUntilDate: new Date("2027-01-01T00:00:00Z"),
  }),
);

await s3.send(new PutObjectLegalHoldCommand({ Bucket, Key, LegalHold: { Status: "ON" } }));

const { Retention } = await s3.send(new GetObjectRetentionCommand({ Bucket, Key }));
const { LegalHold } = await s3.send(new GetObjectLegalHoldCommand({ Bucket, Key }));
console.log(Retention, LegalHold);
```

## Bypassing governance retention

Governance mode leaves one door open, for the day a lock turns out to be a mistake: an access key
with the *Bypass governance retention* permission can permanently delete a version locked in
governance mode, or shorten or remove its retention, by sending
`x-amz-bypass-governance-retention: true` on:

| Operation | Also needs |
| --- | --- |
| [DeleteObject](https://docs.strg.com/s3-api/DeleteObject) with a `versionId` | *Delete object versions* |
| [DeleteObjects](https://docs.strg.com/s3-api/DeleteObjects) with version IDs | *Delete object versions* |
| [PutObjectRetention](https://docs.strg.com/s3-api/PutObjectRetention) | *Set retention* |

The permission comes with *Delete object versions* or *Set retention* beside it, since on its own
it would have nothing to bypass. A request carrying the header from a key without the permission
answers `403 AccessDenied`. For a key without the permission, governance mode is as immovable as
compliance mode. No key can bypass compliance mode or a legal hold, and the dashboard never
bypasses governance retention.

**AWS CLI**

```sh
aws s3api delete-object \
  --bucket my-bucket \
  --key photos/2024/cat.jpg \
  --version-id "$VERSION_ID" \
  --bypass-governance-retention \
  --endpoint-url https://s3.eu-central-1.strg.com \
  --region eu-central-1
```

**Python (boto3)**

```python
import os

import boto3

s3 = boto3.client(
    "s3",
    endpoint_url="https://s3.eu-central-1.strg.com",
    region_name="eu-central-1",
)

s3.delete_object(
    Bucket="my-bucket",
    Key="photos/2024/cat.jpg",
    VersionId=os.environ["VERSION_ID"],
    BypassGovernanceRetention=True,
)
```

**JavaScript (AWS SDK v3)**

```js
import { DeleteObjectCommand, S3Client } from "@aws-sdk/client-s3";

const s3 = new S3Client({
  endpoint: "https://s3.eu-central-1.strg.com",
  region: "eu-central-1",
});

await s3.send(
  new DeleteObjectCommand({
    Bucket: "my-bucket",
    Key: "photos/2024/cat.jpg",
    VersionId: process.env.VERSION_ID,
    BypassGovernanceRetention: true,
  }),
);
```

`VERSION_ID` holds the version to delete, as listed by
[ListObjectVersions](https://docs.strg.com/s3-api/ListObjectVersions).

## Object Lock and agents

Agents can work with Object Lock too, and a human stays in charge of every lock. Through the
[MCP server](https://docs.strg.com/getting-started-mcp), an agent whose key lacks the *Act without confirmation*
permission asks you to confirm each call that sets retention, a legal hold or a bucket's Object
Lock, and each one bypassing governance retention. The [Ask our AI Assistant](https://docs.strg.com/assistant)
in the dashboard can fill in the Object Lock dialog for you; the acknowledgement, the typed
bucket name and the save are always yours.

## Permissions

These permissions are granted on an access key on the **Access Keys** page -
[Access keys](https://docs.strg.com/access-keys) has the details.

| Permission | Allows | Needs beside it |
| --- | --- | --- |
| *Read Object Lock settings* | GetObjectLockConfiguration | - |
| *Manage Object Lock* | PutObjectLockConfiguration - enabling Object Lock and changing the default retention | *Read Object Lock settings* |
| *Read retention* | GetObjectRetention | - |
| *Set retention* | PutObjectRetention, and uploading a version with a retention | - |
| *Bypass governance retention* | `x-amz-bypass-governance-retention` on DeleteObject, DeleteObjects and PutObjectRetention | *Delete object versions* or *Set retention* |
| *Read legal holds* | GetObjectLegalHold | - |
| *Set legal holds* | PutObjectLegalHold, and uploading a version under a legal hold | - |

## Related

- [Lifecycle policies](https://docs.strg.com/lifecycle-policies) - they cannot remove a locked version, and pair with Object Lock as above
- [Access keys](https://docs.strg.com/access-keys)
- [S3 API overview](https://docs.strg.com/s3-api/overview)
