Course outline

2.4

Roles, STS and IAM Security Tools

Temporary credentials for services and people, AssumeRole step by step, Credentials Report and Access Advisor.

22 min Reading + animations

Tip: tap or hover any dotted-underlined term to see what it means.

By the end of this lesson you will be able to

  • Explain why roles exist: permissions without long-term keys, handed out as temporary credentials by AWS STS.
  • Read and write the two policies of a role: the trust policy (who may assume it) and the permissions policy (what it may do).
  • Walk through an AssumeRole call step by step, and diagnose the four classic failures: untrusted caller, missing permission, expired token, role chaining past one hour.
  • Give EC2, Lambda and other services permissions with instance profiles, service roles and service-linked roles.
  • Set up cross-account access with an external ID and an MFA condition, and know where federation and IAM Identity Center fit.
  • Respond to a leaked access key, and audit an account with the Credentials Report, Access Advisor and IAM Access Analyzer.

#The trouble with long-term keys

In lessons 2.1 to 2.3 every identity was an IAM user: a person with a password and, for the CLI or SDK, an access key (an access key ID plus a secret access key, lesson 1.3). Those keys are long-term: they work from anywhere in the world, day and night, until someone deactivates them.

That is fine for one engineer at a laptop. It breaks down quickly:

  • An application on EC2 needs to read S3. If you put a key in a config file, in user data or in the code, everyone who can read that file, image or repository can read the key. It never expires, so a copy taken today still works next year.
  • A Lambda function needs to write to DynamoDB. There is no laptop and no person; who would type the key in?
  • A colleague in another AWS account needs to look at your logs. Creating an IAM user for them in your account means one more password and key to manage, and nobody remembers to delete it when they change teams.
  • A company has 2,000 employees in its own directory. Creating 2,000 IAM users duplicates every joiner, mover and leaver.

The answer to all four is an IAM role: an identity with permissions but no password and no access keys of its own. Someone or something trusted assumes the role, and AWS STS (Security Token Service) hands back temporary credentials that expire on their own, from 15 minutes to a few hours later.

#What a role is

A role is an IAM identity, like a user, with an ARN such as arn:aws:iam::111122223333:role/app-reader. The difference is in how you become it.

IAM userIAM role
Meant forOne person (or, rarely, one old application)Whoever is allowed to assume it: a service, an application, a person from another account or directory
CredentialsLong-term: password, up to two access keysNone of its own; each session gets temporary credentials from STS
ExpiryNever, until you rotate or deactivateAutomatic, at the end of the session
Who can use itWhoever holds the password or keysOnly principals listed in its trust policy
Can be in a groupYesNo; groups contain users only

#The two policies of a role

Every role has two kinds of policy, and mixing them up is the most common mistake with roles.

  1. The trust policy answers who may assume this role. It is a resource-based policy attached to the role itself, and it is the only place a role names a Principal. Its action is sts:AssumeRole (or sts:AssumeRoleWithSAML, sts:AssumeRoleWithWebIdentity for federation).
  2. The permissions policies answer what the role may do once assumed. They are ordinary identity-based policies (managed or inline), exactly like the ones you attach to users in lesson 2.1, and they are evaluated with the same logic as lesson 2.2.

A trust policy that lets the EC2 service assume the role:

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

And a permissions policy that lets it read one bucket:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:ListBucket"],
      "Resource": ["arn:aws:s3:::reports-bucket", "arn:aws:s3:::reports-bucket/*"]
    }
  ]
}

The Principal in a trust policy can be:

PrincipalExampleMeaning
An AWS service"Service": "lambda.amazonaws.com"That service may assume the role on your behalf
A whole account"AWS": "arn:aws:iam::444455556666:root"The account is trusted; its own administrators then decide which of their users and roles may call AssumeRole
A specific user or role"AWS": "arn:aws:iam::444455556666:role/ci-deployer"Only that identity
A SAML provider"Federated": "arn:aws:iam::111122223333:saml-provider/CorpIdP"Users signed in through that identity provider
An OIDC provider"Federated": "arn:aws:iam::111122223333:oidc-provider/token.actions.githubusercontent.com"Holders of tokens issued by that provider (for example a CI pipeline)

