S3
07 / 08

Security, IAM Policies & Access Control

S3 Security, IAM Policies & Access Control

S3 security has multiple layers: Block Public Access settings (account and bucket level), bucket policies, IAM policies, ACLs (legacy), and encryption. The default is private — everything requires explicit grants.

Block Public Access — Always Check This First

# View account-level Block Public Access settings
aws s3control get-public-access-block --account-id 123456789012

# Bucket-level
aws s3api get-public-access-block --bucket my-bucket

# These 4 settings block public access regardless of bucket policy:
# BlockPublicAcls        — ignores ACLs that grant public access
# IgnorePublicAcls       — ignores any existing public ACLs
# BlockPublicPolicy      — rejects bucket policies that grant public access
# RestrictPublicBuckets  — restricts access to authorized users/services only

Bucket Policies

Bucket policies are resource-based IAM policies attached to the bucket. They can grant access to other AWS accounts, specific IAM roles, CloudFront, and anonymous users.

// Allow CloudFront OAC to read objects (modern approach)
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowCloudFrontServicePrincipal",
      "Effect": "Allow",
      "Principal": {
        "Service": "cloudfront.amazonaws.com"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-bucket/*",
      "Condition": {
        "StringEquals": {
          "AWS:SourceArn": "arn:aws:cloudfront::123456789012:distribution/ABCDEF123456"
        }
      }
    }
  ]
}
// Grant another AWS account read access
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::999999999999:root"
      },
      "Action": ["s3:GetObject", "s3:ListBucket"],
      "Resource": [
        "arn:aws:s3:::my-bucket",
        "arn:aws:s3:::my-bucket/*"
      ]
    }
  ]
}
# Apply bucket policy
aws s3api put-bucket-policy --bucket my-bucket --policy file://policy.json

# Get current policy
aws s3api get-bucket-policy --bucket my-bucket | jq '.Policy | fromjson'

IAM Policies for S3 Access

// IAM policy: allow a specific app to read/write its prefix only
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
      "Resource": "arn:aws:s3:::my-bucket/app-data/${aws:username}/*"
    },
    {
      "Effect": "Allow",
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::my-bucket",
      "Condition": {
        "StringLike": {
          "s3:prefix": ["app-data/${aws:username}/*"]
        }
      }
    }
  ]
}

Encryption

  • SSE-S3 (default since Jan 2023): AWS manages keys, AES-256, no extra cost

  • SSE-KMS: your KMS key, audit trail via CloudTrail, extra API costs, per-request charges

  • SSE-C: you provide the key with every request, AWS never stores it

  • DSSE-KMS: dual-layer encryption with KMS (compliance use cases)

  • Client-side: encrypt before upload, AWS sees only ciphertext

# Upload with SSE-KMS
aws s3 cp sensitive.dat s3://my-bucket/ \
  --sse aws:kms \
  --sse-kms-key-id arn:aws:kms:us-east-1:123456789012:key/mrk-abc123

# Require encryption via bucket policy (deny unencrypted uploads)
# Add to bucket policy:
# "Condition": { "StringNotEquals": { "s3:x-amz-server-side-encryption": "aws:kms" } }

S3 Object Lock (WORM)

Object Lock prevents objects from being deleted or overwritten for a fixed period or indefinitely. Must be enabled at bucket creation.

  • Governance mode: only users with s3:BypassGovernanceRetention can delete

  • Compliance mode: nobody can delete, not even root — use for strict compliance

  • Legal hold: indefinite, overrides retention period, toggle on/off

VPC Endpoints & Access Logging

# Create Gateway endpoint to keep S3 traffic inside VPC (free)
aws ec2 create-vpc-endpoint \
  --vpc-id vpc-abc123 \
  --service-name com.amazonaws.eu-west-1.s3 \
  --route-table-ids rtb-xyz789

# Enable S3 access logging to another bucket
aws s3api put-bucket-logging --bucket my-bucket \
  --bucket-logging-status '{
    "LoggingEnabled": {
      "TargetBucket": "my-logs-bucket",
      "TargetPrefix": "s3-access-logs/my-bucket/"
    }
  }'

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

Start free