S3 Bucket Policy Project 3: Restrict Access to a VPC Endpoint
Fatima Khan runs payroll files in a private Mumbai VPC. Security forbids S3 over the public internet path. A gateway VPC endpoint plus a bucket policy Condition on aws:SourceVpce keeps objects reachable only from that VPC path.
What you'll learn in this chapter
- Why sensitive S3 should prefer VPC endpoint paths
- What aws:SourceVpce Deny-unless means line by line
- How to create endpoint, apply policy, test inside vs outside
- Endpoint policy AND bucket policy
- Interview answers
1. Everyday project story
Bengaluru bank engineer Arjun Mehta and Toronto fintech lead Sophie Martin store KYC scans in S3. Only app subnets via vpce-xxxxxxxx should read them.
2. Why VPC-endpoint-only?
Why do we need this?
Even private EC2 can reach S3 via public AWS endpoints. A restrictive bucket policy forces the gateway endpoint path and blocks stolen keys used from the internet.
- Gateway endpoint for S3 (no interface ENI hourly)
- Route table prefix list to S3
- Bucket Deny unless aws:SourceVpce matches
- Optional endpoint policy least privilege
3. What is the policy?
What is it?
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyUnlessFromMyVpce",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::rb-payroll-ap-south-1",
"arn:aws:s3:::rb-payroll-ap-south-1/*"
],
"Condition": {
"StringNotEquals": {
"aws:SourceVpce": "vpce-0a1b2c3d4e5f67890"
}
}
}
]
}
Line-by-line
- Effect Deny + StringNotEquals — deny if not from endpoint.
- Principal * — all callers including your IAM.
- Action s3:* — broad lock; IAM still must Allow needed actions.
- Resource bucket and bucket/* — List + object APIs.
- aws:SourceVpce = endpoint id vpce-...
- Deny does not grant; IAM Allow still required.
4. CLI with output
How to do it (step by step)
# Gateway endpoint
aws ec2 create-vpc-endpoint --vpc-id vpc-0123456789abcdef0 \
--service-name com.amazonaws.ap-south-1.s3 \
--route-table-ids rtb-aaa rtb-bbb --vpc-endpoint-type Gateway
# { "VpcEndpoint": { "VpcEndpointId": "vpce-0a1b2c3d4e5f67890", ... } }
aws s3api put-bucket-policy --bucket rb-payroll-ap-south-1 --policy file://vpce.json
5. Test allow and deny
# ALLOW — EC2 in VPC with endpoint route
aws s3 cp s3://rb-payroll-ap-south-1/payslip.pdf - --region ap-south-1 | wc -c
# >0
# DENY — laptop on internet, same IAM key
aws s3 cp s3://rb-payroll-ap-south-1/payslip.pdf /tmp/x --region ap-south-1
# An error occurred (AccessDenied) when calling the GetObject operation: Access Denied
Both policies matter
VPC endpoint policy and bucket policy are both evaluated. An empty/deny endpoint policy blocks even if the bucket allows.
6. Multi-industry
Payroll (Mumbai — Fatima Khan), banking KYC (Bengaluru — Arjun Mehta), hospital records (Toronto — Sophie Martin), govt file share (Delhi — Kabir Singh).
7. 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
8. Lab: SourceVpce lock
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 lab bucket + gateway endpoint on a private route table.
- Apply DenyUnlessFromMyVpce policy.
- From private EC2: GetObject succeeds.
- From internet laptop with same keys: AccessDenied.
Cleanup (stop billing)
- Delete bucket policy; delete endpoint; delete bucket.
- Remove lab routes if dedicated.
9. 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. |
| Works nowhere after Deny | Wrong vpce id; or endpoint not on the RT used by the ENI. |
| Endpoint policy empty Deny | Add Allow in endpoint policy for your principals/actions. |
10. 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.
- Verify route to S3 prefix list on the instance.
11. 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. Gateway vs Interface for S3?
Gateway is the usual cheap path for S3/DynamoDB.
Q. Same as SourceIp?
No — endpoint id vs client IP.
Q. AWS log services blocked?
May need carve-outs for CloudTrail/ALB.
Q. Does Deny replace IAM Allow?
No — Deny only blocks; IAM must still Allow.
12. Interview Q&A
Q. Force S3 via VPC endpoint how?
Bucket Condition aws:SourceVpce (often Deny if not equal) + endpoint on route tables.
Q. Stolen key from internet?
Should AccessDenied if SourceVpce required.
Q. Endpoint policy denies?
Request fails — both must allow.
Q. Find vpce id?
ec2 describe-vpc-endpoints.
Q. Interface endpoint when?
Special private DNS/hybrid cases; know cost.
Q. Deny-unless vs Allow-only?
Deny-unless stops other Allows from bypassing; design consciously.
13. Tips in English, Marathi and Hindi
Ravindra Bagale's Tip (EN)
Pair SourceVpce with private subnets when compliance demands no public S3 path.
Ravindra Bagale's Tip (MR)
Compliance: SourceVpce + private subnet.
Tip (HI)
Compliance में SourceVpce + private subnet रखें।
Try at home
- Inside-VPC allow and laptop deny; save errors.
- Typo the vpce id; confirm total deny; fix.
- Draw RT → prefix list → S3.
In short
- aws:SourceVpce locks to an endpoint.
- Deny-unless is common.
- Endpoint policy AND bucket policy.
- Test inside VPC and from internet.
- Carve out log services if needed.
14. 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.
15. 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.
16. 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
17. 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.
18. 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.
19. 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
London Legal LLP is a law business in London. Oliver Hughes looks after its IT and cloud work. They count money in GBP and run their AWS resources in the eu-west-2 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 VPCe bucket policy
Why: Read fence.
What to do: Get bucket policy londonlegal-private.
- 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 private bucket
Why: Via endpoint.
What to do: List objects londonlegal-private.
- 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 VPC denied — 1
Why: Fence.
What to do: Enter 1 if internet path should AccessDenied with VPCe-only 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 Route table needs prefix list — 1
Why: Gateway EP.
What to do: Enter 1 if S3 gateway endpoint needs route table association.
- 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 private bucket
Why: Exists.
What to do: Head bucket londonlegal-private.
- 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 location
Why: Region.
What to do: Get bucket location londonlegal-private.
- 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 Describe VPC endpoints
Why: Find vpce id.
What to do: Describe vpc endpoints.
- 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 private
Why: In-VPC read.
What to do: Get object data.csv from londonlegal-private.
- 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 Allow GetObject account root path
Why: Baseline allow.
What to do: Allow s3:GetObject for arn:aws:iam::114543297314:root on arn:aws:s3:::londonlegal-private/*.
- 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 SourceVpce JSON
Why: Condition value.
What to do: JSON aws:SourceVpce vpce-0abc123.
- 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 Endpoint policy also matters — 1
Why: Layers.
What to do: Enter 1 if VPC endpoint policy is evaluated too.
- 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 SourceVpc ≠ SourceVpce — 1
Why: Different.
What to do: Enter 1 if aws:SourceVpc and aws:SourceVpce are different keys.
- 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 Deny unless SourceVpce
Why: Endpoint only.
What to do: Deny s3:* when aws:SourceVpce is not vpce-0abc123 on arn:aws:s3:::londonlegal-private and 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.
Hard Resources bucket+objects JSON
Why: Both ARNs.
What to do: JSON Resource0 arn:aws:s3:::demo-private Resource1 arn:aws:s3:::demo-private/*.
- 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 Put object private
Why: In-VPC write.
What to do: Put object data.csv into londonlegal-private from ./data.csv.
- 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 Delete private policy
Why: Cleanup.
What to do: Delete bucket policy londonlegal-private.
- 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.