:root in a principal ARN does not mean the root user. It means “this account”, and it is how you delegate the final decision to that account’s own IAM policies.

#AssumeRole step by step

  1. The caller asks STS

    An identity that already has some credentials (a user’s access key, another role’s session, the EC2 service, a federated token) calls sts:AssumeRole with the role ARN, a session name that will appear in logs, and optionally a duration.

  2. STS checks both sides

    The request is allowed only if both say yes: the role’s trust policy names this caller (and any conditions such as MFA or an external ID are met), and the caller’s own identity policies allow sts:AssumeRole on that role ARN. When caller and role are in the same account and the trust policy names the caller exactly, the trust policy alone is enough; across accounts both are always needed.

  3. STS returns temporary credentials

    Four things come back: an access key ID (it starts with ASIA, where a long-term key starts with AKIA), a secret access key, a session token, and an expiration time. All three parts must be sent with every request; the keys without the token are useless.

  4. The caller works as the role

    Requests signed with these credentials act as arn:aws:sts::111122223333:assumed-role/app-reader/<session-name>. Each request is evaluated against the role’s permissions policies, not the caller’s original ones: assuming a role means giving up your own permissions for the session.

  5. The session ends by itself

    At the expiration time every request with those credentials fails with an expired-token error. The caller simply assumes the role again. Nobody has to rotate or delete anything.

#How long a session lasts

  • DurationSeconds in the request: from 900 seconds (15 minutes) up to the role’s maximum. The default is 1 hour.
  • The role’s maximum session duration setting: from 1 hour (the default) to 12 hours. Asking for more than the role allows is an error, not a silent cut.
  • Role chaining, assuming a role with credentials that themselves came from a role, is capped at 1 hour, whatever the maximum says. Asking a chained session for more fails.
  • Instance profiles and Lambda use their own rotation (below), so you never set this for them.

Assuming a role with STS

An application holding a small user key assumes the role app-reader to read S3. Watch both checks STS makes, what comes back, and how each failure looks.

Scenario

Both sides allow it, so STS issues a one-hour session.

Applicationci-runner
AWS STStoken service
Role app-readertrust + permissions
Amazon S3reports-bucket
AssumeRole app-reader, 3600 s
Step 1 of 6
1

The application signs the request with the credentials it already has, names the role ARN and a session name, and asks for one hour.

#What goes wrong with AssumeRole

  • The caller is not in the trust policy. Access denied on AssumeRole itself, even if the role has every permission in the world. Fix the Principal (and check its spelling: a principal ARN for a user who has since been deleted turns into an unreadable ID and stops matching anyone).
  • The caller has no permission to call sts:AssumeRole. Common across accounts: the role in account B trusts account A, but nobody in account A was given permission to assume it.
  • The session token expired. Long-running scripts that assumed a role once at the start fail after an hour. Let the CLI or SDK assume the role from a profile (role_arn + source_profile): it refreshes credentials automatically.
  • Role chaining past one hour. A job that runs as a role and then assumes a second role cannot get more than one hour on the second one.
  • Stolen temporary credentials. They keep working until they expire. If you cannot wait, use Revoke active sessions on the role: IAM attaches an inline policy that denies everything to sessions issued before that moment (aws:TokenIssueTime), while new sessions keep working. Changing the role’s permissions policy also applies to existing sessions immediately, because permissions are evaluated on every request; the credentials are not a snapshot of permissions.

Self-checkQuestion

A developer in account 444455556666 tries to assume a role in account 111122223333 and gets access denied. The role’s trust policy allows the principal arn:aws:iam::444455556666:root. What is the most likely missing piece?

#Roles for AWS services

The slides list the most common roles: EC2 instance roles, Lambda function roles and roles for CloudFormation. All of them follow one idea: an AWS service performs actions on your behalf, so you give the service a role instead of a key.

#EC2: instance profiles

This is the exam’s favourite IAM question: an application on EC2 must read S3 or DynamoDB; what is the secure way? The answer is always attach an IAM role to the instance, never put access keys on it.

