AWS Role Assumption Explained: Cross-Account Access, STS and CLI

Role assumption is the process of exchanging your current AWS credentials for temporary credentials that belong to a different IAM role. It is the mechanism behind EC2 instance profiles, Lambda execution roles, cross-account access, and CI/CD pipelines that deploy to AWS without long-lived keys.

Simple explanation

That is role assumption in one sentence: you temporarily become a different identity, with different permissions, using credentials that expire on their own.

Why role assumption matters

Long-lived access keys are the most common cause of AWS security incidents. They get committed to Git, pasted into Slack, or left in old servers. Role assumption eliminates this risk by issuing short-lived credentials that expire on their own.

  • No permanent secrets. Temporary credentials expire, so a leaked credential has a bounded damage window.
  • Least privilege by design. Each role carries only the permissions it needs. Different workloads get different roles.
  • Safer cross-account access. Instead of sharing access keys between teams, you share a role ARN. The target account controls exactly who can assume the role and what they can do.
  • Better auditability. Every AssumeRole call is logged in CloudTrail with the caller identity, session name, and source IP.
Bottom line

If your workloads, automation, or team members still use long-lived access keys to interact with AWS, switching to role assumption is the single highest-impact security improvement you can make.

How role assumption works

When a principal (user, role, or service) wants to assume a role, AWS runs through these steps:

  1. The caller calls sts:AssumeRole with the target role’s ARN.
  2. AWS evaluates the target role’s trust policy to confirm the caller is an allowed principal.
  3. AWS checks that the caller’s own identity policy allows sts:AssumeRole on the target role ARN (required for cross-account assumption and same-account assumption when the trust policy trusts the account root).
  4. If both checks pass, STS returns temporary credentials: access key ID, secret access key, and session token.
  5. The caller uses these credentials for API calls. Those calls are authorized against the role’s permissions policy, not the caller’s original permissions.
Key distinction

The trust policy controls who can assume the role. The permissions policy controls what the role can do after assumption. These are two separate policies on the same role. Confusing the two is the most common source of role assumption errors.

Trust policy vs permissions policy

This distinction trips up most beginners. A single IAM role has two separate policy attachments that serve completely different purposes.

Trust policyPermissions policy
Question it answersWho can assume this role?What can this role do?
Policy typeResource-based policy (attached to the role itself)Identity-based policy (inline or managed)
Principal fieldRequired (specifies the allowed caller)Not used (the role is the principal)
Action fieldsts:AssumeRoleThe AWS actions the role can perform (e.g. s3:GetObject)
ConditionsMFA, ExternalId, source IP, org IDResource tags, request conditions, time windows

Trust policy example: allow an AWS service

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": { "Service": "ec2.amazonaws.com" },
      "Action": "sts:AssumeRole"
    }
  ]
}

Trust policy example: allow a specific IAM user

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::123456789012:user/alice"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

Trust policy example: require MFA

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::123456789012:user/alice"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "Bool": {
          "aws:MultiFactorAuthPresent": "true"
        }
      }
    }
  ]
}

This pattern is common for sensitive roles like production admin or billing access. The caller must authenticate with MFA before AWS will issue the temporary credentials.

When to use role assumption

Role assumption is not just for cross-account access. It is the default way workloads authenticate to AWS.

  • EC2 instance profiles. An EC2 instance assumes a role automatically through the instance metadata service. Your application calls the AWS SDK and gets temporary credentials without any configuration.
  • Lambda execution roles. Every Lambda function has an execution role. When the function runs, it assumes that role to access other AWS services.
  • Cross-account admin or deployment access. A developer in Account A assumes a role in Account B to manage resources there, without needing a separate set of credentials in Account B.
  • CI/CD pipelines. GitHub Actions, CodeBuild, or Jenkins assume a deployment role to push code to production without storing long-lived secrets in the pipeline.
  • Third-party vendor access with ExternalId. A SaaS product assumes a role in your account to read logs or manage resources, using ExternalId to prevent the confused deputy problem.
  • EKS pods. IAM Roles for Service Accounts (IRSA) lets individual Kubernetes pods assume their own IAM roles, so each workload gets only the permissions it needs.
Rule of thumb

If something runs on AWS and talks to other AWS services, it should use a role. EC2 instances, Lambda functions, ECS tasks, CodeBuild jobs, and Step Functions all support role assumption natively. There is almost never a reason to embed access keys in a workload.

Cross-account role assumption in AWS

Cross-account role assumption is how one AWS account grants controlled access to resources in another account. This is the standard pattern in multi-account organizations.

