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

S3 Bucket Policy Project 2: Allow Only CloudFront via OAC

Vikram Joshi at a Mumbai OTT startup keeps master videos in S3. The CDN is CloudFront. If anyone hits the S3 URL directly, signed cookies and geo blocks are bypassed. Origin Access Control (OAC) plus a bucket policy that Allow GetObject only for the CloudFront service principal fixes that.

What you'll learn in this chapter

  • Why private origins beat public buckets for video/CDN
  • What OAC and the CloudFront service principal policy look like
  • How to wire distribution ARN SourceArn and test direct deny
  • OAI migration notes and AccessDenied debug
  • Interview answers

1. Everyday project story

Singapore edtech lead Mei Lin and Hyderabad course platform owner Suresh Reddy both stream course videos. Viewers must use CloudFront URLs only — never the raw S3 REST URL.

Builds on the public-website project: production usually flips to private bucket + OAC instead of Principal *.

2. Why OAC-only access?

Why do we need this?

Public GetObject ignores CloudFront signed URLs, WAF, and geo restrictions. OAC makes CloudFront the only reader.

  • Keep bucket private (BPA fully on)
  • CloudFront signs origin requests with OAC
  • Bucket policy Allow only cloudfront.amazonaws.com with SourceArn
  • Direct S3 URL returns 403

3. What is the policy?

