S3 Bucket Policy Project 1: Public Read-Only Static Website
Priya Deshmukh runs a Pune bakery site on S3. Marketing wants anyone to open the menu URL, but nobody on the internet should upload or delete. That is a public read-only static website bucket policy — with Block Public Access set carefully.
What you'll learn in this chapter
- Why public read-only websites need a tight GetObject allow
- What the full bucket policy JSON means line by line
- How to apply with CLI, prove allow and deny, then cleanup
- BPA pitfalls and AccessDenied debugging
- Interview answers for this pattern
1. Everyday project story
Jaipur travel founder Ananya Sharma and Berlin curator Lukas Weber both host static HTML. They need Principal "*" GetObject on objects, while Put/Delete stay with admin IAM only. Deeper than a one-line example — you ship, test, and tear down.
Related survey (short examples): S3 bucket policy examples. This chapter is the full project lab.
2. Why this policy?
Why do we need this?
Static files need a public read path without opening write. Mixing private PII in the same bucket is how leaks happen — use a dedicated public assets bucket.
- Anyone can GET objects (browser, curl, CDN later)
- Nobody anonymous can PUT/DELETE
- Admins still manage files via IAM roles
- BPA must allow the public policy without opening ACLs if possible
3. What is the policy?
What is it?
A resource-based policy on the bucket. One Allow statement: Principal *, Action s3:GetObject, Resource arn:aws:s3:::BUCKET/*.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::rb-bakery-site-ap-south-1/*"
}
]
}
Line-by-line
- Version 2012-10-17 — current policy language.
- Sid PublicReadGetObject — label for reviews.
- Effect Allow — grant this statement.
- Principal * — anonymous and signed callers for this action.
- Action s3:GetObject — read object bytes only (not ListBucket, not PutObject).
- Resource .../bucket/* — all objects; GetObject never uses the bare bucket ARN.
4. How to do it (CLI with output)
How to do it (step by step)
# Create bucket in Mumbai
aws s3api create-bucket --bucket rb-bakery-site-ap-south-1 --region ap-south-1 \
--create-bucket-configuration LocationConstraint=ap-south-1
# {"Location": "http://rb-bakery-site-ap-south-1.s3.amazonaws.com/"}
# BPA: allow public policy; keep ACL blocks on
aws s3api put-public-access-block --bucket rb-bakery-site-ap-south-1 --public-access-block-configuration \
BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=false,RestrictPublicBuckets=false
aws s3api put-bucket-policy --bucket rb-bakery-site-ap-south-1 --policy file://public-read.json
# (empty success)
echo "<h1>Menu - Priya Bakery Pune</h1>" > index.html
aws s3 cp index.html s3://rb-bakery-site-ap-south-1/index.html --content-type text/html
aws s3 website s3://rb-bakery-site-ap-south-1/ --index-document index.html --error-document index.html
5. Test: allowed and denied
# ALLOW — anonymous GET
curl -s -o /dev/null -w "%{http_code}\n" \
https://rb-bakery-site-ap-south-1.s3.ap-south-1.amazonaws.com/index.html
# 200
# DENY — anonymous PUT
curl -s -o /dev/null -w "%{http_code}\n" -X PUT --data hack \
https://rb-bakery-site-ap-south-1.s3.ap-south-1.amazonaws.com/hack.txt
# 403
Security note
Do not allow s3:ListBucket to the world — listing leaks keys. Prefer CloudFront + OAC (project 2) for production HTTPS.
6. Multi-industry
Ecommerce menus (Pune — Priya Deshmukh), NGO pages (Nairobi — Amina Okello), school timetables (Nagpur — Rohan Patil), museum catalogues (Berlin — Lukas Weber), QR menus (Chennai — Meera Iyer).
7. Security checklist
- Dedicated public bucket; zero PII/secrets
- Only GetObject public; no ListBucket public
- Keep BlockPublicAcls true when possible
- Add CloudFront later for HTTPS custom domain
8. AccessDenied debugging and Policy Simulator
When S3 returns AccessDenied, check in order: (1) Block Public Access, (2) matching bucket policy Deny, (3) missing IAM Allow, (4) KMS key policy, (5) VPC endpoint policy, (6) legacy ACL. IAM Policy Simulator can miss BPA/VPC/KMS — always re-test with real CLI from the real network path.
# Decode
aws s3api get-bucket-policy --bucket BUCKET --query Policy --output text | python3 -m json.tool
aws s3api get-public-access-block --bucket BUCKET
aws iam simulate-principal-policy --policy-source-arn ARN \
--action-names s3:GetObject --resource-arns arn:aws:s3:::BUCKET/key.txt
aws cloudtrail lookup-events --lookup-attributes AttributeKey=EventName,AttributeValue=GetObject --max-items 5
9. Industries and roles
- Frontend: marketing sites without EC2
- DevOps: BPA + policy as code
- Security: review Principal * every quarter
10. Lab: public read-only bakery site
Do this in ap-south-1 (Mumbai) unless your org forbids it. Set a budget alert first.
How to do it (step by step)
- Create rb-bakery-site-ap-south-1 in ap-south-1.
- Tune BPA; put PublicReadGetObject policy; upload index.html.
- curl GET → 200; curl PUT → 403; save both outputs.
- Enable static website hosting; open website endpoint.
Cleanup (stop billing)
- Delete objects, delete bucket policy, delete bucket.
- Restore account BPA if you changed it.
11. Common errors and fixes
Each row is a real mistake students and juniors hit. Read the fix before you panic.
| Error / symptom | Fix |
|---|---|
| AccessDenied but IAM Allow looks fine | Find explicit Deny in bucket policy or BPA blocking public. |
| Policy JSON rejected on put | Validate with python3 -m json.tool; no smart quotes; use file://. |
| Policy Simulator Allow, CLI Deny | Simulator skips some layers; retest with real CLI/role/network. |
| Wrong Resource ARN shape | GetObject needs arn:...:bucket/* ; ListBucket needs bare bucket ARN. |
| Condition key typo or wrong case | Keys like aws:SourceIp are case-sensitive. |
| Missing Principal on bucket policy | Resource-based policies require Principal; identity policies must omit it. |
| Account-level BPA overrides bucket | Account Block Public Access can still block public grants. |
| Encrypted object / KMS forgotten | SSE-KMS needs key policy + kms:Decrypt/GenerateDataKey. |
| Website 403 after policy | BPA BlockPublicPolicy still true, or wrong object key. |
| ListBucket * opened by mistake | Remove it; fix 404s with correct keys or CloudFront. |
12. Troubleshooting checklist
- Confirm region and endpoint hostname for the bucket.
- Print get-bucket-policy and get-public-access-block.
- Reproduce with a least-privilege lab IAM user.
- Binary-search Denies by commenting one Sid at a time in lab.
- Read CloudTrail errorCode and additionalEventData.
- If KMS involved, test key permissions separately.
- From VPC path: route to gateway prefix list + endpoint policy.
- Cleanup versioning/Object Lock labs carefully.
13. FAQ
Q. Is a bucket policy the same as an IAM policy?
No. Bucket policy is resource-based on the bucket; IAM is identity-based. For same-account, either can Allow; explicit Deny wins; cross-account needs both.
Q. Do I still need ACLs in 2026?
Prefer Bucket owner enforced (ACLs off) + bucket policy. Some log deliveries still need canned ACL on Put.
Q. What is Block Public Access?
Switches that block public ACLs/policies at account or bucket. Disable only the minimum for a deliberate public design.
Q. How do you debug AccessDenied fast?
BPA → bucket policy → IAM → KMS → VPCe → CloudTrail. Never “fix” with Action *.
Q. Is Principal * required for websites?
For true anonymous browser access yes — or use CloudFront OAC with a private bucket (better).
Q. Does the S3 website endpoint give HTTPS?
No — HTTP. Use CloudFront for HTTPS.
Q. Can Resource be only index.html?
Yes — pin specific keys/prefixes.
Q. Fast revoke of public access?
Delete bucket policy or enable RestrictPublicBuckets.
14. Interview Q&A
Q. Why Resource .../* for GetObject?
GetObject is object-level; bare bucket ARN is for bucket actions like ListBucket.
Q. Which BPA flag blocks public policies?
BlockPublicPolicy.
Q. Is opening ListBucket to * OK?
No — it leaks object keys.
Q. Safest public-site pattern today?
Private bucket + CloudFront OAC (next project).
Q. How do you prove the policy works?
Anonymous GET 200 and anonymous PUT 403.
Q. Who wins: IAM Allow vs bucket Deny?
Explicit Deny always wins.
15. Tips in English, Marathi and Hindi
Ravindra Bagale's Tip (EN)
Ship public assets in their own bucket. Treat Principal * like a fire alarm — rare and reviewed.
Ravindra Bagale's Tip (MR)
Public assets वेगळ्या bucket मध्ये. Principal * = fire alarm — दुर्मिळ व review.
Tip (HI)
Public assets अलग bucket में रखें। Principal * को तिमाही review करें.
Try at home
- Apply this policy on a lab bucket; capture 200 and 403.
- Change Resource to a single key; confirm other keys 403.
- Explain BPA flags in 60 seconds.
In short
- Public site policy = Allow GetObject for *.
- Tune BPA; prefer ACL blocks on.
- Always test allow and deny.
- Production prefers CloudFront OAC (next project).
- Never mix PII into a public bucket.
16. Policy design checklist before you save
- Write the business rule in one sentence (who can do what from where).
- Choose Allow vs Deny-unless consciously; remember explicit Deny wins.
- List every Resource ARN shape you need (bucket vs bucket/*).
- Name every Condition key exactly (case-sensitive).
- Decide BPA settings before put-bucket-policy.
- Plan the allow test and the deny test with expected HTTP/CLI codes.
- Plan cleanup (versioning, Object Lock, KMS schedules).
- Paste JSON through python3 -m json.tool before apply.
17. Policy Simulator walkthrough
IAM Policy Simulator is great for identity policies and a first pass on resource policies, but it can miss Block Public Access, VPC endpoint policies, and some KMS denials. Use it to catch typos, then always re-test with AWS CLI from the real network path (office IP, VPC, or CloudFront).
- Open IAM → Policy simulator → pick the user/role.
- Add action s3:GetObject / PutObject and the object ARN.
- If you use resource policies, include the bucket policy text where the tool allows.
- Note Allow/Deny; then run the matching CLI from the real path.
- If Simulator Allow but CLI Deny, check BPA, VPCe, KMS, and explicit Denies next.
18. Copy-paste runbook (print this)
# 0) Region + names
export AWS_REGION=ap-south-1
export BUCKET=rb-lab-$(date +%s)-ap-south-1
# 1) Create + show BPA
aws s3api create-bucket --bucket $BUCKET --region $AWS_REGION \
--create-bucket-configuration LocationConstraint=$AWS_REGION
aws s3api get-public-access-block --bucket $BUCKET || true
# 2) Validate + apply policy
python3 -m json.tool policy.json >/dev/null
aws s3api put-bucket-policy --bucket $BUCKET --policy file://policy.json
aws s3api get-bucket-policy --bucket $BUCKET --query Policy --output text | python3 -m json.tool | head
# 3) Allow test + Deny test (fill per project)
# ... project-specific curls / aws s3 cp ...
# 4) Cleanup
aws s3 rm s3://$BUCKET --recursive || true
aws s3api delete-bucket-policy --bucket $BUCKET || true
aws s3api delete-bucket --bucket $BUCKET
19. Industry story bank (reuse the pattern)
Each project above uses one industry story. Use the same policy pattern in these other industries without rewriting the JSON from scratch — only change the bucket name, principal, and Condition values.
- Healthcare (Priya Nair, Apollo-style clinic): patient PDFs — prefer SSE-KMS + OrgID + MFA delete.
- Fintech (Arjun Mehta, UPI wallet): statements — office IP + SecureTransport + CloudFront OAC for the customer portal.
- EdTech (Sofia Alvarez, online course): course videos — CloudFront OAC only; never public ACLs.
- Logistics (Kenji Watanabe, fleet app): GPS dumps — VPC endpoint only from the private subnet runners.
- Media (Fatima Khan, newsroom): raw footage — Object Lock COMPLIANCE + deny delete.
- Retail (Lucas Meyer, D2C brand): product images — public read for /images/* only; deny everything else.
- SaaS (Ananya Iyer, B2B CRM): tenant exports — ${aws:username} or ${aws:PrincipalTag/tenant} prefixes.
- Government (Rahul Deshmukh, state portal): RTI archives — OrgID + MFA + Object Lock.
20. AccessDenied debugging flowchart
- Read the error code and the request ID. Save both.
- Confirm you are calling the right bucket and region.
- Check Block Public Access if the principal is * or a service that looks public.
- Decode the policy: is there an explicit Deny that matches? Deny wins.
- Check Condition keys: SecureTransport, SourceVpce, SourceIp, PrincipalOrgID, encryption headers.
- For KMS: does the key policy allow the caller? Bucket policy alone is not enough.
- For CloudFront: is OAC present and the distribution id matching?
- For VPC: are you really going through the endpoint (no public IP leak)?
- Simulate in IAM Policy Simulator, then re-test with CLI from the real path.
- Only then open a support case with the request id and a redacted policy.
21. What good looks like in a PR review
- Policy JSON validates and is indented.
- Every Statement has Sid, Effect, Action, Resource.
- No Principal:* with write actions unless BPA and a Deny fence exist.
- Allow test and Deny test are documented with expected output.
- Cleanup steps will not strand Object Lock or KMS keys.
Practice Tasks
Maple Leaf Books is a bookselling business in Toronto. Emily Tremblay looks after its IT and cloud work. They count money in CAD and run their AWS resources in the ca-central-1 Region. In the tasks below you do real jobs for them using what this lesson teaches. All names, account IDs, IP addresses and numbers are made up for practice.
0 / 16
Easy List bucket objects
Why: See site files.
What to do: List objects in s3://mapleleafbooks-web.
- Read the task and decide which AWS service it is about.
- Write aws, then the service name, then the operation (the action word).
- Add each needed flag (--name) followed by its value from the task.
- Flags can be in any order. Click Check answer.
Easy Get bucket policy
Why: Read current policy.
What to do: Get bucket policy for mapleleafbooks-web.
- Read the task and decide which AWS service it is about.
- Write aws, then the service name, then the operation (the action word).
- Add each needed flag (--name) followed by its value from the task.
- Flags can be in any order. Click Check answer.
Easy BPA can block public policy — 1
Why: BPA first.
What to do: Enter 1 if Block Public Access can block a public-read policy.
- Write down the numbers the task gives you.
- Work it out step by step on paper.
- Type only the final answer in the box.
- Click Check answer.
Easy Public sites need GetObject — 1
Why: Read only.
What to do: Enter 1 if public sites usually allow GetObject not PutObject.
- Write down the numbers the task gives you.
- Work it out step by step on paper.
- Type only the final answer in the box.
- Click Check answer.
Easy Head bucket
Why: Exists check.
What to do: Head bucket mapleleafbooks-web.
- Read the task and decide which AWS service it is about.
- Write aws, then the service name, then the operation (the action word).
- Add each needed flag (--name) followed by its value from the task.
- Flags can be in any order. Click Check answer.
Medium Put website config
Why: Enable static hosting.
What to do: Put bucket website on mapleleafbooks-web with index index.html.
- Read the task and decide which AWS service it is about.
- Write aws, then the service name, then the operation (the action word).
- Add each needed flag (--name) followed by its value from the task.
- Flags can be in any order. Click Check answer.
Medium Get website config
Why: Confirm hosting.
What to do: Get bucket website mapleleafbooks-web.
- Read the task and decide which AWS service it is about.
- Write aws, then the service name, then the operation (the action word).
- Add each needed flag (--name) followed by its value from the task.
- Flags can be in any order. Click Check answer.
Medium Get public access block
Why: BPA gates public.
What to do: Get public access block mapleleafbooks-web.
- Read the task and decide which AWS service it is about.
- Write aws, then the service name, then the operation (the action word).
- Add each needed flag (--name) followed by its value from the task.
- Flags can be in any order. Click Check answer.
Medium Deny public PutObject
Why: No public write.
What to do: Deny s3:PutObject for Principal * on arn:aws:s3:::mapleleafbooks-web/*.
- Start with { "Version": "2012-10-17", "Statement": [ ... ] }.
- In each statement write Effect, Action and Resource.
- Give only what the task needs (least privilege).
- Use double quotes and commas correctly. Click Check answer.
Medium Index suffix JSON
Why: Website index.
What to do: JSON IndexDocument Suffix index.html.
- Start from the top-level keys the task names.
- Indent child keys with 2 spaces (no tabs).
- Fill in each property with the value from the task.
- Click Check answer. Key order does not matter.
Medium Website endpoint for index — 1
Why: Index docs.
What to do: Enter 1 if website endpoint serves IndexDocument.
- Write down the numbers the task gives you.
- Work it out step by step on paper.
- Type only the final answer in the box.
- Click Check answer.
Medium AccessDenied HTTP code
Why: Debug.
What to do: HTTP status for S3 AccessDenied (enter 403)?
- Write down the numbers the task gives you.
- Work it out step by step on paper.
- Type only the final answer in the box.
- Click Check answer.
Hard Put index object
Why: Upload homepage.
What to do: Put object index.html into mapleleafbooks-web from ./index.html.
- Read the task and decide which AWS service it is about.
- Write aws, then the service name, then the operation (the action word).
- Add each needed flag (--name) followed by its value from the task.
- Flags can be in any order. Click Check answer.
Hard Allow public GetObject
Why: Static site read.
What to do: Allow s3:GetObject for Principal * on arn:aws:s3:::mapleleafbooks-web/*.
- Start with { "Version": "2012-10-17", "Statement": [ ... ] }.
- In each statement write Effect, Action and Resource.
- Give only what the task needs (least privilege).
- Use double quotes and commas correctly. Click Check answer.
Hard Error document JSON
Why: Custom 404.
What to do: JSON ErrorDocument Key error.html.
- Start from the top-level keys the task names.
- Indent child keys with 2 spaces (no tabs).
- Fill in each property with the value from the task.
- Click Check answer. Key order does not matter.
Hard Delete bucket policy
Why: Cleanup.
What to do: Delete bucket policy on mapleleafbooks-web.
- Read the task and decide which AWS service it is about.
- Write aws, then the service name, then the operation (the action word).
- Add each needed flag (--name) followed by its value from the task.
- Flags can be in any order. Click Check answer.
Revision, interview and comparison cards
Revise this lesson in a few minutes. Save the cards on your phone or download them as PNG files. The QR code on each card opens this lesson.








More cards: Ravi Sir's Cards.