On SAA, IAM could be summarized in four words: "User, Group, Role, Policy." On Pro, that doesn't cut it. A single scenario features an IAM User policy, an IAM Role policy, a resource-based policy, a Permission Boundary, an SCP, and a session policy all at once — and questions asking "can this user call this action" appear frequently. To get the answer right, you must know the 6-level priority order of permission evaluation.
In this article we peel back IAM's surface one more layer and look at the evaluation engine where decisions actually happen underneath. We'll also cover why STS and Federation are "the spine of enterprise multi-account environments," and what broke in incidents like Capital One and Uber.
When you type aws s3 ls s3://my-bucket, AWS evaluates the following 6 policies simultaneously.
[1. SCP (Service Control Policy)] ← Organizations level; deny beats everything
↓
[2. Permission Boundary] ← Maximum permission ceiling for an IAM entity (User/Role)
↓
[3. Identity-based Policy] ← Policies attached to Users, Groups, Roles
↓
[4. Resource-based Policy] ← S3 bucket policies, KMS key policies, etc.
↓
[5. Session Policy] ← Attached inline during AssumeRole/GetFederationToken
↓
[6. VPC Endpoint Policy] ← Applies only to traffic through a VPC Endpoint
The evaluation rules are simple but exact.
🔍 Deeper dive: SCPs and Permission Boundaries are both guardrails that set a "maximum ceiling of permissions," but their scopes differ. An SCP attaches at the OU or Account level in Organizations and applies to every IAM entity inside that account (including root). A Permission Boundary attaches only to a specific IAM User or Role. That is, an SCP enforces "this account can never use regions other than us-east-1," while a Permission Boundary enforces "this developer Role can never call IAM or Organizations APIs." Both are , not Allows.
Click a choice to reveal the answer and explanation.
Question 1
A company operates 50 accounts in Organizations. In one developer account, IAM User Alice was granted a policy with `s3:GetObject` permission but cannot retrieve objects. CloudTrail records `AccessDenied`. What is the most likely cause?
Question 2
A SaaS company needs to collect data from its customers' AWS accounts. What is the safest and most standard method?
Question 3
A company wants to let developers "freely create their team's Lambda Roles but never be able to grant IAM or Organizations permissions." What is the most suitable method?
Question 4
A company deploys to AWS Lambda from GitHub Actions. The security team demands "do not store long-lived AWS Access Keys in GitHub Secrets." What should they do?
Question 5
A company operates 200 accounts in Organizations, and employees must access multiple accounts via SSO. Active Directory is the identity source. What is the most appropriate solution?
Question 6
A company wants to find every external Principal that can access its S3 buckets cross-account. What is the most efficient method?
Question 7
In a system, a user cannot retrieve an S3 object. Identity Policy is `s3:*`, the bucket policy Allows, and the KMS key policy is missing. CloudTrail shows `KMS.NotFoundException`. What is the problem?
💡 Related theory: This evaluation structure is a hybrid of Role-Based Access Control (RBAC, NIST RBAC standard INCITS 359-2004) and Attribute-Based Access Control (ABAC, NIST SP 800-162). The
Conditionclause in IAM Policies implements ABAC (aws:RequestTag/Project=Phoenix), and policy attachment implements RBAC. The reason AWS fully embraced ABAC in 2018 is that when account counts explode, RBAC alone causes policy management to explode. 100 projects × 4 environments (dev/stg/prod/test) = 400 Roles needed, but with ABAC it's done with 1 Role + tag-based conditions.
🎯 Scenario: "A data analyst complains they cannot access an S3 bucket. The IAM policy Allows
s3:*, and the bucket policy also Allows. What's the cause?" — The answer is usually that an SCP restricts the region or service, or a Permission Boundary allows only s3:GetObject, or the bucket is KMS-encrypted and the KMS key policy Denies. The Pro exam frequently asks these "why doesn't it work" debugging scenarios.
Within the same account, either the Identity Policy OR the Resource Policy Allowing is sufficient, but when accessing from another account, both sides must Allow.
[Account A: user Alice] [Account B: S3 Bucket]
Identity Policy: Allow s3:GetObject Bucket Policy: Allow Alice from A
↓ ↓
Both sides Allow → access granted
This is where incidents frequently happen in fintech and financial-sector multi-account environments. Allow only one side and access is blocked; Allow both sides and the risk of permission leakage grows. That's why IAM Access Analyzer launched in 2019 to automatically check "can an external account access this resource."
You saw the word AssumeRole in SAA too, but on Pro you must distinguish all 5 STS APIs.
| API | Caller | Credential validity | Use case |
|---|---|---|---|
AssumeRole | IAM User/Role | 15 min - 12 hours | Cross-account access, EC2 Instance Role |
AssumeRoleWithSAML | SAML IdP (AD FS, Okta) | 15 min - 12 hours | Enterprise SSO |
AssumeRoleWithWebIdentity | OIDC (Google, Facebook, GitHub Actions) | 15 min - 12 hours | Mobile apps, CI/CD |
GetFederationToken | IAM User | 15 min - 36 hours | Temporary permissions for external users |
GetSessionToken | IAM User | 15 min - 36 hours | MFA-based session hardening |
🔍 Deeper dive: The heart of temporary credentials is the session token. Calling with only a regular
AccessKeyId+SecretAccessKeygives you indefinitely valid credentials, but adding aSessionTokenenforces the expiration STS issued. Temporary credentials are also a JWT-like structure issued internally by STS, transmitted by including the SessionToken as an extra header in the AWS Signature V4 signature. This ties directly into the mechanism by which IMDSv2 blocks SSRF attacks (the EC2 metadata service issues only temporary credentials, and IMDSv2 mandates a PUT token).
📚 Case study: The Capital One incident of July 2019. An attacker exploited an SSRF vulnerability to access
http://169.254.169.254/latest/meta-data/iam/security-credentials/and stole the temporary credentials of an EC2 Instance Role. Those credentials had broad access to S3 buckets, and data of 106 million people was exfiltrated. As a direct result of this incident, AWS launched IMDSv2 (November 2019) and IAM Access Analyzer (December 2019), and since 2024 has enforced IMDSv2 by default on new EC2 instances. DOJ indictment.
When creating a Role, you define two policies at once.
// Trust Policy (who can assume this Role)
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:root"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "unique-external-id-xyz"
}
}
}]
}
// Permission Policy (what the holder of this Role can do)
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::data-bucket/*"
}]
}ExternalId is a mechanism to prevent the "confused deputy" attack. If Company A allows Company B to assume a Role in A's account, a malicious third party C could impersonate B's ID to assume A's Role. ExternalId is a secret A shared only with B, so without knowing it, C's assume fails.
⚠️ Pitfall: On the Pro exam, when a scenario says "we need to let a third-party SaaS access our AWS account," the answer is almost always Cross-Account Role + ExternalId. Creating an IAM User and handing out access keys is a wrong answer (access keys are static and hard to revoke). SaaS providers like Datadog, Snowflake, and Splunk all use this pattern.
Enterprises have their own identity systems (Active Directory, Okta, Azure AD). Rather than recreating these in AWS each time, you federate them.
SAML (Security Assertion Markup Language) 2.0 is an XML-based authentication and authorization protocol adopted as an OASIS standard in 2005.
[User] → [AD FS / Okta]
↓ SAMLResponse (XML, signed)
[AWS Sign-in Endpoint]
↓ AssumeRoleWithSAML
[STS: issues temporary credentials]
↓
[Use AWS Console / API]
💡 Related theory: SAML 2.0 is based on XML Digital Signature (W3C XMLDSIG) and XML Encryption (W3C XMLENC). The IdP signs the SAMLResponse with the private key of an X.509 certificate, and the SP (AWS) verifies with the public key. However, due to the complexity of XML parsing, SAML has a security issue unique to it called the XML Signature Wrapping attack. At USENIX in 2012, this vulnerability was found in 11 SaaS/SAML implementations, and AWS was one of them. AWS subsequently patched it with strict XML schema validation.
OIDC is a standard that layers authentication on top of OAuth 2.0. Being JSON-based, it's lighter than SAML and well-suited for mobile and SPAs.
[User] → [Google / GitHub / Cognito]
↓ id_token (JWT)
[AssumeRoleWithWebIdentity]
↓
[STS: temporary credentials]
GitHub Actions uses this method to deploy to AWS. Once you configure the OIDC trust, GitHub Actions workflows obtain a temporary token each time and deploy without any long-lived AWS credentials. Since GitHub launched OIDC in 2021, the practice of "storing AWS Access Keys in GitHub Secrets" has rapidly disappeared.
📚 Case study: The 2017 Uber data breach was caused by an employee pushing an AWS Access Key to a public GitHub repository. Data of 57 million users was leaked. GitHub subsequently introduced secret scanning, and AWS automatically detects exposed keys with IAM Access Analyzer. Today, using OIDC with GitHub Actions makes this kind of incident outright impossible (there are no static credentials at all).
AWS Identity Center is a SAML-based multi-account SSO solution. Integrated with Organizations, one login lets you freely switch among multiple accounts and multiple Roles in the console.
[Employee] → [Identity Center login]
↓
[Select Permission Set]
↓
[AssumeRole into target account with temporary credentials]
↓
[Use Console / CLI]
A Permission Set is a concept unique to Identity Center — a template that "deploys the same IAM Role to multiple accounts in bulk." For example, define a "Developer Permission Set" and Identity Center automatically creates and maintains the identical Role in every dev account.
🔍 Deeper dive: Internally, Identity Center supports two identity sources: ① Identity Center's own directory (small scale) ② an external IdP (AD/Okta/Azure AD via SAML or SCIM). SCIM (System for Cross-domain Identity Management, RFC 7644) is a standard protocol that automatically synchronizes user additions and deletions from an external IdP like Azure AD into AWS. Without it, you get the ghost-user problem where an employee leaves the company but their permissions stay alive in AWS.
A scenario large organizations frequently face: "We want developers to create their own Lambda Roles directly. But granting developers IAM Admin permissions is dangerous."
The answer is Permission Boundary. The administrator attaches a constraint alongside: "you may create IAM Roles, but you can never grant permissions beyond this boundary."
// Policy granted to developers
{
"Effect": "Allow",
"Action": "iam:CreateRole",
"Resource": "*",
"Condition": {
"StringEquals": {
"iam:PermissionsBoundary": "arn:aws:iam::123456789012:policy/DevBoundary"
}
}
}This forces developers to attach DevBoundary whenever they create an IAM Role, and permissions beyond that boundary are automatically blocked.
🎯 Scenario: A game company wants to give 100 developers a free-form Lambda development environment. Developers create Roles and grant permissions themselves, but IAM Admin, Organizations, and Billing permissions are absolutely off-limits. — The answer is to force-attach a Permission Boundary that denies IAM, Organizations, and Billing APIs. SCP alone is insufficient because a developer might grant admin permissions to their own Role. The orthodox approach is the dual guardrail of SCP + Permission Boundary.
| Symptom | Policy to suspect | Diagnosis method |
|---|---|---|
| All API calls blocked | SCP | Check SCPs in the Organizations console |
| Only a specific service blocked | Permission Boundary, SCP | IAM simulator, Access Analyzer |
| Only a specific region blocked | SCP's Condition: aws:RequestedRegion | Inspect the SCP JSON directly |
| Cross-account access blocked | Both sides' policies | CloudTrail's errorMessage |
| Cannot read KMS-encrypted objects | KMS Key Policy | KMS console, kms:Decrypt policy |
| AssumeRole fails | Trust Policy, ExternalId | CloudTrail's sts:AssumeRole events |
You can simulate policies from the CLI.
# Simulate whether a specific user can call a specific action
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789012:user/Alice \
--action-names s3:GetObject \
--resource-arns arn:aws:s3:::data-bucket/file.txt| Item | RBAC | ABAC |
|---|---|---|
| Definition | Grant policies per Role | Grant policies per attribute (tag) |
| Suitable scale | Small (< 50 Roles) | Large |
| Policy count | Explodes with roles × environments | Single policy + tags |
| Permission leakage risk | Mistakes when duplicating policies | When tags are missing |
| AWS adoption | From the beginning | ABAC capability added in 2018 |
🔍 Deeper dive: The core Condition Keys of AWS ABAC are
aws:RequestTag/key(tags of the resource the requester is about to create),aws:ResourceTag/key(tags of an already-existing resource), andaws:PrincipalTag/key(tags of the requester themselves). Combining these three, you can express "a user tagged Project=Phoenix can start/stop only EC2 instances tagged Project=Phoenix" in a single policy. Even with 100 projects, the policy stays the same. However, a missing tag means blocked permissions, so it must be enforced together with Tag Policies (an Organizations feature).
IAM was roughly "policy = JSON" on SAA, but on Pro you must handle it at the depth of a 6-layer evaluation engine. SCP, Permission Boundary, Identity Policy, Resource Policy, Session Policy, and VPC Endpoint Policy operate simultaneously; an explicit deny beats every allow; and cross-account requires consent from both sides.
STS is the issuer of temporary credentials, and you must distinguish the five variants of AssumeRole (AssumeRole, WithSAML, WithWebIdentity, GetFederationToken, GetSessionToken) by scenario keywords. Federation splits into SAML (enterprise) and OIDC (mobile/CI-CD), and Identity Center is the standard answer for multi-account SSO. Finally, Permission Boundary is the safety valve of delegation, providing developers "freedom and safety at the same time."
In the next article we look at the channels where traffic flows on top of the permissions IAM decided — VPC, subnets, routing, and security groups — at Pro depth.