Encryption
S3 automatically encrypts new objects with SSE-S3 (AES-256, keys managed by S3). Choose SSE-KMS for KMS key control, key policies and CloudTrail auditing of every decrypt; enable S3 Bucket Keys to cut KMS request costs. SSE-C requires the client to send its key over HTTPS on every request; S3 does not store that key. Client-side encryption encrypts before upload, so S3 only ever sees ciphertext. Enforce TLS in transit with a bucket policy that denies requests where aws:SecureTransport is false.
Who can access an object
flowchart LR
Q["Request<br/>GetObject"] --> BPA{"Block Public Access<br/>blocks it?"}
BPA -- "yes" --> X["403 Denied"]
BPA -- "no" --> D{"Explicit Deny<br/>in any policy?"}
D -- "yes" --> X
D -- "no" --> A{"IAM or bucket<br/>policy Allow?"}
A -- "yes" --> Y["Allowed"]
A -- "no" --> X
%% step Q: Every request is checked against several layers.
%% step BPA: Block Public Access settings override any policy that would make data public.
%% step D: An explicit Deny in the bucket policy, IAM policy or SCP wins.
%% step A: Access needs an Allow in the caller's IAM policy or the bucket policy (for cross-account, both sides).
%% step Y: Only then is the object returned.
%% step X: Most 403 errors come from Block Public Access, a missing Allow or a KMS key permission.
New buckets enable Block Public Access and bucket-owner-enforced Object Ownership, so ACLs are disabled and access is controlled only by policies. Disable public blocking only for a clear public requirement with a minimal bucket policy. If objects use SSE-KMS, the caller also needs kms:Decrypt on the key.
A bucket policy example that allows public read of one prefix:
{"Version": "2012-10-17", "Statement": [{"Sid": "PublicReadSite", "Effect": "Allow",
"Principal": "*", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::devops-assets-123/site/*"}]}
Temporary and browser access
A pre-signed URL grants time-limited access using the signer permissions; if the signer loses access or its temporary credentials expire, the URL stops working early. Use it to let users download or upload a single object without AWS credentials.
CORS only controls which browser origins may read responses from JavaScript; it does not replace IAM or bucket policies.
Static website hosting
S3 website endpoints (http://bucket.s3-website-ap-southeast-1.amazonaws.com) serve index.html and an error document, support HTTP only, and require public read. For production, put CloudFront in front: HTTPS with your domain's certificate, caching at edge locations, and Origin Access Control (OAC) so the bucket stays private and only CloudFront can read it. A 403 commonly indicates Block Public Access or a missing policy.
Monitoring access
Enable server access logs or CloudTrail data events to see who read what, and use IAM Access Analyzer for S3 to find buckets shared publicly or with other accounts. S3 Object Lock (WORM) keeps objects from being deleted for a retention period, useful for backups and compliance.
| Command | Purpose |
|---|---|
aws s3 website s3://devops-assets-123/ --index-document index.html --error-document error.html |
Configure a website |
aws s3api put-bucket-policy --bucket devops-assets-123 --policy file://policy.json |
Attach a bucket policy |
aws s3api get-public-access-block --bucket devops-assets-123 |
Check Block Public Access |
aws s3 presign s3://devops-assets-123/report.pdf --expires-in 3600 |
Create a temporary URL |
Traps: expecting HTTPS from the website endpoint; thinking CORS grants access; forgetting kms:Decrypt for SSE-KMS objects.