The two accounts

  • Source account (Account A). The caller’s identity lives here. This could be a developer’s IAM user, a CI/CD pipeline role, or any other principal.
  • Target account (Account B). The resources to access live here. A role exists in this account with a trust policy that allows principals from Account A.

Both sides must be configured. The target role must trust the source, and the source principal must have permission to call sts:AssumeRole on the target role.

Step 1: Create the role in Account B (target)

The trust policy on this role determines who from Account A can assume it.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::111111111111:root"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "sts:ExternalId": "unique-external-id-abc123"
        }
      }
    }
  ]
}

Trusting :root means any principal in Account A can assume this role, as long as their own identity policy also allows sts:AssumeRole. Both conditions must be true.

Step 2: Allow the caller in Account A (source)

Attach an identity policy to the calling user or role in Account A:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "sts:AssumeRole",
      "Resource": "arn:aws:iam::222222222222:role/CrossAccountRole"
    }
  ]
}

Step 3: Assume the role from Account A

aws sts assume-role \
  --role-arn arn:aws:iam::222222222222:role/CrossAccountRole \
  --role-session-name "cross-account-session" \
  --external-id "unique-external-id-abc123"

ExternalId explained

ExternalId is a string agreed upon between you and a third party. It prevents the confused deputy problem. Without it, a malicious customer of the same SaaS vendor could trick the vendor into accessing your account by providing your role ARN. With ExternalId, the vendor must also supply the correct identifier, which only the legitimate customer knows.

Warning

Always require ExternalId when a third party assumes a role in your account. Skipping it leaves you vulnerable to the confused deputy attack. You generally do not need ExternalId for roles assumed only by your own accounts or organization.

Tip

In AWS Organizations, you can simplify trust policies by using the aws:PrincipalOrgID condition key instead of listing every account ARN. This allows any account in your organization to assume the role without maintaining a list of account IDs.

AWS CLI examples

Direct assume-role call

aws sts assume-role \
  --role-arn arn:aws:iam::123456789012:role/AdminRole \
  --role-session-name "alice-admin-session" \
  --duration-seconds 3600

This returns a JSON response containing temporary credentials. To use them, export them as environment variables:

Export temporary credentials

# Extract and export credentials
eval $(aws sts assume-role \
  --role-arn arn:aws:iam::123456789012:role/AdminRole \
  --role-session-name "alice-admin-session" \
  --query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken]' \
  --output text | awk '{print "export AWS_ACCESS_KEY_ID="$1"\nexport AWS_SECRET_ACCESS_KEY="$2"\nexport AWS_SESSION_TOKEN="$3}')

# Verify you are now acting as the role
aws sts get-caller-identity

Using a named profile in ~/.aws/config

For repeated use, define a profile that assumes the role automatically:

[profile staging-admin]
role_arn = arn:aws:iam::123456789012:role/AdminRole
source_profile = default
role_session_name = alice-staging

Then use it with the —profile flag:

aws s3 ls --profile staging-admin

The CLI handles the AssumeRole call, caches the temporary credentials, and refreshes them when they expire. This is the most convenient approach for daily work.

Verify before you act

Always run aws sts get-caller-identity after assuming a role to confirm you are operating as the intended identity. This takes one second and prevents costly mistakes in the wrong account.

Role assumption vs long-lived access keys

Role assumptionLong-lived access keys
Credential lifetime1 to 12 hours, then expires automaticallyNever expires until manually rotated or deleted
RotationAutomatic (new credentials each session)Manual (you must rotate and update every consumer)
Blast radius if leakedLimited to the session durationUnlimited until someone notices and revokes the key
Cross-account accessBuilt in via trust policiesRequires sharing keys, which is an anti-pattern
AuditabilityEach assumption is a distinct CloudTrail event with session nameAll calls show the same access key, harder to trace to a person
Best forWorkloads, automation, CI/CD, human accessNarrow use cases like CLI access from a personal machine (and even then, SSO is preferred)

For a deeper look at why access keys are risky, read why long-lived AWS access keys are dangerous.

Role chaining

Role chaining is when an already-assumed role assumes another role. This is valid and sometimes necessary in multi-account architectures, but it comes with an important constraint: chained sessions are always capped at one hour, regardless of the MaxSessionDuration setting on the target role.

# First assumption: your IAM user assumes RoleA
aws sts assume-role \
  --role-arn arn:aws:iam::111111111111:role/RoleA \
  --role-session-name sess1

# Export RoleA's credentials, then assume RoleB from RoleA
aws sts assume-role \
  --role-arn arn:aws:iam::222222222222:role/RoleB \
  --role-session-name sess2