What is it?

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowCloudFrontServicePrincipalRead",
      "Effect": "Allow",
      "Principal": { "Service": "cloudfront.amazonaws.com" },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::rb-ott-origin-ap-south-1/*",
      "Condition": {
        "StringEquals": {
          "AWS:SourceArn": "arn:aws:cloudfront::111122223333:distribution/EDFDVBD6EXAMPLE"
        }
      }
    }
  ]
}

Line-by-line

  1. Principal Service cloudfront.amazonaws.com — not an IAM user.
  2. Action GetObject — origin fetch only.
  3. Resource objects in the origin bucket.
  4. Condition AWS:SourceArn — only YOUR distribution.
  5. No Principal * — anonymous direct access stays denied.
  6. Replace distribution id before apply.
Viewer → CloudFront → OAC SigV4 → S3 200Direct S3 URL
403 AccessDenied

4. How to do it (CLI with output)

How to do it (step by step)

# Private bucket
aws s3api create-bucket --bucket rb-ott-origin-ap-south-1 --region ap-south-1 \
  --create-bucket-configuration LocationConstraint=ap-south-1
aws s3api put-public-access-block --bucket rb-ott-origin-ap-south-1 --public-access-block-configuration \
  BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true

# Create OAC, attach to distribution origin, note Distribution ARN, then:
aws s3api put-bucket-policy --bucket rb-ott-origin-ap-south-1 --policy file://oac.json
# (empty success)

echo demo-video-bytes > demo.mp4
aws s3 cp demo.mp4 s3://rb-ott-origin-ap-south-1/videos/demo.mp4

5. Test: allowed and denied

# DENY — direct S3 (no CF)
curl -s -o /dev/null -w "%{http_code}\n" \
  https://rb-ott-origin-ap-south-1.s3.ap-south-1.amazonaws.com/videos/demo.mp4
# 403

# ALLOW — via CloudFront domain (after OAC attached)
curl -s -o /dev/null -w "%{http_code}\n" \
  https://d111111abcdef8.cloudfront.net/videos/demo.mp4
# 200

6. OAC vs legacy OAI

TopicOACOAI
AuthSigV4 service principalSpecial canonical user
SSE-KMSSupported cleanlyPainful / limited
AWS advicePrefer OACMigrate off

7. Multi-industry

OTT (Mumbai — Vikram Joshi), edtech video (Hyderabad — Suresh Reddy), news archives (London — Emily Carter), hospital training films (Kochi — Dr. Neha Nair).

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. Lab: private origin + OAC policy

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 private bucket; BPA all true.
  2. Create CloudFront distribution with S3 origin + OAC.
  3. Put SourceArn-scoped bucket policy; upload a test object.
  4. curl direct S3 → 403; curl CF domain → 200.

Cleanup (stop billing)

  1. Disable distribution; delete OAC; empty and delete bucket.
  2. Remove lab DNS records if any.

10. 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.
OAC 403 with policy presentWrong SourceArn / still using OAI identity / KMS deny.
Left Principal * from old website policyRemove *; OAC Allow only.

11. 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.

12. 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. OAC vs OAI?

OAC is current SigV4; migrate off OAI.

Q. Can multiple distributions share one bucket?

Yes — list each SourceArn or use StringLike with care.

Q. Does OAC need public BPA off?

No — keep BPA fully on.

Q. SSE-KMS with OAC?

Grant CloudFront (via OAC) permission on the key too.

13. Interview Q&A

Q. Why not Principal * with CloudFront?

Bypasses CF controls; anyone with the URL reads S3.

Q. What Condition locks OAC?

AWS:SourceArn of the distribution.

Q. How do you prove OAC works?

Direct S3 403; CF domain 200.

Q. OAI still OK for new work?

Prefer OAC for new; plan migration.

Q. Where does WAF sit?

On CloudFront — another reason to block direct S3.

Q. Cross-account origin?

Possible with careful SourceArn/account; test thoroughly.

14. Tips in English, Marathi and Hindi

Ravindra Bagale's Tip (EN)

Never leave a public GetObject on an origin that CloudFront protects — you just built a back door.

Ravindra Bagale's Tip (MR)

CloudFront protect केलेल्या origin वर public GetObject = back door.

Tip (HI)

CloudFront origin पर Principal * न छोड़ें — यह back door है।

Try at home

  1. Lab: 403 direct vs 200 via CF; save outputs.
  2. Intentionally break SourceArn; watch 403 on CF too; fix.
  3. 90-second interview pitch of OAC.

In short

  1. Private bucket + OAC + SourceArn Allow.
  2. Direct S3 must 403.
  3. Prefer OAC over OAI.
  4. Keep BPA fully enabled.
  5. Grant KMS if objects are SSE-KMS.

15. 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.

16. 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.

17. 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

18. 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.

19. 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.

20. 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

Vidarbha Cotton Mills is a textiles business in Nagpur. Ganesh Wankhede looks after its IT and cloud work. They count money in INR and run their AWS resources in the ap-south-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 Get OAC bucket policy

Why: Confirm OAC.

What to do: Get bucket policy vidarbhacotton-origin.

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 List origin objects

Why: Inventory.

What to do: List objects s3://vidarbhacotton-origin.

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 Prefer OAC over OAI — 1

Why: Modern.

What to do: Enter 1 if OAC is preferred over legacy OAI.

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 Direct S3 should deny — 1

Why: CF only.

What to do: Enter 1 if direct S3 URL should AccessDenied with OAC-only 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 Get BPA on origin

Why: Public block.

What to do: Get public access block vidarbhacotton-origin.

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 Head origin bucket

Why: Exists.

What to do: Head bucket vidarbhacotton-origin.

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 object from origin

Why: Direct test.

What to do: Get object app.js from vidarbhacotton-origin.

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 asset to origin

Why: Publish asset.

What to do: Put object assets/app.js into vidarbhacotton-origin from ./app.js.

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 anonymous GetObject

Why: Block direct.

What to do: Deny s3:GetObject for Principal * on arn:aws:s3:::vidarbhacotton-origin/*.

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 SourceArn condition JSON

Why: Tie distribution.

What to do: JSON AWS:SourceArn arn:aws:cloudfront::123456789012:distribution/EDUMMY123.

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 Avoid Principal * with OAC — 1

Why: Control.

What to do: Enter 1 if Principal * GetObject bypasses CloudFront control.

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 Invalidation after update — 1

Why: Cache.

What to do: Enter 1 if you may need CloudFront invalidation after object update.

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 Get encryption config

Why: SSE check.

What to do: Get bucket encryption vidarbhacotton-origin.

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 CloudFront OAC GetObject

Why: Only CF.

What to do: Allow s3:GetObject for cloudfront.amazonaws.com on arn:aws:s3:::vidarbhacotton-origin/*.

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 Service principal JSON

Why: CF principal.

What to do: JSON Service cloudfront.amazonaws.com.

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 origin policy

Why: Cleanup.

What to do: Delete bucket policy vidarbhacotton-origin.

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: CloudFront OAC only. Direct S3 URLs bypass WAF, HTTPS custom domain, and cache controls. Origin Access Control lets only your distribution read the origin via cloudfront.amazonaws.com + SourceArn. Create OAC → attach to origin → bucket policy Allow GetObject with SourceArn → deny anonymous. Kenji Watanabe serves course videos only through CloudFront, never the S3 URL.
Revision card: CloudFront OAC only · Download PNG · Open card page
Revision card: Deny direct S3 access. If Principal * GetObject remains, users skip CloudFront. Keep Allow only for CloudFront; test curl to S3 REST URL expects AccessDenied. Compare CF URL 200 vs S3 URL 403 after policy. Fatima Khan confirms news images 403 on raw S3 after OAC policy.
Revision card: Deny direct S3 access · Download PNG · Open card page
Comparison: Origin Access Control vs Origin Access Identity. Origin Access Control: SigV4; works with SSE-KMS; modern default. Origin Access Identity: Legacy canonical user; limited features.
Comparison: Origin Access Control vs Origin Access Identity · Download PNG · Open card page
Comparison: CloudFront URL vs Direct S3 REST URL. CloudFront URL: Custom domain, WAF, cache, HTTPS at edge. Direct S3 REST URL: Bypasses edge controls if bucket is open.
Comparison: CloudFront URL vs Direct S3 REST URL · Download PNG · Open card page
Interview card: What is CloudFront OAC?. Origin Access Control — CloudFront signs origin requests; bucket allows cloudfront.amazonaws.com with SourceArn. Prefer OAC over legacy OAI. Only distro E123 can GetObject.
Interview card: What is CloudFront OAC? · Download PNG · Open card page
Interview card: Why not Principal * with OAC?. Principal * GetObject lets clients skip CloudFront controls (WAF, auth, cache). Allow only the CloudFront service principal. Direct S3 URL 403 after correct policy.
Interview card: Why not Principal * with OAC? · Download PNG · Open card page
Interview card: How do you test OAC deny?. Fetch via CloudFront URL (allow) and via S3 REST URL (deny). Watch for cached CF 200 after policy change — invalidate if needed. curl S3 → 403; curl CF → 200.
Interview card: How do you test OAC deny? · Download PNG · Open card page
Interview card: Which Condition pins the distribution?. AWS:SourceArn of the CloudFront distribution ARN. Typo in distribution id → deny from CF too. arn:aws:cloudfront::acct:distribution/Exxx.
Interview card: Which Condition pins the distribution? · Download PNG · Open card page

More cards: Ravi Sir's Cards.