EC2 does not attach the role directly. It uses an instance profile, a small container that holds exactly one role and is what you actually attach to an instance:

  • When you create a role for EC2 in the console, an instance profile with the same name is created for you, so the two look like one thing. With the CLI or infrastructure as code you create the profile and add the role to it yourself.
  • One instance has at most one instance profile, and one instance profile holds one role. Need more permissions? Add policies to that role.
  • You can attach, replace or remove an instance profile on a running instance; no restart is needed.

How the credentials reach the application (lesson 4.3 showed the metadata side in detail):

  1. EC2 assumes the role on the instance’s behalf and puts the temporary credentials in the instance metadata service at iam/security-credentials/<role-name>.
  2. The AWS CLI and every AWS SDK look for credentials in a fixed order (the credential provider chain): command-line options, environment variables, the shared ~/.aws/credentials and ~/.aws/config files, container credentials, and last the instance profile.
  3. AWS refreshes the credentials in IMDS before they expire, and the SDK picks up the new ones. The application never sees a key, and there is nothing on disk to steal.

An application on EC2 using its instance profile

The application calls DynamoDB through the AWS SDK. It never sees a key: watch where the SDK finds credentials, how they rotate, and two ways it goes wrong.

Scenario

The SDK walks its credential chain and ends at the instance profile.

Application + SDKi-0abc
IMDS169.254.169.254
EC2 serviceassumes the role
DynamoDBorders
assume role orders-writer
Step 1 of 8
1

The instance was launched with the instance profile orders-writer. EC2 assumes the role inside it on the instance’s behalf.

#Service roles and passing a role

A service role is a role you create for a service to assume: a Lambda execution role (trusted principal lambda.amazonaws.com, permissions such as writing logs to CloudWatch and reading a DynamoDB table), a role CloudFormation uses to create a stack’s resources.

Handing a role to a service is itself a permission. To launch an instance with a profile, or create a function with an execution role, you need iam:PassRole on that role. Without that check, a developer allowed only to create Lambda functions could attach an administrator role to one and run any code with it. Limit iam:PassRole to the specific role ARNs people really need.

#Service-linked roles

A service-linked role is a role owned by a service, created for you (usually automatically) when you start using it, for example AWSServiceRoleForAutoScaling or AWSServiceRoleForElasticLoadBalancing.

  • Its trust policy and permissions are predefined by the service; you cannot edit them.
  • You can delete it only after the service no longer uses it (for example after deleting the resources that depend on it).
  • It lives under the path /aws-service-role/, so you can spot one at a glance.
Service roleService-linked role
Created byYouThe service (usually when you first use it)
PermissionsYou choose and edit themFixed by the service
ExampleLambda execution roleAWSServiceRoleForAutoScaling
Typical exam hint“give the function access to the table”“the service needs permissions to manage resources for you”

#Cross-account roles

A company with separate dev, prod and security accounts (the normal setup, Chapter 20) should not create IAM users for the same people in every account. Instead:

  1. In the target account (say prod, 111122223333), create a role ops-readonly whose trust policy trusts the source account (444455556666), and whose permissions are read-only.
  2. In the source account, allow the operations group to call sts:AssumeRole on that role’s ARN.
  3. Engineers sign in once, to the source account, and use Switch role in the console (or a CLI profile with role_arn) to work in prod with temporary credentials.

Every action in prod is logged in CloudTrail as assumed-role/ops-readonly/<session-name>, so you still know who did what.

#Requiring MFA to assume the role

For a sensitive role, the trust policy can demand that the caller signed in with MFA (lesson 2.3) recently:

{
  "Effect": "Allow",
  "Principal": { "AWS": "arn:aws:iam::444455556666:root" },
  "Action": "sts:AssumeRole",
  "Condition": {
    "Bool": { "aws:MultiFactorAuthPresent": "true" },
    "NumericLessThan": { "aws:MultiFactorAuthAge": "3600" }
  }
}

A caller using a plain access key (no MFA in the session) is denied. A CLI user can satisfy it by passing --serial-number and --token-code to assume-role, or by setting mfa_serial in the CLI profile.

#Third parties and the external ID

A monitoring or cost-analysis vendor often asks you to create a role it can assume from its AWS account. The danger is the confused deputy: the vendor assumes roles for hundreds of customers, so another customer could type your role ARN into the vendor’s web form and make the vendor read your account.

The fix is an external ID: a unique value the vendor gives you, which your trust policy requires:

