How Assuming a Role Actually Works

When an EC2 instance with an attached IAM Role needs to call S3, here's the underlying mechanism: EC2's instance metadata service (a special internal, non-internet-routable endpoint at 169.254.169.254 reachable only from inside the instance) exposes temporary credentials that AWS automatically generates and rotates on the role's behalf. The AWS SDK, when running inside that EC2 instance, automatically checks this metadata endpoint first, retrieves the current temporary access key/secret/session token, and uses them to sign the S3 API call — all of this happens transparently, with no code in your application needing to fetch or refresh credentials manually.

For a human or an external system assuming a role (rather than an AWS resource), the flow goes through AWS STS's AssumeRole API call explicitly: the caller must already have permission (granted by their own identity's policy) to call sts:AssumeRole on that specific role's ARN, and the role itself must have a 'trust policy' listing which identities are allowed to assume it. STS then returns a temporary access key, secret key, and session token, valid for a configurable duration (15 minutes to 12 hours), which the caller uses for subsequent API calls until they expire.

This two-sided check — the caller's own permission to assume the role, AND the role's trust policy allowing that caller — is what prevents any random IAM user from assuming a highly-privileged role just because they happen to know its name.

In the AWS Console

  1. 1

    IAM → Roles → [your role] → Trust relationships tab

    Review or edit the trust policy JSON to see exactly which identities are permitted to assume this role.

    This is the single most important thing to check when debugging an 'access denied' error on an AssumeRole call — the caller's own policy AND the role's trust policy both have to allow it.

💻 Code example

# Example: a human assuming a role via AWS CLI
aws sts assume-role \
  --role-arn arn:aws:iam::123456789012:role/DeployRole \
  --role-session-name my-deploy-session

# Returns temporary AccessKeyId, SecretAccessKey, and SessionToken
# valid for the role's configured duration (default 1 hour)

💬 Deep Dive with AI