Cross-account S3 access blocked — policy mismatch
A role in account A cannot read a bucket owned by account B
Focused incident mode — strips away everything but this runbook.
Symptoms
- A role in account A cannot read a bucket owned by account B
- Works from one account but not from the delegated role
- CloudTrail shows both slips in S3 + IAM 'no matching statement'
Evidence to grab
Collect these artifacts before you change anything — they prove (or rule out) each cause.
Caller identity
aws sts get-caller-identityBucket policy of the owner
aws s3api get-bucket-policy --bucket <BUCKET> --region <REGION>
Diagnostic flow
Run the steps in order, marking each claim confirmed or refuted. The evidence you gather feeds the ranked causes below.
Guided decision tree is ready.
Steps with commands, expected output, and pass/fail branches.
Ranked root causes
Evidence checklist
- get-bucket-policy has no matching Principal/Action for the caller
- The policy references only the owner account or an explicit Deny
Confirm before fixing
aws s3api get-bucket-policy --bucket <BUCKET> --query Policy --output text | grep -A3 -B3 '<CALLER-ACCOUNT>'Find the caller in the policy
Expected: Either a grant statement or nothing
Fix
aws s3api put-bucket-policy --bucket <BUCKET> --policy file://cross-account-policy.jsonAdd the cross-account grant (Principal = caller account/role, s3:GetObject on arn:aws:s3:::<BUCKET>/*)
Resolution runbook
Apply from top to bottom after the cause is confirmed. Review every command before running it — risk tags are shown on each.
aws sts get-caller-identityPin the caller
aws s3api get-bucket-policy --bucket <BUCKET> --query Policy --output textPin the owner's side
aws s3api put-bucket-policy --bucket <BUCKET> --policy file://cross-account-policy.jsonAdd the grant on the owning bucket
aws s3api head-object --bucket <BUCKET> --key <KEY>Retry from the caller's credentials
Expected: 200 or the resolved permission error
Prevention
- Model cross-account access as policy pairs and validate both in CI (IAM Access Analyzer).
- Prefer org-level principals and aws:PrincipalOrgID for durable grants.
- Share KMS keys and their policy grants when SSE-KMS objects move between accounts.
Related runbooks
Same stack, same failure family — worth a skim.
All client types get 403 Forbidden on a bucket
App or pipeline fails with 'not authorized to perform sts:AssumeRole'
aws s3 ls s3://<BUCKET>/ fails with AccessDenied on the instance
Commands marked READ CHANGE DESTRUCTIVE — never run DESTRUCTIVE commands against production data you cannot restore. Copy → verify → execute.