"Condition": { "StringEquals": { "sts:ExternalId": "vendor-7f3a91c2" } }

The vendor always sends the external ID that belongs to the customer it is working for. A request it makes for somebody else carries a different value and is denied. The external ID is not a password (the vendor knows it); it only ties each role to the right customer.

Cross-account access with a role

An engineer in the dev account needs read-only access to prod, and a vendor needs to read your bills. Watch what each side must allow, and what an MFA condition and an external ID stop.

Scenario

Both accounts allow it and the MFA condition is met.

Dev account444455556666
Vendor account777788889999
Prod account111122223333
Engineeralice
Group opsallow sts:AssumeRole
Vendor app
AWS STS
Role ops-readonlytrusts 4444… + MFA
Role vendor-billingtrusts 7777… + external ID
Logs bucket
Cost data
Step 1 of 5
1

alice has one IAM user, in dev only. In prod there is no user for her, just a role that trusts the dev account and requires MFA.

Self-checkQuestion

A SaaS vendor assumes a role in many customers’ accounts from its own AWS account. A security review warns that one customer could trick the vendor into accessing another customer’s account. What should each customer add to the role’s trust policy?

#Federation in one page

Federation means people (or workloads) keep the identity they already have elsewhere, and AWS trusts that identity provider (IdP) to vouch for them. They never get an IAM user: after signing in at the IdP, they assume a role. The notes’ point stands: behind every federated login there is a role.

Way inWho uses itWhat happens
SAML 2.0Employees in a corporate directory (Microsoft Active Directory through ADFS, Microsoft Entra ID, Okta…)The IdP signs a SAML assertion; the browser posts it to AWS; STS AssumeRoleWithSAML returns credentials for the role the assertion names
Web identity / OIDCWorkloads and apps with a token from an OIDC provider: a GitHub Actions pipeline, a Kubernetes service account, Google or Facebook sign-inThe token goes to STS AssumeRoleWithWebIdentity; the role’s trust policy checks the token’s issuer, audience and subject
Amazon CognitoUsers of your own mobile or web appCognito exchanges the app user’s login for temporary credentials of a role; the recommended way for apps instead of calling STS directly
IAM Identity CenterThe workforce across many accounts in AWS OrganizationsOne sign-in portal (own users, or a directory/IdP connected by SAML and SCIM); permission sets become roles in each account. It is the successor to AWS Single Sign-On and AWS’s recommended way for people to access AWS

Microsoft Active Directory itself does not speak SAML; an IdP in front of it (ADFS, Entra ID) does. Chapter 20 covers Identity Center, Cognito and directory services in depth.

Federated sign-in always ends with a role

Three ways an outside identity gets into AWS. In each, the identity provider vouches for someone, and STS hands out a role’s temporary credentials.

Scenario

An employee signs in with the company directory.

Person or pipeline
Identity providerIdP
AWS STS
AWS console / API
sign in (password + MFA)
Step 1 of 4
1

The employee signs in at the company’s identity provider, for example ADFS in front of Active Directory, or Microsoft Entra ID.

#Leaked and hard-coded keys

The notes put it bluntly: if a question shows a developer who hard-coded access keys in source code or pushed them to GitHub, the answer is always to rotate (deactivate and delete) those keys and, for code on EC2 or Lambda, use an IAM role instead.

Why it is urgent: bots scan public repositories continuously and use a fresh AWS key within minutes, typically to start expensive instances for cryptocurrency mining. AWS also scans public GitHub; when it finds one of your keys it emails you, opens a support case and attaches a quarantine policy (AWSCompromisedKeyQuarantine…) to the user that denies the most abused actions. That limits the damage; it does not fix it.

What to do, in order:

  1. Deactivate the key immediately (deactivating is reversible, so do it before deleting).
  2. Find out what it did: look up the key’s ID in CloudTrail event history; look for new users, roles, keys, instances in every Region, and changes to billing.
  3. Undo the damage: delete anything the attacker created, including other access keys and users they may have added to keep a way back in.
  4. Replace it properly: an IAM role for code on AWS, Identity Center or a role for people, and only if a long-term key is truly unavoidable, a new key kept in a secrets store, never in the code.
  5. Delete the old key, and remove it from the repository history (deleting the latest commit is not enough).

