Ravindra BagaleCourses & study guides मराठी Track your progress

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

  1. Version 2012-10-17 — current policy language.
  2. Sid PublicReadGetObject — label for reviews.
  3. Effect Allow — grant this statement.
  4. Principal * — anonymous and signed callers for this action.
  5. Action s3:GetObject — read object bytes only (not ListBucket, not PutObject).
  6. Resource .../bucket/* — all objects; GetObject never uses the bare bucket ARN.
Browser → S3 GET object → 200Anonymous read of public assets
PUT/DELETE still need IAM admin

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)

  1. Create rb-bakery-site-ap-south-1 in ap-south-1.
  2. Tune BPA; put PublicReadGetObject policy; upload index.html.
  3. curl GET → 200; curl PUT → 403; save both outputs.
  4. Enable static website hosting; open website endpoint.

Cleanup (stop billing)

  1. Delete objects, delete bucket policy, delete bucket.
  2. 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 / symptomFix
AccessDenied but IAM Allow looks fineFind explicit Deny in bucket policy or BPA blocking public.
Policy JSON rejected on putValidate with python3 -m json.tool; no smart quotes; use file://.
Policy Simulator Allow, CLI DenySimulator skips some layers; retest with real CLI/role/network.
Wrong Resource ARN shapeGetObject needs arn:...:bucket/* ; ListBucket needs bare bucket ARN.
Condition key typo or wrong caseKeys like aws:SourceIp are case-sensitive.
Missing Principal on bucket policyResource-based policies require Principal; identity policies must omit it.
Account-level BPA overrides bucketAccount Block Public Access can still block public grants.
Encrypted object / KMS forgottenSSE-KMS needs key policy + kms:Decrypt/GenerateDataKey.
Website 403 after policyBPA BlockPublicPolicy still true, or wrong object key.
ListBucket * opened by mistakeRemove it; fix 404s with correct keys or CloudFront.

12. Troubleshooting checklist

  1. Confirm region and endpoint hostname for the bucket.
  2. Print get-bucket-policy and get-public-access-block.
  3. Reproduce with a least-privilege lab IAM user.
  4. Binary-search Denies by commenting one Sid at a time in lab.
  5. Read CloudTrail errorCode and additionalEventData.
  6. If KMS involved, test key permissions separately.
  7. From VPC path: route to gateway prefix list + endpoint policy.
  8. 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

  1. Apply this policy on a lab bucket; capture 200 and 403.
  2. Change Resource to a single key; confirm other keys 403.
  3. Explain BPA flags in 60 seconds.

In short

  1. Public site policy = Allow GetObject for *.
  2. Tune BPA; prefer ACL blocks on.
  3. Always test allow and deny.
  4. Production prefers CloudFront OAC (next project).
  5. Never mix PII into a public bucket.

16. Policy design checklist before you save

  1. Write the business rule in one sentence (who can do what from where).
  2. Choose Allow vs Deny-unless consciously; remember explicit Deny wins.
  3. List every Resource ARN shape you need (bucket vs bucket/*).
  4. Name every Condition key exactly (case-sensitive).
  5. Decide BPA settings before put-bucket-policy.
  6. Plan the allow test and the deny test with expected HTTP/CLI codes.
  7. Plan cleanup (versioning, Object Lock, KMS schedules).
  8. 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).

  1. Open IAM → Policy simulator → pick the user/role.
  2. Add action s3:GetObject / PutObject and the object ARN.
  3. If you use resource policies, include the bucket policy text where the tool allows.
  4. Note Allow/Deny; then run the matching CLI from the real path.
  5. 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

  1. Read the error code and the request ID. Save both.
  2. Confirm you are calling the right bucket and region.
  3. Check Block Public Access if the principal is * or a service that looks public.
  4. Decode the policy: is there an explicit Deny that matches? Deny wins.
  5. Check Condition keys: SecureTransport, SourceVpce, SourceIp, PrincipalOrgID, encryption headers.
  6. For KMS: does the key policy allow the caller? Bucket policy alone is not enough.
  7. For CloudFront: is OAC present and the distribution id matching?
  8. For VPC: are you really going through the endpoint (no public IP leak)?
  9. Simulate in IAM Policy Simulator, then re-test with CLI from the real path.
  10. 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

About this scenario

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.

How:
  1. Read the task and decide which AWS service it is about.
  2. Write aws, then the service name, then the operation (the action word).
  3. Add each needed flag (--name) followed by its value from the task.
  4. 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.

How:
  1. Read the task and decide which AWS service it is about.
  2. Write aws, then the service name, then the operation (the action word).
  3. Add each needed flag (--name) followed by its value from the task.
  4. 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.

How:
  1. Write down the numbers the task gives you.
  2. Work it out step by step on paper.
  3. Type only the final answer in the box.
  4. 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.

How:
  1. Write down the numbers the task gives you.
  2. Work it out step by step on paper.
  3. Type only the final answer in the box.
  4. Click Check answer.

Easy Head bucket

Why: Exists check.

What to do: Head bucket mapleleafbooks-web.

How:
  1. Read the task and decide which AWS service it is about.
  2. Write aws, then the service name, then the operation (the action word).
  3. Add each needed flag (--name) followed by its value from the task.
  4. 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.

How:
  1. Read the task and decide which AWS service it is about.
  2. Write aws, then the service name, then the operation (the action word).
  3. Add each needed flag (--name) followed by its value from the task.
  4. Flags can be in any order. Click Check answer.

Medium Get website config

Why: Confirm hosting.

What to do: Get bucket website mapleleafbooks-web.

How:
  1. Read the task and decide which AWS service it is about.
  2. Write aws, then the service name, then the operation (the action word).
  3. Add each needed flag (--name) followed by its value from the task.
  4. 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.

How:
  1. Read the task and decide which AWS service it is about.
  2. Write aws, then the service name, then the operation (the action word).
  3. Add each needed flag (--name) followed by its value from the task.
  4. 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/*.

How:
  1. Start with { "Version": "2012-10-17", "Statement": [ ... ] }.
  2. In each statement write Effect, Action and Resource.
  3. Give only what the task needs (least privilege).
  4. Use double quotes and commas correctly. Click Check answer.

Medium Index suffix JSON

Why: Website index.

What to do: JSON IndexDocument Suffix index.html.

How:
  1. Start from the top-level keys the task names.
  2. Indent child keys with 2 spaces (no tabs).
  3. Fill in each property with the value from the task.
  4. 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.

How:
  1. Write down the numbers the task gives you.
  2. Work it out step by step on paper.
  3. Type only the final answer in the box.
  4. Click Check answer.

Medium AccessDenied HTTP code

Why: Debug.

What to do: HTTP status for S3 AccessDenied (enter 403)?

How:
  1. Write down the numbers the task gives you.
  2. Work it out step by step on paper.
  3. Type only the final answer in the box.
  4. Click Check answer.

Hard Put index object

Why: Upload homepage.

What to do: Put object index.html into mapleleafbooks-web from ./index.html.

How:
  1. Read the task and decide which AWS service it is about.
  2. Write aws, then the service name, then the operation (the action word).
  3. Add each needed flag (--name) followed by its value from the task.
  4. 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/*.

How:
  1. Start with { "Version": "2012-10-17", "Statement": [ ... ] }.
  2. In each statement write Effect, Action and Resource.
  3. Give only what the task needs (least privilege).
  4. Use double quotes and commas correctly. Click Check answer.

Hard Error document JSON

Why: Custom 404.

What to do: JSON ErrorDocument Key error.html.

How:
  1. Start from the top-level keys the task names.
  2. Indent child keys with 2 spaces (no tabs).
  3. Fill in each property with the value from the task.
  4. Click Check answer. Key order does not matter.

Hard Delete bucket policy

Why: Cleanup.

What to do: Delete bucket policy on mapleleafbooks-web.

How:
  1. Read the task and decide which AWS service it is about.
  2. Write aws, then the service name, then the operation (the action word).
  3. Add each needed flag (--name) followed by its value from the task.
  4. 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.

Revision card: Public-read static website. Marketing sites need cheap global reads without an app server. Bucket policy Allow s3:GetObject for Principal * on object ARNs; enable website hosting; open BPA carefully. Create bucket → website config → policy → upload index/error → test URL → cleanup. Priya Nair hosts a Pune clinic brochure on S3 website endpoint with public GetObject only.
Revision card: Public-read static website · Download PNG · Open card page
Revision card: Block Public Access and policy. A perfect public policy still fails if BPA blocks public policies/ACLs. Plan which BPA flags to clear; never open write; prefer policy over ACL. Turn off BlockPublicPolicy, put policy, keep BlockPublicAcls if unused. Sofia Alvarez sees AccessDenied until BPA allows the bucket policy.
Revision card: Block Public Access and policy · Download PNG · Open card page
Comparison: S3 website public GetObject vs CloudFront + OAC. S3 website public GetObject: Cheap S3 website endpoint; Principal * GetObject; open BPA carefully. CloudFront + OAC: CloudFront distribution; OAC; deny direct S3.
Comparison: S3 website public GetObject vs CloudFront + OAC · Download PNG · Open card page
Comparison: Bucket policy public read vs Object ACL public-read. Bucket policy public read: Central JSON; easier audit; works with BPA plan. Object ACL public-read: Per-object ACL; easy to drift and over-share.
Comparison: Bucket policy public read vs Object ACL public-read · Download PNG · Open card page
Interview card: Which Action for public sites?. Usually s3:GetObject on object ARNs for Principal * — never public Put/Delete. Pair with website hosting + BPA plan. index.html readable; put-object from internet denied.
Interview card: Which Action for public sites? · Download PNG · Open card page
Interview card: Why does public policy still 403?. Block Public Access can block public policies even when JSON is valid. Check get-public-access-block before blaming JSON. BlockPublicPolicy true → AccessDenied for anonymous Get.
Interview card: Why does public policy still 403? · Download PNG · Open card page
Interview card: Website vs REST endpoint?. Website endpoint serves IndexDocument/ErrorDocument; REST does not behave the same for index. Use website endpoint for static hosting demos. / shows index.html only on website endpoint.
Interview card: Website vs REST endpoint? · Download PNG · Open card page
Interview card: Bucket ARN vs object ARN?. GetObject needs object ARN (bucket/*); ListBucket needs bucket ARN. Wrong Resource shape → silent misses or over-allow. Resource arn:aws:s3:::site/* for GetObject.
Interview card: Bucket ARN vs object ARN? · Download PNG · Open card page

More cards: Ravi Sir's Cards.