Lambda function in Account A was getting 403 Access Denied on every s3:GetObject call to a bucket in Account B. The IAM role attached to the Lambda had an explicit s3:GetObject allow policy. The error: An error occurred (403) when calling the GetObject operation: Forbidden.
Root cause: S3 cross-account access requires permissions to be granted from both sides. The IAM policy on the role in Account A was correct, but the bucket policy in Account B had no statement allowing Account A's role. S3 evaluates both policies — if either side denies (or doesn't explicitly allow), the request fails with 403.
Fix:
Add this statement to the S3 bucket policy in Account B:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCrossAccountAccess",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111122223333:role/my-lambda-role"
},
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-bucket",
"arn:aws:s3:::my-bucket/*"
]
}
]
}Replace 111122223333 with Account A's ID and my-lambda-role with the actual role name.
To debug S3 permission issues quickly, use the IAM policy simulator or check CloudTrail for the denied event — it shows exactly which policy evaluation caused the deny.
Lesson: S3 cross-account access needs an allow on both the IAM role (source account) AND the bucket policy (target account) — one side alone is never enough.