#Rotating a key without downtime

An IAM user can have two access keys, exactly so you can rotate without an outage: create the second key, deploy it everywhere, check with last used that the old key is no longer used, deactivate the old one, wait, then delete it.

An access key pushed to a public repository

A developer commits a long-term access key. Watch how fast it is abused, what AWS does, and the right order of the response. Then see a rotation without downtime.

Scenario

Minutes to abuse; the response order matters.

Developer
Public repositoryGitHub
Attacker’s bot203.0.113.50
Your AWS accountIAM + CloudTrail
push config.py (key inside)
Step 1 of 8
1

A config file with an IAM user’s access key ID and secret key is pushed to a public repository.

#Auditing IAM: three tools

The notes highlight a tricky pair: the Credentials Report and Access Advisor. Add IAM Access Analyzer and you have the full toolbox.

IAM Credentials ReportIAM Access Advisor (last accessed)IAM Access Analyzer
ScopeThe whole account: every user, plus the root userOne identity: a user, group or role (also a policy, and accounts or OUs in Organizations)Resources and identities across an account or organisation
What it showsEach user’s credentials: password enabled, last used and last changed, MFA active, each access key’s status, last rotation and last useWhich services (and, for many services, which actions) the identity’s policies allow, and when each was last used, over up to 400 daysResources shared outside your account or organisation (buckets, roles, keys, queues…), unused roles, keys and permissions, policy checks, and policies generated from real activity in CloudTrail
FormatA downloadable CSV file, regenerated at most every 4 hoursShown in the console or through the APIFindings in the console, API, Security Hub
Answers“Who has no MFA? Whose keys are over 90 days old? Who has not logged in for months?”“Which of these permissions does this role actually use?”“Who outside my account can reach this bucket? Which permissions are never used?”

The exam trick from the notes: “How do you find and remove unused permissions to apply least privilege?” → IAM Access Advisor (last accessed information). For a compliance audit of all users’ credentials → Credentials Report. For resources shared with an external account → IAM Access Analyzer.

Auditing IAM with three tools

The security team asks three different questions. Each one has its own tool: see what each report looks like and which rows need action.

Scenario

Account-wide: every user’s passwords, MFA and keys.

Account111122223333
Security team
IAM
Credentials ReportCSV
Access Advisorlast accessed
Access Analyzer
Bucket shared-data
generate report
Step 1 of 3
1

Question: “Which users have no MFA, or old keys?” The answer covers every user in the account, so it needs the account-level tool.

#IAM best practices for this chapter

The slides’ checklist, updated with what AWS recommends today:

  • Do not use the root user except for the few tasks that need it (lesson 2.3). Turn on MFA for it, and have no root access keys.
  • People get temporary credentials: prefer IAM Identity Center or federation over IAM users. Where IAM users remain, one person = one user, never shared.
  • Workloads get roles: instance profiles, Lambda execution roles, task roles, OIDC for CI. Long-term access keys only when nothing else works, and then rotated.
  • Permissions go to groups (for users) and roles, not to individual users one by one.
  • Least privilege: start from AWS managed policies if you must, then narrow them with Access Advisor and Access Analyzer’s generated policies.
  • A strong password policy and MFA for everyone who signs in.
  • Conditions for extra safety: MFA on sensitive roles, external IDs for third parties, iam:PassRole limited to named roles.
  • Audit regularly with the Credentials Report, Access Advisor and Access Analyzer; log everything with CloudTrail.
  • Never share IAM users or access keys, and never put keys in code, images or user data.

#How it fits together in a design

A typical company setup after this chapter:

  1. Employees sign in through IAM Identity Center, connected to the company directory. Each gets permission sets that appear as roles in the dev, prod and security accounts.
  2. Web servers in prod run with an instance profile whose role can read one S3 bucket and one Secrets Manager secret; the launch template requires IMDSv2 (lesson 4.3).
  3. Lambda functions each have their own execution role with only what that function needs; developers can pass only those roles (iam:PassRole scoped by ARN).
  4. The deployment pipeline assumes a deploy role through OIDC; no key exists in the pipeline.
  5. A cost-monitoring vendor assumes a read-only cross-account role with an external ID.
  6. Every month the security team reviews the Credentials Report, trims roles with Access Advisor, and acts on Access Analyzer findings.

