S3 data breaches consistently make headlines—not because S3 is insecure, but because it's misconfigured. A single overly-permissive bucket policy can expose millions of records. This guide covers the security controls that prevent breaches and meet compliance requirements.
S3 Security Model
S3 security operates on multiple layers that work together:
┌─────────────────────────────────────────────────────────────────────────┐
│ S3 SECURITY LAYERS │
└─────────────────────────────────────────────────────────────────────────┘
│
┌────────────────────────────────┼────────────────────────────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ NETWORK │ │ ACCESS │ │ DATA │
│ CONTROLS │ │ CONTROLS │ │ PROTECTION │
├──────────────┤ ├──────────────┤ ├──────────────┤
│ • VPC Endpts │ │ • Block Publ │ │ • Encryption │
│ • S3 Access │ │ • IAM Policy │ │ • Versioning │
│ Points │ │ • Bucket Pol │ │ • Object Lock│
│ • Firewall │ │ • ACLs │ │ • MFA Delete │
└──────────────┘ └──────────────┘ └──────────────┘
│
▼
┌──────────────────┐
│ MONITORING │
├──────────────────┤
│ • Access Logging │
│ • CloudTrail │
│ • S3 Analytics │
│ • Access Analyzer│
└──────────────────┘
Block Public Access
The first line of defense is S3 Block Public Access, which prevents buckets from being made public—even if a bucket policy or ACL attempts to grant public access.
Account-Level Settings
Apply to all buckets in the account:
# Enable all Block Public Access settings at account level
aws s3control put-public-access-block \
--account-id 123456789012 \
--public-access-block-configuration \
"BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"
Bucket-Level Settings
Apply to individual buckets:
# Enable Block Public Access on a bucket
aws s3api put-public-access-block \
--bucket my-bucket \
--public-access-block-configuration \
"BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"
# Verify settings
aws s3api get-public-access-block --bucket my-bucket
What Each Setting Does
| Setting | Effect |
|---|---|
| BlockPublicAcls | Reject PUT requests that include public ACLs |
| IgnorePublicAcls | Ignore all public ACLs on bucket and objects |
| BlockPublicPolicy | Reject bucket policies that grant public access |
| RestrictPublicBuckets | Restrict access to bucket with public policies to authorized users only |
Recommendation: Enable all four settings at the account level unless you have a specific need for public buckets (like static website hosting).
Encryption Options
All data in S3 should be encrypted at rest. S3 offers four encryption methods:
Server-Side Encryption with S3-Managed Keys (SSE-S3)
AWS manages the encryption keys entirely. Simplest option with no additional cost.
# Upload with SSE-S3
aws s3 cp file.txt s3://my-bucket/ --sse AES256
# Enable default encryption on bucket
aws s3api put-bucket-encryption \
--bucket my-bucket \
--server-side-encryption-configuration '{
"Rules": [{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "AES256"
},
"BucketKeyEnabled": true
}]
}'
Server-Side Encryption with KMS (SSE-KMS)
AWS KMS manages keys with additional features: audit trails, key policies, automatic rotation.
# Upload with SSE-KMS
aws s3 cp file.txt s3://my-bucket/ \
--sse aws:kms \
--sse-kms-key-id alias/my-s3-key
# Enable default KMS encryption on bucket
aws s3api put-bucket-encryption \
--bucket my-bucket \
--server-side-encryption-configuration '{
"Rules": [{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "aws:kms",
"KMSMasterKeyID": "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
},
"BucketKeyEnabled": true
}]
}'
Bucket Keys: Enable BucketKeyEnabled to reduce KMS API calls and costs by using a bucket-level key derived from your KMS key.
Server-Side Encryption with Customer Keys (SSE-C)
You provide and manage encryption keys. S3 encrypts/decrypts but doesn't store your key.
# Generate a 256-bit key
KEY=$(openssl rand -base64 32)
# Upload with customer key
aws s3 cp file.txt s3://my-bucket/file.txt \
--sse-c \
--sse-c-key "$KEY"
# Download requires same key
aws s3 cp s3://my-bucket/file.txt ./file.txt \
--sse-c \
--sse-c-key "$KEY"
Warning: If you lose the key, you cannot decrypt the data. S3 doesn't store customer-provided keys.
Client-Side Encryption
Encrypt data before uploading. Provides end-to-end encryption where data is never decrypted on AWS.
# Encrypt locally with OpenSSL
openssl enc -aes-256-cbc -salt -in file.txt -out file.txt.enc -pass pass:$PASSWORD
# Upload encrypted file
aws s3 cp file.txt.enc s3://my-bucket/
# Download and decrypt
aws s3 cp s3://my-bucket/file.txt.enc ./
openssl enc -aes-256-cbc -d -in file.txt.enc -out file.txt -pass pass:$PASSWORD
Encryption Decision Matrix
| Requirement | Recommended Encryption |
|---|---|
| Default protection, no extra cost | SSE-S3 |
| Audit trail of key access | SSE-KMS |
| Key rotation policies | SSE-KMS |
| Separate key permissions | SSE-KMS |
| You must control keys | SSE-C |
| Regulatory requirement for client-side | Client-side |
| Cross-account sharing with encryption | SSE-KMS with key policy |
Access Control Methods
S3 offers multiple access control mechanisms. Use them in combination:
IAM Policies
Attach to IAM users, groups, or roles. Define what actions the principal can perform on which resources.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowS3BucketAccess",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-bucket",
"arn:aws:s3:::my-bucket/*"
]
}
]
}
Bucket Policies
Attach directly to buckets. Best for cross-account access and resource-based conditions.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCrossAccountAccess",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::999888777666:root"
},
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-bucket",
"arn:aws:s3:::my-bucket/*"
]
}
]
}
Common Bucket Policy Patterns
Deny unencrypted uploads:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyUnencryptedUploads",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::my-bucket/*",
"Condition": {
"StringNotEquals": {
"s3:x-amz-server-side-encryption": ["AES256", "aws:kms"]
}
}
}
]
}
Restrict access to specific VPC:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "RestrictToVPC",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::my-bucket",
"arn:aws:s3:::my-bucket/*"
],
"Condition": {
"StringNotEquals": {
"aws:SourceVpce": "vpce-1234567890abcdef0"
}
}
}
]
}
Restrict to specific IP addresses:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "RestrictByIP",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::my-bucket",
"arn:aws:s3:::my-bucket/*"
],
"Condition": {
"NotIpAddress": {
"aws:SourceIp": [
"192.168.1.0/24",
"10.0.0.0/8"
]
}
}
}
]
}
Enforce HTTPS only:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "EnforceHTTPS",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::my-bucket",
"arn:aws:s3:::my-bucket/*"
],
"Condition": {
"Bool": {
"aws:SecureTransport": "false"
}
}
}
]
}
VPC Endpoints for Private Access
VPC endpoints allow EC2 instances to access S3 without traversing the public internet.
Gateway Endpoints (Free)
Route S3 traffic through AWS network:
# Create VPC endpoint
aws ec2 create-vpc-endpoint \
--vpc-id vpc-12345678 \
--service-name com.amazonaws.us-east-1.s3 \
--route-table-ids rtb-12345678
# The route table automatically gets a route to S3 via the endpoint
Interface Endpoints (PrivateLink)
Create an ENI in your VPC for S3 access:
# Create interface endpoint
aws ec2 create-vpc-endpoint \
--vpc-id vpc-12345678 \
--vpc-endpoint-type Interface \
--service-name com.amazonaws.us-east-1.s3 \
--subnet-ids subnet-12345678 \
--security-group-ids sg-12345678
Use gateway endpoints for most cases (free, simpler). Use interface endpoints when you need:
- On-premises access via Direct Connect/VPN
- Cross-region access
- Specific IP addresses for firewall rules
Logging and Monitoring
S3 Server Access Logging
Logs all requests to your bucket:
# Create logging bucket
aws s3 mb s3://my-access-logs-bucket
# Grant S3 permission to write logs
aws s3api put-bucket-acl \
--bucket my-access-logs-bucket \
--grant-write URI=http://acs.amazonaws.com/groups/s3/LogDelivery \
--grant-read-acp URI=http://acs.amazonaws.com/groups/s3/LogDelivery
# Enable logging
aws s3api put-bucket-logging \
--bucket my-bucket \
--bucket-logging-status '{
"LoggingEnabled": {
"TargetBucket": "my-access-logs-bucket",
"TargetPrefix": "my-bucket/"
}
}'
CloudTrail for API Auditing
Enable CloudTrail data events for S3:
aws cloudtrail put-event-selectors \
--trail-name my-trail \
--event-selectors '[{
"ReadWriteType": "All",
"DataResources": [{
"Type": "AWS::S3::Object",
"Values": ["arn:aws:s3:::my-bucket/"]
}]
}]'
S3 Access Analyzer
Identifies buckets with public or cross-account access:
# Create analyzer
aws accessanalyzer create-analyzer \
--analyzer-name my-s3-analyzer \
--type ACCOUNT
# List findings
aws accessanalyzer list-findings --analyzer-arn arn:aws:access-analyzer:us-east-1:123456789012:analyzer/my-s3-analyzer
Object Lock for Immutability
Object Lock prevents objects from being deleted or overwritten for a retention period—essential for compliance and ransomware protection.
Enable Object Lock
Must be enabled when creating the bucket:
# Create bucket with Object Lock
aws s3api create-bucket \
--bucket compliance-bucket \
--object-lock-enabled-for-bucket
# Set default retention
aws s3api put-object-lock-configuration \
--bucket compliance-bucket \
--object-lock-configuration '{
"ObjectLockEnabled": "Enabled",
"Rule": {
"DefaultRetention": {
"Mode": "COMPLIANCE",
"Years": 7
}
}
}'
Lock Modes
| Mode | Can Override? | Use Case |
|---|---|---|
| Governance | Yes, with s3:BypassGovernanceRetention permission | Testing, flexible retention |
| Compliance | No, not even root account | Regulatory (SEC 17a-4, HIPAA), ransomware protection |
Legal Hold
Indefinite lock independent of retention period:
# Apply legal hold
aws s3api put-object-legal-hold \
--bucket my-bucket \
--key evidence/document.pdf \
--legal-hold Status=ON
# Remove legal hold
aws s3api put-object-legal-hold \
--bucket my-bucket \
--key evidence/document.pdf \
--legal-hold Status=OFF
Security Checklist
Account-Level
- Enable Block Public Access at account level
- Enable S3 Access Analyzer
- Enable CloudTrail for management events
- Configure AWS Config rules for S3 compliance
Bucket-Level
- Enable Block Public Access on each bucket
- Enable default encryption (SSE-S3 or SSE-KMS)
- Configure bucket policy with least privilege
- Enable versioning for critical data
- Enable access logging
- Disable ACLs (use policies instead)
Sensitive Buckets
- Enable Object Lock (compliance mode)
- Enable MFA Delete
- Restrict access via VPC endpoint
- Enable CloudTrail data events
- Use SSE-KMS for encryption key auditing
Monitoring
- Review S3 Access Analyzer findings weekly
- Alert on public bucket creation
- Monitor for unusual access patterns
- Audit bucket policies quarterly
Conclusion
S3 security is not a single setting—it's a combination of network controls, access policies, encryption, and monitoring working together. The most common breaches result from:
- Misconfigured bucket policies — Always test with least privilege
- Disabled Block Public Access — Keep it enabled unless absolutely necessary
- No encryption — Enable default encryption on all buckets
- No monitoring — You can't protect what you can't see
Start with the account-level settings (Block Public Access, Access Analyzer), then apply bucket-specific controls based on data sensitivity.
For complete S3 coverage, see our S3 Complete Guide. For protecting against data loss, see Versioning and Replication Guide. For building secure CLI commands, use our AWS S3 Command Generator.