Securing Amazon S3
Misconfigured buckets are a classic source of data breaches, so S3 security is heavily tested. Modern S3 defaults are secure; your job is to grant exactly the access needed and nothing more.
Layers of S3 access control
| Control | Scope | Notes |
|---|---|---|
| Block Public Access | Account and bucket | Overrides any policy or ACL that would make data public. On by default for new buckets |
| IAM policies | Users/roles in your account | What identities can do on S3 |
| Bucket policies | The bucket | Resource-based; grant cross-account access, enforce conditions |
| ACLs | Bucket/object (legacy) | Disabled by default (Object Ownership = bucket owner enforced); use policies instead |
| Access points | Named endpoints with their own policies | Simplify access for many teams/apps on shared buckets; can be restricted to a VPC |
| Pre-signed URLs | A single object, temporarily | Time-limited access without credentials |
| VPC endpoint policies | Traffic through an endpoint | Restrict which buckets can be reached from a VPC |
An access is allowed only if no explicit Deny applies and an Allow exists in an identity or resource policy (and Block Public Access doesn't prevent it).
Bucket policy examples
Require HTTPS:
{
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": ["arn:aws:s3:::secure-data", "arn:aws:s3:::secure-data/*"],
"Condition": { "Bool": { "aws:SecureTransport": "false" } }
}
Allow only from a specific VPC endpoint:
{
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": ["arn:aws:s3:::internal-data", "arn:aws:s3:::internal-data/*"],
"Condition": { "StringNotEquals": { "aws:SourceVpce": "vpce-0abc1234" } }
}
Allow a CloudFront distribution (Origin Access Control) to read objects — and nobody else from the internet:
{
"Effect": "Allow",
"Principal": { "Service": "cloudfront.amazonaws.com" },
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::site-assets/*",
"Condition": { "StringEquals": { "AWS:SourceArn": "arn:aws:cloudfront::111122223333:distribution/EDFDVBD6EXAMPLE" } }
}
Encryption
| Option | Keys managed by | Notes |
|---|---|---|
| SSE-S3 | S3 | Default for all new objects |
| SSE-KMS | KMS (AWS managed or customer managed key) | Audit key use in CloudTrail; control via key policy; use S3 Bucket Keys to cut KMS request costs |
| DSSE-KMS | KMS | Two layers of encryption, for strict compliance |
| SSE-C | You supply the key with each request | AWS doesn't store your key |
| Client-side | You, before upload | Data is encrypted before it reaches AWS |
Set default encryption on the bucket, and use bucket policies to deny uploads that don't meet your requirement (e.g. not using a specific KMS key).
Cross-origin resource sharing (CORS)
Browsers block requests from one website domain to another unless allowed. If a web app on app.example.com loads assets directly from a bucket, configure a CORS rule on the bucket.
Monitoring and data protection
- CloudTrail data events — log object-level API calls (GetObject, PutObject) for auditing.
- S3 server access logs — detailed request records.
- Amazon Macie — find sensitive data (PII) in buckets.
- IAM Access Analyzer for S3 — find buckets shared publicly or with other accounts.
- Versioning + Object Lock + replication — protect against deletion and ransomware.
Exam patterns
- "Make sure no bucket in the account can ever be public" → account-level Block Public Access (and an SCP to stop disabling it).
- "Only CloudFront should serve the bucket's content" → OAC + bucket policy restricting to the distribution.
- "Many teams need different access to one shared data lake bucket" → S3 access points.
- "Audit every use of the encryption key" → SSE-KMS with a customer managed key.
- "Grant another account write access while the bucket owner owns the objects" → bucket policy + Object Ownership (bucket owner enforced).