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

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

  1. Allow Get/List when PrincipalOrgID matches.
  2. Deny s3:* when OrgID not match AND caller is not an AWS service.
  3. PrincipalIsAWSService helps some AWS deliveries survive.
  4. Replace o-xxxxxxxxxx with your org id.
  5. Member account IAM must still Allow the actions.
  6. 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)

  1. Put PrincipalOrgID policy with your org id (or simulate with two accounts).
  2. Member profile lists objects; external gets AccessDenied.
  3. Create a presigned URL; curl it with a fake Referer — still works until expiry.
  4. Document why Referer is not auth in your lab notes.

Cleanup (stop billing)

  1. Delete policy; delete bucket objects/bucket.
  2. 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 / 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.
Org Deny breaks CloudTrail/ALB deliveryService principal needs carve-out (PrincipalIsAWSService or explicit Allow).
Relied on Referer for private contentLeaked URL + forged Referer still works — use OAC/authZ.

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

  1. Member allow + external deny outputs.
  2. Presign + fake Referer curl demo in notes.
  3. List services that need carve-outs in your org.

In short

  1. aws:PrincipalOrgID scopes to the org.
  2. Still need IAM Allows in members.
  3. Carve out AWS services when Deny-outside.
  4. Referer is not authorization.
  5. Presigned URLs follow signer + expiry — not Referer.

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

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.

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 org bucket

Why: In-org.

What to do: List objects thanehousing-org.

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 Outside org denied — 1

Why: Fence.

What to do: Enter 1 if principals outside the org should AccessDenied.

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 Presigned URL is temporary creds — 1

Why: Secret.

What to do: Enter 1 if a presigned URL is like sharing temporary access.

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 org bucket

Why: Exists.

What to do: Head bucket thanehousing-org.

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 BPA org

Why: BPA.

What to do: Get public access block thanehousing-org.

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 in-org

Why: Allow.

What to do: Get object report.pdf from thanehousing-org.

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 object in-org

Why: Allow write.

What to do: Put object report.pdf into thanehousing-org from ./report.pdf.

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 insecure with Org bucket

Why: TLS.

What to do: Deny s3:* when SecureTransport false on thanehousing-org objects.

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 PrincipalOrgID JSON

Why: Condition.

What to do: JSON aws:PrincipalOrgID o-xxxxxx.

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 Referer is weak primary auth — 1

Why: Pitfall.

What to do: Enter 1 if aws:Referer is weak as primary authorization.

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 Presign still hits bucket policy — 1

Why: Conditions.

What to do: Enter 1 if bucket policy conditions still apply to presigned requests.

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 org encryption

Why: SSE.

What to do: Get bucket encryption thanehousing-org.

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 Deny unless PrincipalOrgID

Why: Org only.

What to do: Deny s3:* when aws:PrincipalOrgID is not o-xxxxxx on thanehousing-org ARNs.

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 Referer pitfall JSON

Why: Weak auth.

What to do: JSON referer_is_not_auth true.

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

Why: Cleanup.

What to do: Delete bucket policy thanehousing-org.

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: PrincipalOrgID fence. Partner accounts outside the org should not read internal datasets. Deny s3:* unless aws:PrincipalOrgID equals your o-….  Apply Deny-unless → in-org role allow → external role deny. Kenji Watanabe shared research bucket accepts only o-xxxxxx principals.
Revision card: PrincipalOrgID fence · Download PNG · Open card page
Revision card: Presigned URL and Referer pitfalls. A presigned URL is temporary access — treat it like a secret. Bucket conditions still apply; aws:Referer is spoofable and weak as primary auth. Short expiry; HTTPS; prefer OrgID/OAC over Referer allowlists. Fatima Khan shows a stolen http presign failing SecureTransport Deny.
Revision card: Presigned URL and Referer pitfalls · Download PNG · Open card page
Comparison: PrincipalOrgID fence vs Explicit account Allow list. PrincipalOrgID fence: Any principal in o-…; scales with new accounts. Explicit account Allow list: Enumerate account ids; precise but brittle.
Comparison: PrincipalOrgID fence vs Explicit account Allow list · Download PNG · Open card page
Comparison: Presigned URL vs aws:Referer allowlist. Presigned URL: SigV4 temporary access; treat as secret; short TTL. aws:Referer allowlist: Spoofable header; weak primary auth.
Comparison: Presigned URL vs aws:Referer allowlist · Download PNG · Open card page
Interview card: What is PrincipalOrgID?. Condition key matching the AWS Organizations id (o-…) of the caller’s account. Deny-unless keeps outsiders out. External account role → AccessDenied.
Interview card: What is PrincipalOrgID? · Download PNG · Open card page
Interview card: Risk of sharing presigned URLs?. Anyone with the URL can perform the signed action until expiry — like lending credentials. Short TTL; HTTPS; least Action; still subject to bucket policy. Leaked URL downloads report.pdf until expiry.
Interview card: Risk of sharing presigned URLs? · Download PNG · Open card page
Interview card: Why is Referer a pitfall?. Referer can be omitted or spoofed; it is not strong authorization. Do not use as primary auth for sensitive objects. curl -H Referer:… can fake hotlink checks.
Interview card: Why is Referer a pitfall? · Download PNG · Open card page
Interview card: Do conditions bind to presigns?. Yes — SecureTransport, OrgID, IP, etc. still evaluate on the request. Design presigns knowing Deny fences remain. http presign + SecureTransport Deny → fail.
Interview card: Do conditions bind to presigns? · Download PNG · Open card page

More cards: Ravi Sir's Cards.