How to Use S3 Bucket Policies and Access Points to Enforce Least Privilege
Retrospective: this article looks back at events from July 2017, written in 2026 with the benefit of hindsight.
Bucket policies decide who can access data in Amazon S3. Written loosely, they leak data; written tightly, they make many attacks impossible. Here is how to write S3 bucket policies and access points that enforce least privilege.
Principles
- Grant access to specific IAM roles, not users or whole accounts.
- Allow only the actions needed (for example
s3:GetObject, nots3:*). - Scope to specific prefixes where possible.
- Add explicit denies for anything that must never happen.
Step 1: Deny unencrypted transport
Require TLS for every request:
{
"Sid": "DenyInsecureTransport",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": ["arn:aws:s3:::example-bucket", "arn:aws:s3:::example-bucket/*"],
"Condition": {"Bool": {"aws:SecureTransport": "false"}}
}
Step 2: Restrict access to your organization
Use the aws:PrincipalOrgID condition to deny any principal outside your AWS Organization, so a mistaken grant to another account has no effect.
Step 3: Restrict network paths
For internal data, require access through your VPC endpoint with the aws:SourceVpce condition.
Step 4: Use access points for shared buckets
When several applications or teams share a bucket, create an S3 access point for each, with its own policy limited to its prefix. Delegate access control from the bucket policy to access points owned by your account.
Step 5: Disable ACLs
Set Object Ownership to "Bucket owner enforced" so ACLs no longer apply and policies are the only access mechanism.
Verify
Use IAM Access Analyzer policy validation and external access findings to confirm the result matches your intent.