AWS S3 Security: Complete Enterprise Hardening Guide

Amazon S3 is the most widely used cloud storage service, holding petabytes of enterprise data. It is also the source of countless data breaches—nearly all preventable with proper configuration. From the Capital One breach (100 million records) to numerous public bucket exposures, S3 misconfiguration remains a top cloud security risk. This comprehensive guide provides an enterprise security checklist that I have developed through securing S3 infrastructure across financial services and healthcare organizations.

The Defense-in-Depth Model

flowchart TB
    subgraph Layer1 ["Layer 1: Account Level"]
        A1["Block Public Access (Account)"]
        A2["SCPs in AWS Organizations"]
    end
    
    subgraph Layer2 ["Layer 2: Bucket Level"]
        B1["Block Public Access (Bucket)"]
        B2["Bucket Policy"]
        B3["ACLs Disabled"]
    end
    
    subgraph Layer3 ["Layer 3: Object Level"]
        C1["SSE-KMS Encryption"]
        C2["Object Lock (WORM)"]
        C3["Versioning"]
    end
    
    subgraph Layer4 ["Layer 4: Access Level"]
        D1["IAM Policies"]
        D2["VPC Endpoints"]
        D3["Access Points"]
    end
    
    subgraph Layer5 ["Layer 5: Detection"]
        E1["Access Logging"]
        E2["CloudTrail"]
        E3["Macie"]
    end
    
    Layer1 --> Layer2 --> Layer3 --> Layer4 --> Layer5
    
    style A1 fill:#FFCDD2,stroke:#C62828
    style C1 fill:#C8E6C9,stroke:#2E7D32

Layer 1: Account-Level Controls

Block Public Access (Account Level)

This is the single most important control. Enable it at the AWS account level to prevent any bucket from being made public, regardless of bucket policy:

aws s3control put-public-access-block \
  --account-id 123456789012 \
  --public-access-block-configuration \
  "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"

What each setting does:

  • BlockPublicAcls: Rejects PUT requests that include public ACLs
  • IgnorePublicAcls: Ignores existing public ACLs
  • BlockPublicPolicy: Rejects bucket policies that grant public access
  • RestrictPublicBuckets: Restricts access to buckets with public policies to AWS service principals only

Service Control Policies (SCPs)

In AWS Organizations, use SCPs to enforce S3 security across all accounts:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyUnencryptedUploads",
      "Effect": "Deny",
      "Action": "s3:PutObject",
      "Resource": "*",
      "Condition": {
        "Null": {
          "s3:x-amz-server-side-encryption": "true"
        }
      }
    },
    {
      "Sid": "DenyNonKMSEncryption",
      "Effect": "Deny",
      "Action": "s3:PutObject",
      "Resource": "*",
      "Condition": {
        "StringNotEquals": {
          "s3:x-amz-server-side-encryption": "aws:kms"
        }
      }
    }
  ]
}

Layer 2: Bucket-Level Controls

Disable ACLs Entirely

S3 ACLs are a legacy access control mechanism that causes confusion. For new buckets, disable them entirely:

aws s3api put-bucket-ownership-controls \
  --bucket my-bucket \
  --ownership-controls "Rules=[{ObjectOwnership=BucketOwnerEnforced}]"

With BucketOwnerEnforced, ACLs are disabled and the bucket owner automatically owns all objects. All access is controlled via IAM policies and bucket policies only.

Layer 3: Object-Level Encryption

SSE-KMS with Customer Managed Keys

For compliance (HIPAA, PCI-DSS), use SSE-KMS with customer-managed keys (CMK). This provides audit trails via CloudTrail and allows you to revoke access by disabling the key.

{
  "Rules": [
    {
      "ApplyServerSideEncryptionByDefault": {
        "SSEAlgorithm": "aws:kms",
        "KMSMasterKeyID": "arn:aws:kms:us-east-1:123456789012:key/abcd1234-xxxx"
      },
      "BucketKeyEnabled": true
    }
  ]
}

Important: Enable BucketKeyEnabled to reduce KMS costs by up to 99%. Without it, every object operation makes a KMS API call.

Layer 4: Network Controls

VPC Endpoints (Gateway)

For private workloads, access S3 through a VPC Gateway Endpoint. Traffic never leaves the AWS network:

resource "aws_vpc_endpoint" "s3" {
  vpc_id       = aws_vpc.main.id
  service_name = "com.amazonaws.us-east-1.s3"
  
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Action    = ["s3:GetObject", "s3:PutObject"]
      Effect    = "Allow"
      Resource  = "arn:aws:s3:::my-bucket/*"
      Principal = "*"
    }]
  })
}

Layer 5: Detection and Monitoring

S3 Server Access Logging

Enable access logging to a dedicated logging bucket:

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

Amazon Macie for PII Detection

Enable Macie to automatically scan buckets for sensitive data (PII, credentials, financial data):

aws macie2 enable-macie
aws macie2 create-classification-job \
  --job-type SCHEDULED \
  --s3-job-definition '{"bucketDefinitions": [{"accountId": "123456789012", "buckets": ["my-bucket"]}]}'

Compliance Checklist

ControlHIPAAPCI-DSSSOC 2
Block Public AccessRequiredRequiredRequired
SSE-KMS EncryptionRequiredRequiredRecommended
Access LoggingRequiredRequiredRequired
VersioningRecommendedRecommendedRecommended
Object Lock (WORM)OptionalRequired (for logs)Optional

Key Takeaways

  • Enable Block Public Access at the account level first
  • Disable ACLs using BucketOwnerEnforced
  • Use SSE-KMS with customer-managed keys for compliance
  • Enable Bucket Key to reduce KMS costs
  • Use VPC Endpoints for private access
  • Deploy Macie for automated sensitive data detection

Discover more from C4: Container, Code, Cloud & Context

Subscribe to get the latest posts sent to your email.

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.