For the past four days, we've repeated "use Role and its temporary credentials, not IAM User's long-term access key." Today we dive into how that Role is "assumed" — the mechanism of STS (Security Token Service) that issues credentials, how external identities (federation) exchange for AWS credentials, and why cross-account delegation demands blocking the Confused Deputy attack. The deepest differentiation in SCS-C03's Domain 4 lies here — AssumeRole trust relationships, role chaining constraints, and mandatory Confused Deputy prevention.
Core one-liner first: STS issues expiring temporary credentials (AccessKeyId + SecretAccessKey + SessionToken), and "who can assume this role" is decided by the role's trust policy.
Long-term IAM User credentials are 2 keys (AccessKeyId, SecretAccessKey), but STS temporary credentials are 3.
| Component | Role |
|---|---|
| AccessKeyId | Identifier |
| SecretAccessKey | Signing key |
| SessionToken | Temporary session proof (doesn't exist in long-term keys) |
These credentials have an expiration time. AssumeRole defaults to 1 hour, maximum 12 hours (within role's MaxSessionDuration). After expiration, you must get new ones. Even if exposed, damage is time-limited — the fundamental reason temporary credentials are safer than long-term keys.
Key STS APIs:
| API | Purpose |
|---|---|
AssumeRole | Assume an IAM Role in same/different account (most common) |
AssumeRoleWithSAML | SAML 2.0 IdP federation (e.g., AD FS, Okta) |
AssumeRoleWithWebIdentity | OIDC federation (e.g., Cognito, Google, GitHub Actions) |
GetSessionToken | MFA-applied temporary credentials (IAM User) |
GetFederationToken | Legacy federation |
💡 Related Theory: EC2 instance profiles, Lambda execution roles, ECS task roles all internally use STS AssumeRole. For EC2, the instance metadata service (IMDS) auto-issues and refreshes temporary credentials. No need to embed keys in code. IMDSv2 (token-based) must be enforced to block SSRF attacks that steal metadata — a classic exam topic and was central to the Capital One incident.
A Role holds two policies. This distinction is the most confusing in IAM.
sts:AssumeRole permissible targets). Contains Principal = resource-based policy.// Trust policy: principals in account 111122223333 can assume this role
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::111122223333:root"},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {"sts:ExternalId": "unique-secret-2026"},
"Bool": {"aws:MultiFactorAuthPresent": "true"}
}
}]
}Cross-account AssumeRole succeeds only when both align:
sts:AssumeRole Allow (target role ARN specified)⚠️ Trap: Using
"arn:aws:iam::111122223333:root"in trust policy Principal means "entire account 111122223333 can assume this role" — not just the root user. However, practically, only principals in that account holdingsts:AssumeRolepermission can assume it (condition 2). The intuition "root Principal is risky" is half right — account delegation is normal, but internal permission control is that account's responsibility.
Without creating IAM Users, accessing AWS via corporate directory (AD) or social/OIDC identity is federation.
[ SAML Federation Flow ]
User → Enterprise IdP(AD FS/Okta) login
→ IdP issues SAML assertion(role ARN included)
→ AWS STS AssumeRoleWithSAML
→ Temporary credentials issued
→ Access AWS resources
Today's recommended approach: IAM Identity Center (formerly AWS SSO). Define permission sets in multi-account environments, integrate with external IdPs (Okta, Entra ID, etc.), and users get temporary credentials across multiple accounts and roles via single login. Eliminates the anti-pattern of creating IAM Users per account.
Mobile apps (Cognito), GitHub Actions, Kubernetes (EKS IRSA) use OIDC for AssumeRoleWithWebIdentity. Particularly GitHub Actions OIDC matters in both exam and practice — instead of embedding long-lived access key in GitHub secrets, GitHub's OIDC token assumes a role, eliminating key exposure risk.
🔍 Deep Dive: GitHub Actions OIDC trust policy sets
token.actions.githubusercontent.comas OIDC provider, and Condition limits withtoken.actions.githubusercontent.com:subto specific repo·branch only. Loose conditions likerepo:org/*let any org repo assume the role — risky. Tighten torepo:org/repo:ref:refs/heads/main. EKS IRSA uses the same OIDC principle, mapping pods to roles.
Assuming another role while already in one role is role chaining. Account A → assume role X → assume role Y with role X credentials.
Two key constraints in exams:
MaxSessionDuration is 12 hours, a chained session tops at 1 hour.GetSessionToken.💡 Related Theory: Role chaining obscures permission tracking and is generally avoided. Prefer session policy or direct AssumeRole. However, "hub account role → spoke account access" pattern in cross-account is legitimate chaining. Traceability is supplemented by CloudTrail's
assumedRolesession name (setsts:RoleSessionNamemeaningfully).
Today's security core. Confused Deputy (confused intermediary) is when an authorized principal (intermediary) is tricked into using permissions on behalf of a third party.
Say monitoring SaaS (e.g., Datadog) AssumeRoles into your account. SaaS uses the same SaaS account for all customers. If trust policy only checks "SaaS account, allow," a malicious user could impersonate your account ID to SaaS and gain your resources.
Solution: ExternalId — SaaS issues you a unique secret that you embed in the trust policy's Condition.
{
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::SAAS-ACCOUNT-ID:root"},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {"sts:ExternalId": "your-unique-external-id-xyz"}
}
}SaaS passes this ExternalId when assuming your role; customers' ExternalIds won't work. Isolation is guaranteed.
When a service principal (e.g., CloudWatch, S3, SNS) uses your role or resource, the same attack is possible. Use aws:SourceArn (or aws:SourceAccount) to enforce "only this specific resource triggers this."
{
"Effect": "Allow",
"Principal": {"Service": "sns.amazonaws.com"},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {"aws:SourceAccount": "111122223333"},
"ArnLike": {"aws:SourceArn": "arn:aws:sns:us-east-1:111122223333:my-topic"}
}
}🎯 Scenario: "Third-party backup vendor needs cross-account S3 access." Answer pattern: ① never give vendor IAM User access keys (absolute ban) ② create IAM Role + trust policy with vendor account + ExternalId ③ permission policy restricted to needed buckets/Actions (least privilege). Without ExternalId, another vendor customer using the same vendor could access our resources — Confused Deputy hole. This pattern applies uniformly to Datadog, New Relic, PagerDuty, all third-party integrations.
⚠️ Trap: ExternalId isn't a "password" — even if guessable, it blocks the attack. The key is customer doesn't set ExternalId; vendor issues it. Customer-set IDs could collide with others or be predictable, breaking isolation. Trap answer: "Customer freely sets ExternalId" — wrong.
# Assume role — returns 3 credential types
aws sts assume-role \
--role-arn arn:aws:iam::444455556666:role/CrossAccountReadRole \
--role-session-name security-audit-2026 \
--external-id your-unique-external-id-xyz \
--duration-seconds 3600
# Use returned credentials for subsequent calls (after env vars set)
export AWS_ACCESS_KEY_ID=ASIA...
export AWS_SECRET_ACCESS_KEY=...
export AWS_SESSION_TOKEN=... # SessionToken mandatory for temp credentials
aws sts get-caller-identity # Verify who you are now📚 Case Study: The 2019 Capital One incident's essence: ① WAF (SSRF defense insufficient) ② IMDSv1 let EC2 temp credentials be stolen ③ those credentials accessed S3. Temporary credentials are safe, but the path to steal them (SSRF → IMDS) being open neutralizes that. Hence IMDSv2 enforcement + least-privilege instance role + WAF bundle in exam. Temporary credentials are "exposure damage is time-limited," but blocking exposure paths is separate.
Three essentials today. First, STS issues expiring temporary credentials (with SessionToken), and EC2/Lambda/ECS/federation all run on this — replace long-key code/CI anti-patterns with OIDC and instance roles. Second, AssumeRole is two axes: trust policy (who) and permission policy (what) — cross-account needs both sides allowing. Third, cross-account delegation must block Confused Deputy via ExternalId (third-party) or aws:SourceArn (AWS service).
Tomorrow closes Week 1 with integrated scenarios. "Why is access denied despite having permissions," "safely delegate to third parties," "convert long-term keys to temporary credentials" — real-world problems synthesizing everything Week 1 taught. We cement this week's IAM thinking framework.
Click a choice to reveal the answer and explanation.
Question 1
What is the most fundamental reason STS temporary credentials are more secure than long-lived IAM user access keys?
Question 2
A third-party monitoring SaaS uses the same SaaS account to AssumeRole into many customers' AWS accounts. What is the key mechanism that stops one customer from assuming another customer's role through the SaaS?
Question 3
Which statement about role chaining (assuming another role while already in an assumed role) is correct?
Question 4
When configuring GitHub Actions to assume an AWS role without long-lived access keys, which trust policy setting matters most for security?
Question 5
Which control should you apply most directly to prevent an incident where temporary credentials granted to an EC2 instance are stolen through an SSRF attack?