Making Public S3 Objects Private

Length: 00:10:24

Lesson Summary:

In this lesson, we'll learn how to detect unintended public access permissions in the ACL of an S3 object and how to revoke them automatically using Lambda, Boto3, and CloudWatch events.

Create an S3 Bucket

  1. Create the S3 bucket:

     aws s3 mb s3://123456789012-everything-must-be-private
     aws s3 mb s3://123456789012-bucket-for-my-object-level-s3-trail
  2. Apply a bucket policy:

     aws s3api put-bucket-policy \
     --bucket 123456789012-bucket-for-my-object-level-s3-trail \
     --policy file://bucket_policy.json

Create a CloudTrail Trail and Start Logging

  1. Run the following command:

     aws cloudtrail create-trail \
     --name my-object-level-s3-trail \
     --s3-bucket-name 123456789012-bucket-for-my-object-level-s3-trail
     aws cloudtrail start-logging --name my-object-level-s3-trail
  2. Create the event selectors:

     aws cloudtrail put-event-selectors \
     --trail-name my-object-level-s3-trail \
     --event-selectors file://event_selectors.json

Create an IAM Execution Role for Lambda

  1. Create the IAM role:

     aws iam create-role \
     --role-name AllowLogsAndS3ACL \
     --assume-role-policy-document file://trust_policy.json
  2. Define the access policy:

     aws iam put-role-policy \
     --role-name AllowLogsAndS3ACL \
     --policy-name AllowLogsAndS3ACL \
     --policy-document file://access_policy.json

Create a Lambda Function

For a PutObjectAcl API event, the function gets the bucket and key name from the event. If the object is not private, then it makes the object private by making a PutObjectAcl call.

  1. Zip the Lambda function:

     zip -r9
  2. Create the Lambda function:

     aws lambda create-function \
     --function-name RemediateObjectACL \
     --zip-file fileb:// \
     --role arn:aws:iam::123456789012:role/AllowLogsAndS3ACL \
     --handler lambda_function.lambda_handler \
     --runtime python3.7 \
     --environment Variables={BUCKET_NAME=123456789012-everything-must-be-private}
  3. Allow CloudWatch events to invoke Lambda:

     aws lambda add-permission \
     --function-name RemediateObjectACL \
     --statement-id AllowCloudWatchEventsToInvoke \
     --action 'lambda:InvokeFunction' \
     --principal \
     --source-arn arn:aws:events:us-east-2:123456789012:rule/S3ObjectACLAutoRemediate

Create a CloudWatch Events Rule

  1. Create the rule:

     aws events put-rule \
     --name S3ObjectACLAutoRemediate \
     --event-pattern file://event_pattern.json
  2. Set the Lambda function as the target:

     aws events put-targets \
     --rule S3ObjectACLAutoRemediate \
     --targets Id=1,Arn=arn:aws:lambda:us-east-2:123456789012:function:RemediateObjectACL


  1. Run the following command:

     aws s3api put-object \
     --bucket 123456789012-everything-must-be-private \
     --key MyPersonalInfo
     aws s3api get-object-acl \
     --bucket 123456789012-everything-must-be-private \
     --key MyPersonalInfo

    The above should return 1 grantee, the owner (you). This indicates that the object is private.

  2. Add public read access, violating our policy:

     aws s3api put-object-acl \
     --bucket 123456789012-everything-must-be-private \
     --key MyPersonalInfo \
     --acl public-read
  3. Quickly check access again:

     aws s3api get-object-acl \
     --bucket 123456789012-everything-must-be-private \
     --key MyPersonalInfo
  4. You will see another grantee, allowing everyone to read the object:

       "Grantee": {
         "Type": "Group",
         "URI": ""
       "Permission": "READ"
  5. Describe the ACL again, and you'll see the Lambda function has removed public read access. Verify this in CloudWatch Logs.


aws events remove-targets --rule S3ObjectACLAutoRemediate --ids "1"
aws events delete-rule --name S3ObjectACLAutoRemediate
aws lambda delete-function --function-name RemediateObjectACL
aws iam delete-role-policy --role-name AllowLogsAndS3ACL --policy-name AllowLogsAndS3ACL
aws iam delete-role --role-name AllowLogsAndS3ACL
aws cloudtrail delete-trail --name my-object-level-s3-trail
aws s3 rb s3://123456789012-bucket-for-my-object-level-s3-trail --force
aws s3 rb s3://123456789012-everything-must-be-private --force

This lesson is only available to Linux Academy members.

Sign Up To View This Lesson

Or Log In

Looking For Team Training?

Learn More