S3 Bucket Policy Project 10: Org-Only Access and Presigned URL Pitfalls
Org-only buckets and two famous pitfalls: (1) aws:PrincipalOrgID locks access to your AWS Organizations id — great until an AWS service principal needs a carve-out. (2) aws:Referer and “secret” query tricks on presigned URLs are not real auth — signatures do not bind Referer. This final project teaches both.
What you'll learn in this chapter
- Why PrincipalOrgID beats listing every account
- What the org-only JSON looks like
- How to test member allow vs external deny
- Why Referer and naive presigned checks fail
- Interview traps
1. Everyday project story
Multi-account land: org admin Tara Krishnan in Chennai and Paris landing-zone architect Camille Dupont share an AMI/scripts bucket for all member accounts — not the whole world.
2. Why PrincipalOrgID?
Why do we need this?
Listing 50 account roots in Principal is fragile. aws:PrincipalOrgID equals o-xxxxxxxxxx allows any principal in the org (still needs IAM Allow).
- One condition for the whole org
- Still least-privilege IAM in each account
- Carve out AWS services that are not “in” the org
- Never use Referer as authorization
3. What is the policy?
What is it?
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowOrgPrincipals",
"Effect": "Allow",
"Principal": "*",
"Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": [
"arn:aws:s3:::rb-org-shared-ap-south-1",
"arn:aws:s3:::rb-org-shared-ap-south-1/*"
],
"Condition": {
"StringEquals": { "aws:PrincipalOrgID": "o-xxxxxxxxxx" }
}
},
{
"Sid": "DenyOutsideOrg",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::rb-org-shared-ap-south-1",
"arn:aws:s3:::rb-org-shared-ap-south-1/*"
],
"Condition": {
"StringNotEquals": { "aws:PrincipalOrgID": "o-xxxxxxxxxx" },
"Bool": { "aws:PrincipalIsAWSService": "false" }
}
}
]
}
Line-by-line
- Allow Get/List when PrincipalOrgID matches.
- Deny s3:* when OrgID not match AND caller is not an AWS service.
- PrincipalIsAWSService helps some AWS deliveries survive.
- Replace o-xxxxxxxxxx with your org id.
- Member account IAM must still Allow the actions.
- External account access key → AccessDenied.
4. Presigned URL and Referer pitfall
Not real security
aws:Referer checks an optional browser header. Attackers can send any Referer. Presigned URLs authorize with SigV4 — they do not bind Referer. Do not ship “hotlink protection” as auth.
# BAD idea (do not rely on this)
# Condition StringLike aws:Referer https://myportal.example.com/*
# A curl -H 'Referer: https://myportal.example.com/' bypasses the "protection".
# Presigned URL — still valid if leaked, until expiry, regardless of Referer
aws s3 presign s3://rb-org-shared-ap-south-1/guide.pdf --expires-in 60
# https://rb-org-shared-ap-south-1.s3.amazonaws.com/guide.pdf?X-Amz-Algorithm=...
5. CLI with output
How to do it (step by step)
ORG=o-xxxxxxxxxx
aws s3api put-bucket-policy --bucket rb-org-shared-ap-south-1 --policy file://org-only.json
# ALLOW — member account role
aws s3 ls s3://rb-org-shared-ap-south-1/ --profile member
# guide.pdf
# DENY — external account
aws s3 ls s3://rb-org-shared-ap-south-1/ --profile external
# AccessDenied
6. Test allow and deny
Allow: org member GetObject. Deny: external account. Separately: show Referer header forge does not equal security.
7. Multi-industry
Landing zones (Chennai — Tara Krishnan), EU retail org (Paris — Camille Dupont), hospital multi-account (Manipal — Dr. Leena Shetty), gaming studios org (Montreal — Noah Berger).
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: org-only + referer myth
Do this in ap-south-1 (Mumbai) unless your org forbids it. Set a budget alert first.
How to do it (step by step)
- Put PrincipalOrgID policy with your org id (or simulate with two accounts).
- Member profile lists objects; external gets AccessDenied.
- Create a presigned URL; curl it with a fake Referer — still works until expiry.
- Document why Referer is not auth in your lab notes.
Cleanup (stop billing)
- Delete policy; delete bucket objects/bucket.
- Rotate any lab keys used.
10. 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. |
| Org Deny breaks CloudTrail/ALB delivery | Service principal needs carve-out (PrincipalIsAWSService or explicit Allow). |
| Relied on Referer for private content | Leaked URL + forged Referer still works — use OAC/authZ. |
11. 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.
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. Where is org id?
Organizations console or describe-organization.
Q. Principal * with Org condition safe?
Common pattern; Deny outside hardens further.
Q. Presigned + Org policy?
Presign uses signer identity; external signer fails Org Deny.
Q. Referer ever useful?
Weak hotlink deterrent only — never secrets.
13. Interview Q&A
Q. Lock bucket to AWS Organization how?
Condition aws:PrincipalOrgID equals o-xxxx.
Q. Why list accounts is worse?
Churn; misses new accounts.
Q. Service delivery fails after Org Deny?
Carve out AWS services.
Q. Is aws:Referer authorization?
No — spoofable header.
Q. Presigned URL + Referer condition?
Signature does not bind Referer; do not rely on it.
Q. Prove org policy?
Member allow; external AccessDenied.
14. Tips in English, Marathi and Hindi
Ravindra Bagale's Tip (EN)
PrincipalOrgID for shared org resources; never confuse it with “Referer security theater”.
Ravindra Bagale's Tip (MR)
Org shared = PrincipalOrgID; Referer security theater नव्हे.
Tip (HI)
Org shared संसाधन = PrincipalOrgID; Referer को सुरक्षा न समझें।
Try at home
- Member allow + external deny outputs.
- Presign + fake Referer curl demo in notes.
- List services that need carve-outs in your org.
In short
- aws:PrincipalOrgID scopes to the org.
- Still need IAM Allows in members.
- Carve out AWS services when Deny-outside.
- Referer is not authorization.
- Presigned URLs follow signer + expiry — not Referer.
15. 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.
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).
- 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.
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
- 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.
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
Thane Housing Society is a real estate business in Thane. Mahesh Patwardhan 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 org-only policy
Why: Org fence.
What to do: Get bucket policy thanehousing-org.
- 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 List org bucket
Why: In-org.
What to do: List objects thanehousing-org.
- 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 Outside org denied — 1
Why: Fence.
What to do: Enter 1 if principals outside the org should AccessDenied.
- 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 Presigned URL is temporary creds — 1
Why: Secret.
What to do: Enter 1 if a presigned URL is like sharing temporary access.
- 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 org bucket
Why: Exists.
What to do: Head bucket thanehousing-org.
- 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 BPA org
Why: BPA.
What to do: Get public access block thanehousing-org.
- 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 object in-org
Why: Allow.
What to do: Get object report.pdf from thanehousing-org.
- 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 object in-org
Why: Allow write.
What to do: Put object report.pdf into thanehousing-org from ./report.pdf.
- 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 insecure with Org bucket
Why: TLS.
What to do: Deny s3:* when SecureTransport false on thanehousing-org objects.
- 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 PrincipalOrgID JSON
Why: Condition.
What to do: JSON aws:PrincipalOrgID o-xxxxxx.
- 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 Referer is weak primary auth — 1
Why: Pitfall.
What to do: Enter 1 if aws:Referer is weak as primary authorization.
- 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 Presign still hits bucket policy — 1
Why: Conditions.
What to do: Enter 1 if bucket policy conditions still apply to presigned requests.
- 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 Get org encryption
Why: SSE.
What to do: Get bucket encryption thanehousing-org.
- 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 Deny unless PrincipalOrgID
Why: Org only.
What to do: Deny s3:* when aws:PrincipalOrgID is not o-xxxxxx on thanehousing-org ARNs.
- 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 Referer pitfall JSON
Why: Weak auth.
What to do: JSON referer_is_not_auth true.
- 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 org policy
Why: Cleanup.
What to do: Delete bucket policy thanehousing-org.
- 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.