Cross-Account IAM Roles
A cross-account IAM role grants access to resources in one AWS account — the trusting account — to principals in another — the trusted account. It is the standard building block for least privilege in a multi-account estate, and the canonical reference for the mechanics of role assumption.
For the choice between this and a resource-based policy, see Cross-account access: resource-based policies vs IAM roles.
The three policies
Section titled “The three policies”Three separate policies have to line up. Missing any one of them produces an
AccessDenied that looks the same from the outside.
1. Trust policy, on the role in the trusting account
Section titled “1. Trust policy, on the role in the trusting account”States who may assume the role:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::111122223333:role/DeploymentRole" }, "Action": "sts:AssumeRole", "Condition": { "StringEquals": { "aws:PrincipalOrgID": "o-example123" } } } ]}Naming the specific role, rather than arn:aws:iam::111122223333:root, keeps the grant with
one identity instead of delegating it to the other account’s administrators.
2. Permission policy, on the same role
Section titled “2. Permission policy, on the same role”States what a session holding the role may do:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::example-bucket", "arn:aws:s3:::example-bucket/*" ] } ]}3. AssumeRole permission, in the trusted account
Section titled “3. AssumeRole permission, in the trusted account”Attached to the user or role that will do the assuming:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "sts:AssumeRole", "Resource": "arn:aws:iam::444455556666:role/CrossAccountRole" } ]}How it works
Section titled “How it works”- Setup. The trusting account creates the role with its trust and permission policies.
The trusted account attaches
sts:AssumeRolepermission to the identities that need it. No role has to be created in the trusted account. - Assumption. The caller invokes
sts:AssumeRole. AWS STS checks both the trust relationship and the caller’s own permission to make the call. If both hold, it returns temporary credentials. - Access. The credentials carry the role’s permissions and expire — between 15 minutes
and the role’s
MaxSessionDuration, which can be up to 12 hours. A session obtained by chaining from an already-assumed role is capped at one hour.
Common uses
Section titled “Common uses”- Central security tooling. A security account holds the tools and assumes a read-only role in every other account for logging, monitoring and compliance.
- Deployment. A pipeline in a build account assumes a narrowly scoped role in each target account, so no account holds a long-lived deployment credential.
- Human access across environments. Engineers work in development, staging and production through roles assigned by IAM Identity Center rather than through per-account users.
- Third parties. A vendor’s account assumes a role in yours, with an external ID and a scope limited to what the integration actually needs.
Practice
Section titled “Practice”External IDs. When granting access to a third party, require an sts:ExternalId
condition in the trust policy. Without it, a vendor that holds roles in many customer
accounts can be tricked into using its access to yours on someone else’s behalf — the
confused deputy problem. The external ID should be a value you choose and give to the vendor,
not one they choose.
Bound the maximum. Use permissions boundaries on the roles, and service control policies at the OU level, so that a mistake in a role’s permission policy has a ceiling.
Monitor. CloudTrail records every assumption with the session name and the source. Alarm on assumptions from unexpected principals or at unexpected times, and review the roles’ permissions against what the sessions actually do.
Setup with the CLI
Section titled “Setup with the CLI”# In the trusting account: create the roleaws iam create-role \ --role-name CrossAccountRole \ --assume-role-policy-document file://trust-policy.json
# In the trusting account: attach permissions to the roleaws iam attach-role-policy \ --role-name CrossAccountRole \ --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
# In the trusted account: allow a principal to assume itaws iam put-role-policy \ --role-name DeploymentRole \ --policy-name CrossAccountAssume \ --policy-document file://assume-role-policy.json
# From the trusted account: assume the roleaws sts assume-role \ --role-arn arn:aws:iam::444455556666:role/CrossAccountRole \ --role-session-name deployment-sessionTroubleshooting
Section titled “Troubleshooting”Access denied when calling AssumeRole
- Confirm the trust policy names the calling principal, and that the ARN matches exactly
- Confirm the caller has
sts:AssumeRoleon the specific role ARN - Check for an external ID that is required but not supplied, or supplied but not required
- Check whether a service control policy above either account denies the action
- If the role in the trust policy was deleted and recreated, the stored principal ID no longer matches — re-save the trust policy with the current ARN
Access denied after assuming the role
- Confirm the role’s permission policy covers the action and the exact resource ARN
- Check for a permissions boundary or session policy narrowing the session
- Check for a resource-based policy on the target that denies, or does not allow, the role
- For a chained session, remember the one-hour cap — an expired session and a missing permission look identical
Unexpected access levels
- Use IAM Access Analyzer to see what the role can actually reach
- Check inherited permissions from group membership on the calling identity
- Evaluate resource-based policies on the targets, which can grant access independently of the role