A role in account A has an identity policy allowing s3:GetObject on a bucket owned by account B, and the call still returns AccessDenied. What rule governs cross-account access in AWS, and how does it differ from the same-account case?
answer
- one account cannot help itself
- both owners must opt in
- union here, intersection there
- name the role ARN, not the session
- the KMS key is a second door
basics
~20 sCross-account access needs an allow on both sides: the caller's identity policy in account A and the resource's own policy in account B. Within a single account the two are a union, so one allow suffices — which is why the same policy works locally and fails across accounts.
solid answer
~50 sAcross an account boundary the two policies **intersect**: account A must allow its role to call `s3:GetObject` on that bucket, and account B's bucket policy must allow that principal. Missing either side gives `AccessDenied`, and account A cannot fix it alone no matter how broad its policy is — which is the whole point, since otherwise anyone could grant themselves access to someone else's data by writing their own policy. Inside one account the same two documents are a **union** instead: an Allow in either the identity policy or the resource policy is enough. That asymmetry is why a policy that works fine locally breaks the moment the bucket moves to another account. When debugging, confirm the resource policy names the right principal — usually the role ARN, not the assumed-role session ARN — and remember that reading an object encrypted with a customer managed KMS key needs the key policy to allow the caller too.
code
json · 12 lines{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowAccountAReader",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::111122223333:role/report-reader" },
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::account-b-reports/*"
}
]
}go deeper
Know that touching another account's resource takes permission from both accounts, and that a policy working in your own account proves nothing about a bucket someone else owns.
State the union-versus-intersection rule crisply and describe both access shapes — a resource policy naming the caller, or assuming a role in the resource's account — with a reason to choose each.
Debug it under pressure: read the denial message, check ARNs and the principal form, then look past IAM to the KMS key and to organization guardrails. Also raise S3 object ownership before it bites the receiving team.
Decide the org-wide pattern: which sharing goes through assumed roles versus resource policies, how aws:PrincipalOrgID and similar conditions bound every grant, and how cross-account access is reviewed rather than accumulated ad hoc.
## The rule When the calling principal and the target resource live in different AWS accounts, **both** accounts must allow the request: - the caller's account must grant its principal permission to perform the action on that resource (an identity-based policy on the role or user), and - the resource's account must grant that principal access (a resource-based policy on the bucket, queue, topic, key, function, or a role trust policy). Neither grant alone is sufficient. Within a single account the same two documents form a union — one Allow anywhere is enough — so the identical identity policy behaves differently depending on who owns the resource. ## Why AWS designed it this way An identity policy is written by the caller's account. If it alone could authorize access to foreign resources, every account on earth could write `Allow s3:GetObject on arn:aws:s3:::your-bucket/*` and be done. The requirement that the resource owner also opt in is what makes account boundaries a real security boundary rather than a billing construct. It is also why account separation is the strongest isolation primitive AWS offers, and why organizations use accounts, not just IAM, to separate environments. ## The two shapes of cross-account access There are two ways to reach across, and confusing them causes a lot of wasted debugging. **Direct access with a resource-based policy.** Account B's bucket policy names account A's role as `Principal`, and account A's role has the matching S3 permission. The caller keeps its own identity and credentials for the whole call. ```json { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::111122223333:role/report-reader" }, "Action": "s3:GetObject", "Resource": "arn:aws:s3:::account-b-reports/*" } ``` **Role assumption.** Account B publishes a role whose trust policy allows account A's principal, and account A's principal is allowed to call `sts:AssumeRole` on it. The caller then acts *as an account-B principal*, so the request is no longer cross-account at all and the union rule applies again inside B. This is the pattern to prefer when the caller needs several permissions in B, because you manage one role there instead of editing many resource policies. Not every service supports resource-based policies, which makes role assumption the general answer. ## Debugging the AccessDenied Work through the two sides deliberately. 1. **Whose policy is missing?** Modern `AccessDenied` messages often say so, distinguishing a missing identity-based allow from a resource-policy denial. Read the message before touching anything. 2. **Does the resource policy name the right principal?** For an assumed role, write the **role** ARN (`arn:aws:iam::111122223333:role/report-reader`). The session ARN under `sts::.../assumed-role/...` is not what the `Principal` element expects, and a deleted-and-recreated role breaks a policy that pinned a principal by unique ID rather than ARN. 3. **Are the ARNs right?** `arn:aws:s3:::bucket` covers the bucket, `arn:aws:s3:::bucket/*` covers objects. Half of all S3 cross-account tickets are this. 4. **Is a guardrail interfering?** An SCP in either organization, a permissions boundary on the caller, or a session policy can block a request both explicit policies allow. 5. **Is there another resource in the chain?** Reading an object encrypted with a customer managed KMS key also requires the key policy in account B to allow the caller — a second cross-account handshake people forget entirely. ## Two details worth knowing **Object ownership in S3.** If a principal from account A writes an object into account B's bucket, the object can end up owned by A, and B's own users may then be unable to read it. Enabling **S3 Object Ownership** with the bucket-owner-enforced setting settles this by disabling ACLs and making the bucket owner the owner of every object. **Confused-deputy protection.** When you grant access to a whole account or to an AWS service, add condition keys that pin the actual caller — `aws:SourceArn`, `aws:SourceAccount`, or `aws:PrincipalOrgID` to limit access to principals in your organization. A `Principal` of `"AWS": "arn:aws:iam::444455556666:root"` trusts every principal in that account, present and future. ## The one-line answer *Same account, identity and resource policies union; different accounts, they intersect — and the resource owner always has the final say.*
- When would you prefer cross-account role assumption over a resource-based policy?When the caller needs more than a single narrow action, when many resources are involved, or when the service has no resource-based policy at all. Assuming a role in the resource-owning account collapses the problem to one trust relationship plus normal in-account permissions, instead of a growing set of resource policies each of which must be kept in sync.
- The bucket policy allows the account root of account A. Is that enough for the role to read objects?Not by itself. Granting to `arn:aws:iam::111122223333:root` delegates the decision to account A, so account A must still attach an identity policy allowing s3:GetObject on that bucket. It also trusts every principal in A, so pair it with conditions such as aws:PrincipalArn or aws:PrincipalOrgID if you mean something narrower.
- Why can a cross-account S3 read fail even when both the identity policy and the bucket policy are correct?Because another resource is in the path. If the object uses SSE-KMS with a customer managed key, the key policy in the owning account must also allow the caller's kms:Decrypt. Guardrails can also intervene — an SCP in either organization, a permissions boundary, or a session policy narrower than the identity policy.
saying these in an interview costs you the question
- Thinks a broad identity policy alone can reach another account
- Puts the assumed-role session ARN in a Principal element
- Confuses the bucket ARN with the object ARN
- Grants to account root without any condition key
- Forgets SSE-KMS needs a separate cross-account key grant