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