Amazon S3 object lock (legal hold) in Python with IAM policy example

S3 object lock is a feature to prevent permanent deletion on S3 objects, by accident and deliberate. This article focus on legal hold. For setting the retention period in governance mode, please refer to another article.

Once the legal hold has been turned ON on an S3 object, it cannot be deleted. AWS root user can turn OFF legal hold on an object. For IAM users, we can configure IAM policy to prevent the legal hold from being turned OFF.

Use case: We can allow a machine (IAM user) to put objects to S3, but not allowing it to delete the objects permanently. In case the machine has been controlled by an attacker, the attacker will not able to delete the data on S3.

This article will cover

  • Create S3 bucket in AWS console with object lock enabled
  • IAM policy to prevent a user from disabling legal hold
  • Examples in Python – write an object to S3 and enable legal hold
  • Test – try to disable object lock and delete the object with AWS CLI

Create a new bucket in AWS S3 console

When creating an S3 bucket, tick the following options

  • Versioning – “Keep all versions of an object in the same bucket.”
  • Object lock – “Permanently allow objects in this bucket to be locked.”

Otherwise, it’s not possible to create an object with object lock (retention or legal hold). The Python script below will raise exception in put_object_legal_hold()

IAM policies

This IAM policy allows a user to put legal hold “ON”, but not turning it off. AWS root user can turn legal hold off permanently and delete objects.

Note: I’m not trying to block object deletion in IAM. Because without object hold, an attacker could overwrite an object by putting a zero-byte object with the same key (object name).

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Deny",
            "Action": "s3:PutObjectLegalHold",
            "Resource": "*",
            "Condition": {
                "StringNotEquals": {
                    "s3:object-lock-legal-hold": "ON"
                }
            }
        }
    ]
}

Allow bucket listing

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "s3:ListAllMyBuckets",
            "Resource": "*"
        }
    ]
}

Allow access to buckets

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "s3:*",
            "Resource": [
                "arn:aws:s3:::obj-lock-test-bucket",
                "arn:aws:s3:::obj-lock-test-bucket/*"
            ]
        }
    ]
}

Write an object and put legal hold in Python

#!/usr/bin/python3

import logging
from datetime import datetime
from datetime import timedelta

import boto3
from botocore.exceptions import ClientError


def upload_file(file_name, bucket, object_name):
    s3_client = boto3.client('s3')
    try:
        s3_client.upload_file(
                Filename=file_name,
                Bucket=bucket,
                Key=object_name,
        )

        s3_client.put_object_legal_hold(
                Bucket=bucket,
                Key=object_name,
                LegalHold={
                    'Status':'ON',
                },
        )
    except ClientError as e:
        logging.error(e)
        return False
    return True

upload_file("local-filename", "obj-lock-test-bucket", "object-key")

Test it

We can get version ID with AWS CLI

aws s3api list-object-versions \
    --bucket obj-lock-test-bucket

And try to delete it – it should fail

aws s3api delete-object \
    --bucket obj-lock-test-bucket \
    --key object-key \
    --version-id JDRmmQxdmwC3NTfwvPsXOQ0dH0H.0hTS

it’s possible to place delete marker, but the version history cannot be deleted.

aws s3 rm s3://obj-lock-test-bucket/object-key
aws s3 ls s3://obj-lock-test-bucket/
aws s3api list-object-versions \
    --bucket obj-lock-test-bucket

Leave a Reply

Your email address will not be published. Required fields are marked *