S3
02 / 08

Permissions & Policies

S3 Permissions & Policies

S3 access control has multiple layers: bucket policies (resource-based), IAM policies (identity-based), Block Public Access settings, and Access Points. Understanding when to use each is critical for both security and correct functionality.

Bucket Policies & Block Public Access

// Bucket policy — applied at the bucket level, allows cross-account access
// Evaluated alongside IAM policies; DENY always wins

// Allow public read for static website bucket (ACLs disabled, use bucket policy)
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadGetObject",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-website-bucket/*"
    }
  ]
}

// Allow only CloudFront OAC (Origin Access Control) to read objects
// (Replace PUBLIC read policy once CloudFront is set up)
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowCloudFrontOAC",
      "Effect": "Allow",
      "Principal": {
        "Service": "cloudfront.amazonaws.com"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-website-bucket/*",
      "Condition": {
        "StringEquals": {
          "AWS:SourceArn": "arn:aws:cloudfront::123456789012:distribution/EDFDVBD6EXAMPLE"
        }
      }
    }
  ]
}

// Allow a specific IAM role to read/write to a prefix
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {"AWS": "arn:aws:iam::123456789012:role/my-app-role"},
      "Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
      "Resource": "arn:aws:s3:::my-app-bucket/uploads/*"
    },
    {
      "Effect": "Allow",
      "Principal": {"AWS": "arn:aws:iam::123456789012:role/my-app-role"},
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::my-app-bucket",
      "Condition": {"StringLike": {"s3:prefix": "uploads/*"}}
    }
  ]
}
# Apply bucket policy
aws s3api put-bucket-policy \
  --bucket my-website-bucket \
  --policy file://bucket-policy.json

# Block Public Access — 4 independent settings, all ON by default for new buckets
# BlockPublicAcls:        Reject requests to PUT public ACLs
# IgnorePublicAcls:       Ignore public ACLs already applied
# BlockPublicPolicy:      Reject requests to PUT bucket policy that grants public access
# RestrictPublicBuckets:  Restrict public/cross-account access even if policy allows it

# Check Block Public Access settings
aws s3api get-public-access-block --bucket my-bucket

# Disable Block Public Access for a static website bucket
# (Only do this if you intend public access — use OAC+CloudFront instead)
aws s3api put-public-access-block \
  --bucket my-website-bucket \
  --public-access-block-configuration \
    "BlockPublicAcls=false,IgnorePublicAcls=false,BlockPublicPolicy=false,RestrictPublicBuckets=false"

# Legacy ACLs — avoid for new buckets; use bucket policies instead
# "Bucket owner enforced" setting disables ACLs (recommended for new buckets)
aws s3api put-bucket-ownership-controls \
  --bucket my-bucket \
  --ownership-controls "Rules=[{ObjectOwnership=BucketOwnerEnforced}]"

IAM Policies & Access Points

// IAM policy for an application role — read/write to specific bucket prefix
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:DeleteObject"
      ],
      "Resource": "arn:aws:s3:::my-app-uploads/*"
    },
    {
      "Effect": "Allow",
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::my-app-uploads",
      "Condition": {
        "StringLike": {
          "s3:prefix": ["", "uploads/", "uploads/*"]
        }
      }
    }
  ]
}
# VPC Endpoint for S3 — route S3 traffic within AWS network (no internet)
# Gateway endpoint: free; affects route tables; only S3 and DynamoDB
aws ec2 create-vpc-endpoint \
  --vpc-id vpc-0abc123 \
  --service-name com.amazonaws.us-east-1.s3 \
  --route-table-ids rtb-0abc123

# S3 Access Points — per-application access control on a shared bucket
# Useful when multiple teams/apps share a bucket but need isolated permissions
aws s3control create-access-point \
  --account-id 123456789012 \
  --name data-science-access-point \
  --bucket my-shared-data-lake \
  --vpc-configuration VpcId=vpc-0abc123

# Access via Access Point ARN
# s3://arn:aws:s3:us-east-1:123:accesspoint/data-science-access-point/key.csv

# Cross-account access — bucket policy grants access to another account
# Account B accesses Account A's bucket:
# Account A bucket policy allows Account B's role
# Account B IAM policy allows s3:* on Account A's bucket ARN

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free