No long-term key exists anywhere in that picture, so there is nothing to leak.

#Try it yourself

# 1. Who am I right now? (works even with no other permissions)
aws sts get-caller-identity

# 2. Create a role EC2 can assume, give it read-only S3, and wrap it in an instance profile
cat > trust-ec2.json <<'EOF'
{"Version":"2012-10-17","Statement":[{"Effect":"Allow",
 "Principal":{"Service":"ec2.amazonaws.com"},"Action":"sts:AssumeRole"}]}
EOF
aws iam create-role --role-name app-reader --assume-role-policy-document file://trust-ec2.json
aws iam attach-role-policy --role-name app-reader \
  --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
aws iam create-instance-profile --instance-profile-name app-reader
aws iam add-role-to-instance-profile --instance-profile-name app-reader --role-name app-reader

# 3. Attach it to a running instance (no restart), then on the instance check who it is
aws ec2 associate-iam-instance-profile --instance-id $ID --iam-instance-profile Name=app-reader
aws sts get-caller-identity     # run on the instance: ...:assumed-role/app-reader/i-0...

# 4. Assume a role by hand and look at the temporary credentials
aws sts assume-role --role-arn arn:aws:iam::111122223333:role/ops-readonly \
  --role-session-name alice-test --duration-seconds 900
# -> AccessKeyId (ASIA...), SecretAccessKey, SessionToken, Expiration

# 5. Or let the CLI assume it and refresh it for you: ~/.aws/config
# [profile prod-ro]
# role_arn = arn:aws:iam::111122223333:role/ops-readonly
# source_profile = default
# mfa_serial = arn:aws:iam::444455556666:mfa/alice
aws s3 ls --profile prod-ro

# 6. Credentials Report: generate, then download and decode the CSV
aws iam generate-credential-report
aws iam get-credential-report --query Content --output text | base64 -d > report.csv

# 7. Access Advisor: which services did this role use, and when?
JOB=$(aws iam generate-service-last-accessed-details \
  --arn arn:aws:iam::111122223333:role/app-reader --query JobId --output text)
aws iam get-service-last-accessed-details --job-id $JOB

# 8. A key leaked: deactivate it first, then find out what it did
aws iam update-access-key --user-name ci-old --access-key-id AKIAEXAMPLE --status Inactive
aws cloudtrail lookup-events --lookup-attributes AttributeKey=AccessKeyId,AttributeValue=AKIAEXAMPLE

#Summary

Key takeaways

  • An IAM role is an identity with permissions but no long-term credentials. Whoever its trust policy allows can assume it and gets temporary credentials from STS: access key ID (ASIA…), secret key, session token, expiration.
  • Trust policy = who may assume (the only place with a Principal). Permissions policies = what the session may do. Across accounts the caller also needs sts:AssumeRole in its own account.
  • Sessions last 15 minutes to the role’s maximum (1 hour by default, up to 12 hours); role chaining is capped at 1 hour. Expired tokens fail; assume again. Revoke active sessions kills stolen ones early.
  • EC2 gets permissions through an instance profile (one role per profile); credentials come from IMDS and rotate automatically. An old key in ~/.aws/credentials or environment variables overrides the role.
  • Service roles (Lambda execution role, CloudFormation role) are yours to define; giving one to a service needs iam:PassRole. Service-linked roles are predefined and owned by the service.
  • Cross-account: a role in the target account trusting the source account, plus permission in the source. Add an MFA condition for sensitive roles and an external ID for third parties (confused deputy).
  • Federation (SAML 2.0, OIDC web identity, Cognito, IAM Identity Center) always ends with assuming a role. Identity Center is AWS’s recommended way in for people.
  • Hard-coded or leaked keys: deactivate, investigate in CloudTrail, clean up, replace with a role, delete. Rotate long-term keys with the two-key method.
  • Credentials Report = account-wide CSV of every user’s credentials (MFA, key age). Access Advisor = services an identity can use and when it last used them, the tool for removing unused permissions. Access Analyzer = external sharing and unused access findings, and policy generation.