How does cross-account access work with IAM roles and resource policies?
Quick Answer
Cross-account access uses two mechanisms: IAM role assumption (account B creates a role with a trust policy allowing account A's principal, and account A's principal has sts:AssumeRole permission) or resource-based policies (the resource in account B directly grants access to account A's principal). Role assumption requires two policies to align, while resource-based policies enable direct access without assuming a role, and the two approaches have different implications for permission boundaries, SCPs, and session policies.
Detailed Answer
Cross-account access is one of the most misunderstood topics in AWS because there are two fundamentally different mechanisms, and the permissions evaluation logic differs between them. Think of it like two buildings: with role assumption, you walk into building B, get a visitor badge (temporary credentials), and operate under building B's rules. With resource-based policies, building B sends a package to you in building A; you never enter building B, so your building A badge is what matters.
Mechanism 1: IAM Role Assumption (sts:AssumeRole). Account B (the trusting account, e.g., 987654321098) creates an IAM role with a trust policy that specifies which principals in account A (the trusted account, e.g., 123456789012) can assume it. Account A's principal must also have an IAM policy granting sts:AssumeRole on the role ARN in account B. Both policies must align; either one missing means access denied. When the principal in account A calls sts:AssumeRole, it receives temporary credentials (access key, secret key, session token) that operate with the permissions of the role in account B. Critically, the original permissions from account A are dropped entirely. The principal now operates as if it lives in account B, subject to account B's SCPs, permission boundaries, and session policies.
Mechanism 2: Resource-Based Policies. Some AWS resources (S3 buckets, SQS queues, SNS topics, Lambda functions, KMS keys) support resource-based policies that directly grant cross-account access. When account B's S3 bucket policy grants s3:GetObject to arn:aws:iam::123456789012:role/prod-analytics-etl-role, the principal in account A can access the bucket directly without assuming a role. The critical difference: the principal retains its own identity and permissions. Both the identity-based policy in account A and the resource-based policy in account B are evaluated, and if either grants access (in the absence of an explicit deny), the request succeeds. This is called the union of permissions for cross-account resource-based policies.
SCP interactions create the most confusion. When using role assumption, SCPs in account B apply to the assumed role session. If account B's SCP denies s3:DeleteObject, the assumed role cannot delete objects even if the role's IAM policy allows it. SCPs in account A do not apply because the principal is now operating in account B's context. For resource-based policies, SCPs in both accounts apply: account A's SCPs affect the principal making the request, and account B's SCPs affect the resource being accessed.
Permission boundaries interact differently too. If principal A has a permission boundary that does not include s3:GetObject, it cannot access account B's S3 bucket via resource-based policy (because its effective permissions are the intersection of IAM policy and permission boundary). However, if principal A assumes a role in account B, the permission boundary on principal A is irrelevant; only the permission boundary on the role in account B matters.
The confused deputy problem arises when a service in account B assumes a role on behalf of an external entity. Always use the ExternalId condition in trust policies for third-party access. The ExternalId acts like a shared secret that prevents an attacker from tricking your role into granting them access through a different AWS account they control.
Production gotchas: S3 bucket policies have a 20KB size limit, which you hit quickly when granting access to many individual accounts; use aws:PrincipalOrgID instead to grant access to all accounts in your AWS Organization. KMS key policies are unique: the key policy must explicitly grant access even for same-account principals (unlike S3 where the IAM policy alone is sufficient). When using cross-account role assumption in CI/CD pipelines, implement session tags to pass metadata (environment, pipeline-id, commit-sha) that can be used in condition keys for fine-grained access control.