Introduction
Cross-account S3 access fails when bucket policies contain explicit deny statements, when the requesting IAM principal lacks proper permissions, or when Service Control Policies (SCPs) at the organization level block the action. The error manifests as 403 AccessDenied even when both the bucket policy and IAM policy appear to grant access.
Symptoms
An error occurred (AccessDenied) when calling the GetObject operation: Access Denied- S3 access logs show
403 AccessDeniedwith the requester's account ID aws s3 ls s3://bucket-name/ --profile cross-accountfails but works with root account- CloudTrail shows
s3:GetObjectwitherrorCode: AccessDenied
Common Causes
- Explicit Deny in bucket policy overrides Allow statements
- Missing
s3:ListBucketpermission on the bucket resource (separate from object permission) - SCP at organization level blocks S3 actions from external accounts
- Missing
aws:PrincipalArncondition in bucket policy - VPC endpoint policy restricting S3 access for specific principals
Step-by-Step Fix
- 1.Check bucket policy for explicit denies:
- 2.```bash
- 3.aws s3api get-bucket-policy --bucket my-bucket --query 'Policy' --output text | python3 -m json.tool
- 4.
` - 5.Search for
"Effect": "Deny"statements that may match the cross-account principal. - 6.Verify IAM role permissions in the requesting account:
- 7.```bash
- 8.aws iam get-role-policy --role-name CrossAccountS3Role --policy-name S3Access
- 9.
` - 10.The role needs both
s3:GetObjectonarn:aws:s3:::bucket/*ANDs3:ListBucketonarn:aws:s3:::bucket. - 11.Check organization SCPs:
- 12.```bash
- 13.aws organizations list-policies --filter SERVICE_CONTROL_POLICY
- 14.aws organizations describe-policy --policy-id p-example
- 15.
` - 16.Look for SCPs that deny
s3:*or specific S3 actions for the requesting account. - 17.Update bucket policy for cross-account access:
- 18.```json
- 19.{
- 20."Version": "2012-10-17",
- 21."Statement": [
- 22.{
- 23."Sid": "CrossAccountAccess",
- 24."Effect": "Allow",
- 25."Principal": {"AWS": "arn:aws:iam::EXTERNAL-ACCOUNT:role/CrossAccountS3Role"},
- 26."Action": ["s3:GetObject", "s3:ListBucket"],
- 27."Resource": [
- 28."arn:aws:s3:::my-bucket",
- 29."arn:aws:s3:::my-bucket/*"
- 30.]
- 31.}
- 32.]
- 33.}
- 34.
` - 35.```bash
- 36.aws s3api put-bucket-policy --bucket my-bucket --policy file://policy.json
- 37.
` - 38.Verify access from the requesting account:
- 39.```bash
- 40.aws sts assume-role --role-arn arn:aws:iam::BUCKET-ACCOUNT:role/S3AccessRole \
- 41.--role-session-name test
- 42.aws s3 ls s3://my-bucket/ --profile assumed-role
- 43.
`
Prevention
- Use AWS RAM for structured cross-account resource sharing
- Test bucket policy changes in the IAM policy simulator before applying
- Document all cross-account access patterns in a central registry
- Enable S3 Block Public Access to prevent accidental public exposure
- Use VPC endpoints with proper policies for controlled access