For this to work, RoleB’s trust policy must allow RoleA as a principal. RoleA needs permission to call sts:AssumeRole on RoleB’s ARN.

Warning

Avoid deep role chaining (more than two hops) where possible. Each hop adds complexity, reduces the maximum session duration, and makes CloudTrail logs harder to follow. Design your role architecture with as few hops as necessary.

Common mistakes

  1. Trust policy configured but caller permission missing. For cross-account assumption, both the trust policy on the target role AND an sts:AssumeRole allow in the caller’s identity policy must exist. Missing either one returns AccessDenied.
  2. Trusting account root too broadly. When the trust policy trusts :root, any principal in that account can assume the role if their identity policy allows it. Add caller-side restrictions to limit which principals actually use the role.
  3. Not using ExternalId for third-party access. Without ExternalId, a confused deputy attack lets one customer of a SaaS vendor trick the vendor into acting on another customer’s account. Always require ExternalId for external integrations.
  4. Poor session naming. The session name appears in CloudTrail. Use a name that identifies who is assuming the role (username, job ID, pipeline name) so you can trace actions during incident investigations.
  5. Assuming the wrong CLI profile. With multiple AWS profiles configured, it is easy to run commands against the wrong account. Always verify with aws sts get-caller-identity before and after assumption.
  6. Misunderstanding role chaining limits. Chained sessions are always capped at one hour, even if the role allows longer sessions. If your automation needs longer credentials, avoid chaining and assume the target role directly.
  7. Overusing broad admin roles. Creating one AdministratorAccess role for all cross-account work defeats the purpose of role assumption. Create separate roles with scoped permissions for each use case.

Troubleshooting AssumeRole failures

If your AssumeRole call returns an error, work through this checklist. Most failures come down to one of these causes. For a broader guide, see fixing IAM AccessDenied errors.

SymptomLikely causeFix
AccessDenied on sts:AssumeRoleCaller’s identity policy does not allow sts:AssumeRole on the target role ARNAdd an allow statement for sts:AssumeRole on the role ARN in the caller’s policy
AccessDenied (trust policy)The target role’s trust policy does not list the caller as an allowed principalUpdate the trust policy to include the caller’s ARN or account root
AccessDenied (MFA required)The trust policy has an MFA condition but the caller did not authenticate with MFAPass —serial-number and —token-code with the AssumeRole call, or use GetSessionToken with MFA first
AccessDenied (ExternalId mismatch)The trust policy requires an ExternalId but the caller sent the wrong value or omitted itInclude the correct —external-id in the CLI call
Wrong account or wrong roleTypo in the role ARN or using the wrong AWS profileRun aws sts get-caller-identity to confirm your current identity, then double-check the target role ARN
Credentials expire too quicklyRole chaining caps sessions at one hourAssume the target role directly instead of chaining through an intermediate role
Common trap

The AssumeRole AccessDenied error message does not tell you which side failed: the trust policy or the caller’s identity policy. When debugging, check both. Start with aws sts get-caller-identity to confirm who you are, then verify the trust policy allows that principal, then verify the caller has sts:AssumeRole permission.

For authentication errors beyond AssumeRole, see troubleshooting authentication errors in AWS.

Frequently asked questions

What does assuming a role mean in AWS?

Assuming a role means calling AWS STS to exchange your current credentials for a set of temporary credentials that belong to a different IAM role. For the duration of the session your API calls run with the permissions attached to that role, not your original identity.

What is the difference between a trust policy and a permissions policy?

The trust policy is attached to the role and controls who can assume it. The permissions policy is also attached to the role and controls what the role can do once assumed. Trust policy answers who, permissions policy answers what.

When do I need sts:AssumeRole permission on the caller side?

You need an identity policy that allows sts:AssumeRole on the target role ARN whenever the caller is a different principal type than an AWS service. Cross-account assumption always requires it. Same-account assumption requires it when the trust policy trusts the account root rather than the specific principal.

What is role chaining in AWS?

Role chaining is when an already-assumed role assumes another role. AWS allows this but enforces a maximum session duration of one hour for chained sessions, regardless of the MaxSessionDuration setting on the target role.

When should I use ExternalId?

Use ExternalId any time a third party such as a SaaS vendor needs to assume a role in your account. ExternalId prevents the confused deputy problem by ensuring only a caller who knows the agreed-upon identifier can assume the role.

How long do assumed-role credentials last?

The default is one hour. You can request up to 12 hours if the role MaxSessionDuration is configured to allow it. Role-chained sessions are always capped at one hour.

Last verified: 30 March 2026 Cloud services change frequently. Verify details against official documentation before making infrastructure decisions.