S3 Bucket Policy Project 7: Cross-Account ALB and CloudTrail Logs Bucket
Central logging account owned by Sneha Rao in Hyderabad must accept ALB access logs and CloudTrail from workload accounts. That needs a bucket policy with ELB and CloudTrail service principals (and sometimes ACL bucket-owner-full-control on Put).
What you'll learn in this chapter
- Why log buckets are cross-account by design
- What ELB and CloudTrail principals look like in JSON
- How to apply policy, enable logging, see objects appear
- ACL vs policy for log delivery
- Interview answers
1. Everyday project story
Mumbai ecommerce platform (CTO: Harshad Joshi) and London retail (SRE: Claire Thompson) both ship ALB logs into a security account bucket owned by Sneha Rao.
2. Why cross-account log bucket?
Why do we need this?
Workloads must not delete their own audit trail. A locked logging account owns the bucket; services in other accounts need explicit resource-policy Allow.
- Separate security/logging account
- Service principals for log delivery
- Optional SourceAccount conditions
- Block human deletes with extra Deny (project 9)
3. What is the policy?
What is it?
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowALBLogDelivery",
"Effect": "Allow",
"Principal": { "Service": "logdelivery.elasticloadbalancing.amazonaws.com" },
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::rb-central-logs-ap-south-1/alb/AWSLogs/444455556666/*",
"Condition": {
"StringEquals": { "aws:SourceAccount": "444455556666" }
}
},
{
"Sid": "AllowCloudTrailPut",
"Effect": "Allow",
"Principal": { "Service": "cloudtrail.amazonaws.com" },
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::rb-central-logs-ap-south-1/cloudtrail/AWSLogs/444455556666/*",
"Condition": {
"StringEquals": {
"aws:SourceAccount": "444455556666",
"s3:x-amz-acl": "bucket-owner-full-control"
}
}
},
{
"Sid": "AllowCloudTrailAclCheck",
"Effect": "Allow",
"Principal": { "Service": "cloudtrail.amazonaws.com" },
"Action": "s3:GetBucketAcl",
"Resource": "arn:aws:s3:::rb-central-logs-ap-south-1"
}
]
}
Line-by-line
- ALB principal logdelivery.elasticloadbalancing.amazonaws.com.
- Resource prefix includes AWSLogs/SOURCE_ACCOUNT/.
- aws:SourceAccount condition — only that workload account.
- CloudTrail PutObject + bucket-owner-full-control ACL condition (classic).
- GetBucketAcl for CloudTrail validation.
- Replace account ids and prefixes before apply.
4. CLI with output
How to do it (step by step)
aws s3api put-bucket-policy --bucket rb-central-logs-ap-south-1 --policy file://logs.json
# (empty success)
# On workload account: enable ALB access logs to s3://rb-central-logs-ap-south-1/alb
# Enable CloudTrail to s3://rb-central-logs-ap-south-1/cloudtrail
aws s3 ls s3://rb-central-logs-ap-south-1/alb/AWSLogs/444455556666/ --recursive | head
# AWSLogs/444455556666/elasticloadbalancing/ap-south-1/...
5. Test allow and deny
# ALLOW — after enabling ALB logs, objects appear under alb/AWSLogs/...
aws s3 ls s3://rb-central-logs-ap-south-1/alb/AWSLogs/444455556666/ | head
# PRE elasticloadbalancing/
# DENY — random IAM user from another account without grant
aws s3 cp s3://rb-central-logs-ap-south-1/alb/secret.log - --profile other
# AccessDenied
6. ACL vs policy
Modern ALB delivery often works with policy alone; CloudTrail historically expects bucket-owner-full-control on Put. Prefer Bucket owner enforced when the service supports it — read current AWS docs per service.
7. Multi-industry
Ecommerce ALB (Mumbai — Harshad Joshi), retail (London — Claire Thompson), bank org trail (Hyderabad — Sneha Rao), hospital API logs (Sydney — Liam O’Connor).
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: cross-account log bucket
Do this in ap-south-1 (Mumbai) unless your org forbids it. Set a budget alert first.
How to do it (step by step)
- In logging account create bucket + policy with your workload account id.
- From workload account enable ALB access logs to the prefix.
- Wait and ls for AWSLogs objects (ALLOW proof).
- Try GetObject as unrelated user → AccessDenied.
Cleanup (stop billing)
- Disable logging on ALB/Trail; empty prefixes; delete policy/bucket.
- Remove lab trail if created.
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. |
| Logs never appear | Wrong prefix/account id; missing GetBucketAcl; BPA issues. |
| AccessDenied on enable | Policy Principal/Service spelling or Resource path. |
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. Why SourceAccount?
Stops other accounts from writing into your log prefixes.
Q. Same for Config/ELB?
Each service has its own principal — check docs.
Q. SSE-KMS on log bucket?
Key policy must allow the service — extra care.
Q. Org trail vs account trail?
Org trail centralizes; bucket still needs policy.
13. Interview Q&A
Q. Who writes ALB logs?
ELB log delivery service principal into your bucket.
Q. CloudTrail ACL condition why?
Classic delivery uses bucket-owner-full-control so owner can read.
Q. How prove allow?
Objects land under AWSLogs/account/.
Q. How prove deny?
Unrelated principal AccessDenied on Get/Put.
Q. Why separate logging account?
Attackers in workload account cannot easily wipe logs.
Q. Prefix per account?
Yes — AWSLogs/ACCOUNT_ID/ convention.
14. Tips in English, Marathi and Hindi
Ravindra Bagale's Tip (EN)
Never give workload admins s3:DeleteObject on the central log bucket.
Ravindra Bagale's Tip (MR)
Workload admin ला central log DeleteObject देऊ नका.
Tip (HI)
Workload admin को central log पर DeleteObject न दें।
Try at home
- Enable one ALB log path; screenshot ls output.
- Remove SourceAccount; explain risk; put it back.
- Sketch org logging landing zone on paper.
In short
- Log buckets use service principals + SourceAccount.
- Prefixes follow AWSLogs/account.
- CloudTrail may need ACL condition.
- Prove with objects appearing + stranger denied.
- Keep humans least-privilege on log buckets.
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
Nairobi Ride is a transport business in Nairobi. Wanjiru Kamau looks after its IT and cloud work. They count money in KES and run their AWS resources in the af-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 logs bucket policy
Why: Delivery allow.
What to do: Get bucket policy nairobiride-logs.
- 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 log prefixes
Why: AWSLogs.
What to do: List objects nairobiride-logs.
- 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 Service principal needed — 1
Why: Delivery.
What to do: Enter 1 if CloudTrail needs an allow in the destination bucket 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 Cross-account is different owners — 1
Why: Accounts.
What to do: Enter 1 if cross-account means writer and bucket can be different accounts.
- 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 Get logs versioning
Why: State.
What to do: Get bucket versioning nairobiride-logs.
- 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 Head logs bucket
Why: Exists.
What to do: Head bucket nairobiride-logs.
- 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 logs
Why: BPA.
What to do: Get public access block nairobiride-logs.
- 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 bucket ACL
Why: Owner check.
What to do: Get bucket acl nairobiride-logs.
- 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 logs location
Why: Region.
What to do: Get bucket location nairobiride-logs.
- 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 ELB log delivery GetBucketAcl
Why: ALB logs.
What to do: Allow s3:GetBucketAcl for elb service on arn:aws:s3:::nairobiride-logs.
- 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 bucket-owner-full-control JSON
Why: Ownership.
What to do: JSON s3:x-amz-acl bucket-owner-full-control.
- 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 Unrelated Put should deny — 1
Why: Least.
What to do: Enter 1 if unrelated IAM users should not PutObject to the log bucket.
- 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 Protect deletes on log bucket — 1
Why: Integrity.
What to do: Enter 1 if denying delete helps protect audit logs.
- 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 Allow CloudTrail PutObject
Why: Trail delivery.
What to do: Allow s3:PutObject for cloudtrail.amazonaws.com on arn:aws:s3:::nairobiride-logs/AWSLogs/104591109902/*.
- 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 AWSLogs prefix JSON
Why: Organize.
What to do: JSON prefix AWSLogs/123456789012/.
- 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 logs policy
Why: Cleanup.
What to do: Delete bucket policy nairobiride-logs.
- 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.