AWS Identity and Access Management (IAM) is the gatekeeper of every AWS API call, and on SOA-C02 the bar is no longer "design the right IAM model" — that is SAA-C03 territory — but "operate IAM at scale". A SysOps engineer is the person who runs the break-glass when an executive locks themselves out of the management account at 11pm, audits who had write access to the logs bucket for an SOC-2 review, debugs why a developer keeps getting AccessDenied despite a freshly attached policy, validates that the new permissions boundary still lets the deployment role assume into the staging account, and rotates the SAML metadata when the corporate identity provider rolls a signing certificate. Every one of those activities pulls on a different IAM policy type, condition key, or audit tool — and the SOA-C02 exam tests them with operational, not architectural, precision.
This guide covers IAM policies, MFA, SCPs, permissions boundaries, federation, and access troubleshooting from the SOA-C02 angle. You will see the five policy types laid out with crisp scope rules; the simplified evaluation logic that the exam actually tests; how to use the IAM Policy Simulator and IAM Access Analyzer to debug a denied action without making real API calls; the password policy and MFA enforcement patterns the audit team will ask for; how SAML, OIDC, and Cognito federation flow through STS:AssumeRoleWithSAML and friends; and the recurring SOA-C02 troubleshoot scenarios — "developer cannot read S3 bucket" and "auditor wants the list of every principal who could write to the logs bucket". The goal is that by the end of this note you can look at any AccessDeniedException and walk the evaluation chain in your head: explicit deny, organizations SCP, resource policy, identity policy, permissions boundary, session policy, condition keys.
Why IAM Policies Sit at the Heart of SOA-C02 Domain 4
Domain 4 (Security and Compliance) is worth 16 percent of the SOA-C02 exam, and Task Statement 4.1 explicitly lists six skills around IAM: implement IAM features (password policies, MFA, roles, SAML, federated identity, resource policies, policy conditions), troubleshoot and audit access issues using CloudTrail / IAM Access Analyzer / IAM Policy Simulator, validate SCPs and permissions boundaries, review Trusted Advisor security checks, validate region and service compliance, and implement secure multi-account strategies via Control Tower and AWS Organizations. Five of those six skills land squarely on this topic; the sixth (multi-account strategies) is covered in detail in the Multi-Account Organizations note but reuses the IAM policy types you learn here.
The SOA-C02 framing is operational. SAA-C03 asks "which IAM policy type should the architect use for cross-account read-only access?" — a design choice. SOA-C02 asks "the developer was assigned the S3ReadOnly managed policy yesterday, but aws s3 ls s3://prod-logs/ still returns AccessDenied — what do you check?" That answer requires walking the evaluation chain: is there an explicit deny anywhere? Is the developer in an OU with a restrictive SCP? Does the bucket policy explicitly allow the principal? Does the developer's permissions boundary cap S3 actions? Did the developer assume a role with a session policy that scoped down further? Did the request fail a condition key like aws:SourceVpc or aws:MultiFactorAuthPresent? IAM policies, MFA, SCPs, and access troubleshooting is the exam topic where every later security and operations workflow plugs in.
- IAM user: a long-lived identity with a username and either console password or programmatic access keys. SysOps avoids creating IAM users for humans where federation is available.
- IAM group: a named bundle of IAM users that share attached identity policies. Groups simplify policy management; you cannot attach a role to a group.
- IAM role: a temporary identity assumed by users, services, or federated principals via STS. Roles have a trust policy (who can assume) and identity policies (what the assumed session can do).
- Identity policy: a JSON document attached to a user, group, or role describing what the principal can do. Most permissions in AWS are granted via identity policies.
- Resource policy: a JSON document attached to a resource (S3 bucket policy, KMS key policy, SQS queue policy, Lambda resource policy, IAM role trust policy) describing who can do what to that resource.
- Service control policy (SCP): an AWS Organizations policy attached to an OU or account that defines the maximum permissions any principal in scope can ever have. SCPs do not grant; they only constrain.
- Permissions boundary: an advanced IAM feature that caps the maximum permissions a user or role can have, even if identity policies grant more. Used to delegate IAM admin safely.
- Session policy: an inline JSON policy passed at
AssumeRole(orGetFederationToken) time that further scopes down a session. Cannot grant beyond the role's identity policies. - Condition key: a JSON key inside a
Conditionblock (aws:SourceIp,aws:MultiFactorAuthPresent,aws:SourceVpc,aws:RequestedRegion,aws:PrincipalTag/...) that gates a statement based on request context. - STS (Security Token Service): the AWS service that issues temporary credentials via
AssumeRole,AssumeRoleWithSAML,AssumeRoleWithWebIdentity, andGetFederationToken. - IAM Policy Simulator: a console and API tool that evaluates whether a given principal can perform a given action against a given resource, without calling the actual API.
- IAM Access Analyzer: a service that uses automated reasoning to identify resources shared with external principals and to validate policy correctness.
- Reference: https://docs.aws.amazon.com/IAM/latest/UserGuide/introduction.html
白話文解釋 IAM Identities, Policies, and Access Auditing
The IAM vocabulary stacks fast. Three different analogies make the moving pieces stick.
Analogy 1: The Office Building Security Guard Hierarchy
IAM is the multi-tier security guard hierarchy of a large office building. Identity policies are the employee badges that say "this person is a software engineer in team Alpha and may enter floors 3, 5, and the cafeteria". Resource policies are the door locks on each room — the boardroom door has its own access list saying "only board members and the CEO may enter, regardless of whether their badge says they should". SCPs are the building owner's master rules posted at the lobby: "no employee, visitor, contractor, or even the CEO may enter the rooftop helipad" — the rules apply to everyone in the building, including the senior management. Permissions boundaries are the HR-issued contractor caps that say "this contractor was hired to work on Floor 5 only — no matter what badges they accumulate over time, they may never enter Floor 8 or the data center". Session policies are the temporary visitor passes issued at the front desk for a one-day audit — they restrict the visitor to specific rooms even if the host's badge is broader.
When someone tries to enter a room, the guard checks all four layers in order: building rules first (SCP — is the room off-limits to everyone?), then the door lock (resource policy — does the room itself allow them?), then the badge (identity policy — does their employee badge grant entry?), then the visitor pass and contractor cap (session policy and permissions boundary — does the temporary or long-term cap allow it?). If any layer says "no", entry is denied. Explicit deny anywhere is like a red lockout sticker on the door — it overrides every other rule on the entire building.
Analogy 2: The Conference Badge Tiers
A multi-vendor cloud conference uses a layered badge system. The conference organiser's policy (SCP) is printed on every badge: "no badge of any tier grants entry to the speakers' green room". On top of that, you receive a base attendee tier badge (identity policy) that allows session rooms 1 through 5. If you upgrade to the VIP tier (managed policy attached to a group), you get rooms 1 through 10 plus the VIP lounge. The room itself can have its own door check (resource policy) — for example, a sponsor demo room may say "only platinum sponsors and badge tier 3+", regardless of how many room-rights your tier nominally has.
When you sign up to moderate a panel (assume a role), you get a temporary moderator wristband (STS session credentials) that lets you control the AV system in that room only for 60 minutes. If the conference uses MFA (multi-factor authentication), you also have to scan your phone QR code at the door — an aws:MultiFactorAuthPresent condition. Bring no phone, no entry. Federation (SAML/OIDC) is when you arrive with a partner-org badge that the conference organisers trust — the front desk converts your partner badge into a one-day conference badge automatically.
Analogy 3: The Building Access Card With Time Locks
A modern building uses smart access cards rather than physical keys. Each card has multiple permissions encoded: base employee permissions (identity policy), department-level rules (group membership), time-of-day locks (aws:CurrentTime condition key), location locks (aws:SourceIp for the office network, aws:SourceVpc for the secure vault), and MFA requirements for sensitive rooms (aws:MultiFactorAuthPresent). When you tap the card on a reader, the system evaluates every layer — and any single deny rejects entry. The HR system audit log (CloudTrail) records every tap with timestamp, room, decision (allow/deny), and source IP. The security analyst reviewing access (IAM Access Analyzer + Policy Simulator) can either look at the logs after the fact or simulate "what would happen if employee X tapped on door Y at 3am from the parking lot?" before granting the access.
For SOA-C02 the office building security guard hierarchy analogy is most useful when a question mixes multiple policy types — SCP plus identity plus resource plus permissions boundary. The smart access card analogy is more useful when condition keys (aws:MultiFactorAuthPresent, aws:SourceIp, aws:RequestedRegion) are the focus. The conference badge tier analogy fits federation and assumed role questions naturally. Reference: https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html
IAM Identity Types: Users, Groups, Roles, and Federated Principals
Before policies make sense, the identities they apply to need a precise mental model.
IAM users
An IAM user is a long-lived identity inside one AWS account. Each user has a unique ARN (arn:aws:iam::123456789012:user/alice), an optional console password protected by an account password policy, and up to two access key pairs for programmatic access. SOA-C02 expects you to know that IAM users are the legacy pattern: a mature SysOps environment uses federated identity for humans and IAM roles for everything else, leaving IAM users only for emergency break-glass and a small set of service accounts where federation is impossible.
IAM groups
An IAM group is a named bundle of IAM users that share identity policies. Attaching policies to a group is the only scalable way to manage permissions for a fleet of IAM users. Three things SOA-C02 expects you to know about groups:
- A user can belong to up to 10 groups (default account quota; can be raised).
- Groups cannot be nested — there are no groups-of-groups in IAM.
- You cannot attach a role to a group, only identity policies. To give a group the right to assume a role, attach an identity policy with
sts:AssumeRoleon the role's ARN.
IAM roles
An IAM role is a temporary identity, assumed by a principal via STS. A role has two policies attached:
- Trust policy (the role's resource policy) — defines who can assume the role. This is a JSON document with
Principal: { ... }listing AWS accounts, services, federated identity providers, or specific users/roles. - Identity policies (one or more attached) — define what the assumed session can do. These are normal IAM policies attached to the role.
When the role is assumed, STS issues temporary credentials (access key, secret key, session token) with a configurable duration — 15 minutes minimum, 12 hours maximum for sts:AssumeRole and sts:AssumeRoleWithSAML (with role chaining capped at 1 hour). The session inherits the role's identity policies, optionally further scoped by a session policy passed at AssumeRole time.
Roles are the SysOps default for:
- EC2 instance profiles — instance gets credentials at startup automatically.
- Lambda execution roles — function gets credentials per invocation.
- Cross-account access — user in account A assumes a role in account B.
- Federated user access — corporate IdP user assumes a role via SAML.
- AWS service-linked roles — pre-defined roles a specific AWS service uses on your behalf (e.g.,
AWSServiceRoleForAutoScaling).
Federated principals
A federated principal is an external identity (corporate AD, Google Workspace, Okta, GitHub OIDC, Cognito user, custom OIDC provider) that authenticates outside AWS and is mapped to an IAM role via STS:
AssumeRoleWithSAML— for SAML 2.0 IdPs (corporate AD via ADFS, Okta SAML, OneLogin).AssumeRoleWithWebIdentity— for OIDC providers (Google, Facebook, Amazon, GitHub Actions, Cognito user pools, EKS service accounts).AssumeRolewith external session name — for trusted partners using their own AWS account credentials, often combined withExternalId.
Federated identity is the SOA-C02 preferred pattern for human access — no IAM users for employees, no shared keys, no rotation overhead, and the corporate IdP handles MFA, password policy, and offboarding.
On SOA-C02 the canonical answer for "give 200 employees access to AWS" is federation through SAML or OIDC into IAM roles, never per-employee IAM users. The exam favors federation for cost (zero per-user IAM cost), security (centralized MFA at the IdP), audit (one source-of-truth identity store), and operations (offboard once at the IdP, all AWS access dies). IAM users remain only for emergency break-glass accounts protected by hardware MFA and audited via CloudTrail. Reference: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_saml.html
The Five IAM Policy Types: Identity, Resource, SCP, Permissions Boundary, Session
The single highest-leverage IAM concept for SOA-C02 is the precise scope and effect of each of the five policy types. Memorise this matrix and most "why was access denied" questions become trivial.
- Identity policy — Attached to: a user, group, or role. Effect: grants or explicitly denies actions for that principal. Most permissions in AWS come from identity policies. Maximum 10 managed policies attached per identity (raise via quota); inline policies sized into the identity's policy size budget.
- Resource policy — Attached to: a resource (S3 bucket, KMS key, SQS queue, SNS topic, Lambda function, IAM role trust, etc.). Effect: grants or denies access to that resource for any principal listed (including cross-account). For cross-account access, a resource policy is required in addition to the requesting account's identity policy.
- Service Control Policy (SCP) — Attached to: an AWS Organizations OU, account, or root. Effect: defines the maximum permissions any principal in the scope (including the account root user, except for the management account) can have. SCPs do not grant access; they only filter what identity and resource policies can authorize.
- Permissions boundary — Attached to: an IAM user or role. Effect: defines the maximum permissions the user or role can have, regardless of identity policies. Used to delegate IAM admin safely — you can let a developer create new roles, but cap the new roles' permissions to what the boundary allows.
- Session policy — Passed inline at:
AssumeRole,AssumeRoleWithSAML,AssumeRoleWithWebIdentity, orGetFederationTokentime. Effect: further scopes down the assumed session. Cannot grant beyond the role's identity policies; only narrows. Maximum 2048 characters; passed as thePolicyparameter or up to 10 managed policy ARNs. - Reference: https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html
Identity policies — the workhorse
Identity policies are the most common policy type. AWS provides hundreds of managed policies (AmazonS3ReadOnlyAccess, AmazonRDSFullAccess, etc.) and customers create customer-managed policies for fine-grained control. Inline policies embed JSON directly into the user/group/role and are the wrong default — they cannot be reused and complicate audit.
A minimal identity policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": [
"arn:aws:s3:::prod-logs",
"arn:aws:s3:::prod-logs/*"
]
}
]
}
Resource policies — the cross-account bridge
Resource policies live on the resource itself. The most-tested resource policies on SOA-C02 are:
- S3 bucket policies — gate object operations across accounts and conditions.
- KMS key policies — required for cross-account key usage; default key policy delegates to IAM.
- IAM role trust policies — the resource policy of a role; defines who can assume.
- Lambda resource policies — permit cross-account invocation, S3 event triggers, API Gateway integration.
- SNS topic and SQS queue policies — gate cross-account publish/subscribe and send/receive.
SCPs — the organizational ceiling
Service Control Policies live in AWS Organizations and are attached to OUs, accounts, or the organization root. SCPs do not grant; they filter. The most common SCP patterns:
- Region restriction: deny all actions outside an approved region using
aws:RequestedRegion. - Service restriction: deny services the org has not approved (e.g., deny
ec2:*in a logging-only OU). - Mandatory encryption: deny
s3:PutObjectunlesss3:x-amz-server-side-encryptionis set. - Prevent CloudTrail disable: deny
cloudtrail:StopLoggingandcloudtrail:DeleteTrailfor everyone. - Root user lockdown: deny actions other than basic management for the root user.
The management account is not subject to SCPs — a critical SysOps trap (covered below).
Permissions boundaries — delegated admin safety
A permissions boundary caps the maximum permissions of a user or role. The classic use case: a SysOps engineer wants to let a senior developer create new IAM roles for their team's Lambda functions — but does not want those new roles to be able to escalate privileges. The pattern:
- Create a permissions boundary policy
DeveloperBoundarythat lists the maximum permissions any role created by this developer should ever have. - Attach an identity policy to the developer that grants
iam:CreateRole,iam:AttachRolePolicy, etc. — with a condition that any role they create must haveDeveloperBoundaryattached as the permissions boundary. - The developer can now create roles freely, but no role they create can ever exceed the boundary, even if they attach
AdministratorAccess.
The condition that enforces this:
"Condition": {
"StringEquals": {
"iam:PermissionsBoundary": "arn:aws:iam::123456789012:policy/DeveloperBoundary"
}
}
Session policies — narrow at assume time
A session policy is passed inline at AssumeRole time, not stored persistently. It scopes down the assumed session to a subset of the role's permissions. Use cases:
- A SaaS application assumes a customer role and presents a session policy listing only the actions needed for the current operation.
- An identity broker (Cognito, custom federation) issues per-session restricted credentials.
- A
sts:AssumeRoleCLI call with--policyflag for one-off scoped operations.
The session policy effective permissions are the intersection of the role's identity policies and the session policy — neither alone, only what both allow.
- Max managed policies per IAM identity: 10 (default; raise via Service Quotas).
- Max managed policy size: 6,144 characters.
- Max inline policy size per user: 2,048 characters.
- Max inline policy size per group: 5,120 characters.
- Max inline policy size per role: 10,240 characters.
- Max session policy size: 2,048 characters (passed as Policy parameter).
- Max managed policies passed as session policies: 10 ARNs.
- STS AssumeRole session duration: 15 minutes minimum, 12 hours maximum (set by
MaxSessionDurationon the role; default 1 hour). - STS role chaining session duration: 1 hour maximum (cannot be extended).
- AssumeRoleWithSAML duration: 15 minutes to 12 hours (per role's MaxSessionDuration).
- Number of access keys per IAM user: 2 maximum.
- Number of MFA devices per user: 8 (1 hardware + multiple virtual or FIDO2).
- SCP maximum size: 5,120 characters.
- Number of groups per IAM user: 10 (default).
- Reference: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_iam-quotas.html
IAM Policy Evaluation Logic — The SOA-C02 Simplified Model
The official IAM policy evaluation logic is a multi-page flowchart. For SOA-C02 the simplified model is sufficient, and once you internalize it most "why was this denied" questions are mechanical to answer.
The simplified evaluation chain
When a principal makes an API call, AWS evaluates the request through the following gates in order. If any gate denies, the request is denied. To allow, every applicable gate must allow (or be silent in a way that resolves to allow).
- Explicit deny — anywhere wins. If any policy in scope (SCP, identity, resource, permissions boundary, session) has an
Effect: Denymatching the action and resource, the request is denied. No subsequent gate can reverse this. - SCP — if the principal is in an Organizations OU, the SCP must allow the action. SCPs are deny-by-default in allow-list mode, allow-by-default in deny-list mode. AWS-provided default SCP
FullAWSAccessallows everything; custom SCPs replace or augment it. - Resource policy — if the resource has a resource policy and the principal is from a different account, the resource policy must allow the action. For same-account requests, the resource policy can be sufficient on its own (S3 bucket policy alone, with no IAM identity policy, can grant access to a same-account user — but the safer mental model is to require both).
- Identity policy — at least one identity policy attached to the principal must allow the action.
- Permissions boundary — if the principal has a permissions boundary, the boundary must allow the action.
- Session policy — if the request was made with assumed session credentials carrying a session policy, the session policy must allow the action.
Conditions in any of these layers are evaluated as part of that layer's allow check. A condition that fails behaves like a non-match — the statement does not allow the action, and a different statement may.
The "explicit deny wins" rule in detail
The single most-tested rule on SOA-C02 is that an explicit Effect: Deny at any layer overrides every Allow everywhere else. Common operational consequence: a developer is given AdministratorAccess on their identity policy, but the OU-level SCP has a Deny on iam:CreateUser. The developer cannot create IAM users no matter what, because the SCP's explicit deny wins.
Cross-account requests — both sides must allow
For a request from account A to a resource in account B:
- Account A's IAM identity policy must allow the action.
- Account B's resource policy must allow the principal from account A.
- Account A's SCP and permissions boundary must allow the action.
- (No requirement on account B's SCP or permissions boundary — those govern principals in account B, not visitors from account A.)
The cleanest SOA-C02 example: a developer in the dev account wants to read a logs bucket in the prod account. Both must be true: (a) the developer's IAM identity policy in dev allows s3:GetObject on the bucket ARN, and (b) the bucket policy in prod allows the developer's principal ARN to do s3:GetObject. Either alone fails. Candidates often forget the resource policy requirement and waste time debugging the identity policy. Reference: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_evaluation-logic.html
Trust policy is a special resource policy
A role's trust policy is technically a resource policy on the role. For sts:AssumeRole to succeed, two things must be true: (a) the principal must have sts:AssumeRole permission in their identity policy, and (b) the role's trust policy must list the principal in its Principal block. Both sides — like every cross-account access. Trust policies also commonly include conditions (sts:ExternalId for third-party assume, aws:SourceVpc for VPC-scoped assume).
MFA Setup and Enforcement via aws:MultiFactorAuthPresent
Multi-factor authentication is mandatory for every privileged identity in a SOA-C02 mature environment, and the exam tests both enabling MFA and enforcing it via condition keys.
MFA device types
- Virtual MFA app (Authy, Google Authenticator, AWS-managed
AWS Virtual MFA) — TOTP, six-digit codes every 30 seconds. Free, fast to enroll, vulnerable to phishing. - U2F / FIDO2 security key (YubiKey, Google Titan) — hardware key plugged in via USB. Phishing-resistant, recommended for root and admin accounts.
- Hardware OTP token (Gemalto / Yubico) — separate physical device that displays a six-digit code. Used by orgs with strict hardware-token-only policies.
- SMS MFA is no longer supported for new IAM users (deprecated by AWS).
A user can have up to 8 MFA devices registered, but only one is active during sign-in.
MFA setup on the root user
The root user's MFA is the first thing every Trusted Advisor security check audits. The recommended pattern: a hardware FIDO2 key stored physically secure with sealed bag and dual control (two custodians). On account creation, set the root MFA before any other action.
MFA enforcement via condition key
AWS exposes the global condition key aws:MultiFactorAuthPresent (Boolean) and aws:MultiFactorAuthAge (Numeric, seconds since MFA). The canonical enforcement pattern:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyAllExceptListedIfNoMFA",
"Effect": "Deny",
"NotAction": [
"iam:CreateVirtualMFADevice",
"iam:EnableMFADevice",
"iam:GetUser",
"iam:ListMFADevices",
"iam:ListVirtualMFADevices",
"iam:ResyncMFADevice",
"sts:GetSessionToken"
],
"Resource": "*",
"Condition": {
"BoolIfExists": {
"aws:MultiFactorAuthPresent": "false"
}
}
}
]
}
This pattern uses BoolIfExists because the key is absent (rather than false) for non-MFA sessions in some scenarios. The NotAction carve-out lets the user enroll their MFA device on first login.
Per-API MFA requirement
For sensitive APIs you can require MFA on each call:
{
"Effect": "Allow",
"Action": "ec2:TerminateInstances",
"Resource": "*",
"Condition": {
"Bool": { "aws:MultiFactorAuthPresent": "true" },
"NumericLessThan": { "aws:MultiFactorAuthAge": "3600" }
}
}
The MultiFactorAuthAge condition requires MFA within the last hour, raising the bar against stolen session credentials.
- Root account: hardware FIDO2 + sealed-bag dual custody.
- Identity policy MFA enforcement:
DenywithCondition: { BoolIfExists: { aws:MultiFactorAuthPresent: false } }andNotActioncarve-out for self-MFA-enrollment APIs. - Per-API MFA requirement:
AllowwithCondition: { Bool: { aws:MultiFactorAuthPresent: true } }plusaws:MultiFactorAuthAgenumeric upper bound. - STS AssumeRole MFA: trust policy with
Condition: { Bool: { aws:MultiFactorAuthPresent: true } }— caller must present MFA at AssumeRole time. - AssumeRole session 1h max with role chaining: regardless of
MaxSessionDurationon the role. - AssumeRole session 12h max otherwise: configurable per role via
MaxSessionDuration(3600 to 43200 seconds). - Reference: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_mfa.html
Account Password Policy and Credential Rotation
The IAM account password policy is a single, account-wide setting that governs every IAM user's console password. SOA-C02 tests both the levers available and the audit/rotation patterns.
Password policy levers
- Minimum password length: 6 to 128 characters (default 8; recommended 14+).
- Require at least one uppercase: yes/no.
- Require at least one lowercase: yes/no.
- Require at least one number: yes/no.
- Require at least one non-alphanumeric: yes/no.
- Allow users to change their own password: yes/no.
- Password expiration: 1 to 1095 days; once set, users must rotate at next login after expiry.
- Password reuse prevention: 1 to 24 previous passwords blocked.
- Password expiration requires admin reset vs users can reset themselves: governs whether expired users can self-recover.
The password policy applies only to IAM users — federated users authenticate against the IdP, which has its own policy.
Credentials report
The IAM credentials report is a CSV download (or iam:GenerateCredentialReport API) listing every IAM user with: console password set, password last used, password last changed, MFA enabled, access key 1/2 status, access key last used, access key last rotated. Compliance audits and SOC reports rely on this report to validate password and key rotation.
Access key rotation
Each IAM user can have up to 2 access key pairs simultaneously, exactly to support rotation without downtime:
- Create a second access key (now both are active).
- Distribute the new key to applications, redeploy.
- Verify with
Last usedtimestamp on the old key — it should stop being used. - Deactivate (not delete) the old key for an observation window.
- After the observation window, delete the old key.
SOA-C02 may ask "how to rotate access keys without service interruption" — the answer is the dual-key pattern above.
For any compliance question about who has stale credentials, the credentials report is the right tool. Generate it via aws iam generate-credential-report then aws iam get-credential-report --output text --query Content | base64 -d. The report tells you every user, MFA status, password age, key age, and last-used timestamps. Pair with a scheduled Lambda that imports the report into S3/Athena weekly for trend analysis. Reference: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_passwords_account-policy.html
SAML, OIDC, and Cognito Federation — Operational Setup
Federation is the SOA-C02 preferred pattern for human and workload access. Each protocol has a distinct setup and a distinct STS API.
SAML 2.0 federation
SAML 2.0 is the corporate identity protocol — used by ADFS, Azure AD / Entra ID SAML, Okta SAML, Ping, OneLogin. Operational setup:
- Configure the IdP: register AWS as a service provider, define the SAML assertion claims (NameID, role mappings via the
https://aws.amazon.com/SAML/Attributes/Roleclaim). - Create an IAM SAML identity provider: upload the IdP metadata XML to IAM. Generates a SAML provider ARN.
- Create IAM roles with trust policies that allow the SAML provider as principal and condition on the SAML audience:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Federated": "arn:aws:iam::123456789012:saml-provider/CorpAD" },
"Action": "sts:AssumeRoleWithSAML",
"Condition": {
"StringEquals": {
"SAML:aud": "https://signin.aws.amazon.com/saml"
}
}
}]
}
- Map IdP groups to IAM roles in the IdP (one IdP group per IAM role typically).
- Users authenticate at the IdP and are redirected to AWS console with a SAML response; AWS calls
AssumeRoleWithSAMLand starts a session.
Session duration is set by MaxSessionDuration on the role (1 to 12 hours).
OIDC federation
OIDC is used by Google, Facebook, Amazon, GitHub Actions, Cognito user pools, and Kubernetes service accounts (EKS IRSA). Operational setup:
- Create an IAM OIDC identity provider with the IdP's issuer URL and thumbprint of the TLS certificate.
- Create IAM roles with trust policies that allow the OIDC provider as principal, conditioned on the audience and subject:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com" },
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub": "repo:my-org/my-repo:ref:refs/heads/main"
}
}
}]
}
- Workload requests an ID token from the IdP, then calls
AssumeRoleWithWebIdentitywith the token.
The GitHub Actions pattern above is increasingly the SOA-C02 preferred answer for CI/CD that accesses AWS — no long-lived access keys, just short-lived OIDC tokens scoped to the repo and branch.
Cognito identity pools
Cognito user pools handle authentication; Cognito identity pools handle authorization to AWS resources. The pattern: a mobile app authenticates the user against a Cognito user pool, exchanges the ID token at the identity pool, and the identity pool calls AssumeRoleWithWebIdentity on a role configured in the pool. The mobile client then makes AWS API calls with those temporary credentials. SOA-C02 tests Cognito identity pools in the context of "give mobile app users scoped AWS access without embedding long-term keys".
STS AssumeRole — ExternalId, Role Chaining, and Session Tags
sts:AssumeRole is the workhorse for cross-account, federated, and service-to-service access. Three operational details routinely appear on SOA-C02.
ExternalId — the SaaS provider pattern
When a third-party SaaS (Datadog, New Relic, Snyk, Wiz) needs access to your account, the canonical pattern is to create a role in your account that the SaaS's AWS account can assume — but to require an ExternalId in the trust policy:
{
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::222222222222:root" },
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": { "sts:ExternalId": "unique-tenant-id-abc123" }
}
}
The ExternalId is a shared secret negotiated out-of-band that prevents the confused deputy problem: a malicious customer of the SaaS cannot trick the SaaS into assuming your role on their behalf, because the SaaS will only present the ExternalId you registered with it. SOA-C02 tests this pattern as "what condition prevents a malicious third-party customer from accessing my account through the SaaS".
Role chaining and the 1-hour cap
Role chaining is when an assumed-role session assumes another role. Example: SAML user assumes corp-developer role (12-hour session), then corp-developer assumes prod-readonly role. The second AssumeRole is role chaining.
The critical constraint: role chaining caps the second session at 1 hour, regardless of the target role's MaxSessionDuration. This is a security control to limit blast radius of escalating privilege through chained roles. SOA-C02 questions about "the script's session keeps expiring after one hour despite MaxSessionDuration being set to 12 hours" point to role chaining as the cause.
Session tags
Session tags are key-value pairs attached to the session at AssumeRole time and inherited into the request context as aws:PrincipalTag/key condition keys. Use cases:
- ABAC (attribute-based access control) — scope access by department, project, or cost center via tags rather than per-team roles.
- Audit trail enrichment — CloudTrail records session tags, making the audit trail more useful than principal ARN alone.
Session tags are passed via the Tags parameter of AssumeRole (or as SAML/OIDC claims auto-mapped to tags).
A frequent SOA-C02 distractor: "the developer's automation script keeps failing every hour despite MaxSessionDuration being set to 12 hours on the target role". The candidate looks at role configuration; the actual cause is role chaining. The first AssumeRole established a session, the script then assumed another role from that session, and that second AssumeRole is hard-capped at 1 hour. The fix is to either (a) eliminate the chain by directly assuming the final role, or (b) have the script re-AssumeRole every 50 minutes. Reference: https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html
Access Auditing: CloudTrail + IAM Access Analyzer + Policy Simulator
The SOA-C02 access-auditing toolkit has three pillars, and the exam tests when to use each.
CloudTrail — what already happened
CloudTrail records every API call across the account. For access auditing it answers questions like:
- "Who deleted the production S3 bucket policy yesterday at 3pm?" — query
eventName=PutBucketPolicyorDeleteBucketPolicyon the bucket between 14:00 and 16:00. - "Who logged in as the root user this quarter?" — query
userIdentity.type=Rootfor the period. - "Which principals accessed the logs bucket in the last 90 days?" — query S3 data events for the bucket (must be enabled separately in the trail; off by default).
- "Did anyone disable CloudTrail itself?" — query
eventName=StopLoggingorDeleteTrail.
CloudTrail is reactive — you can only audit what already happened, and only if the trail was on at the time. Management events are recorded by default in every account; data events (S3 object-level, Lambda function invoke) require explicit enabling.
IAM Access Analyzer — who could access
Access Analyzer uses automated reasoning (mathematical proof) to answer two distinct questions:
- External Access Findings — for each resource policy in scope (S3 buckets, IAM roles, KMS keys, Lambda functions, SQS queues, Secrets Manager secrets, EFS file systems, RDS snapshots, EBS snapshots), Access Analyzer determines whether the resource is accessible from outside the account or organization. Findings list each external principal and the access path. The SysOps team reviews and either marks as intended (archive) or remediates by tightening the policy.
- Unused Access Findings (newer feature) — identifies IAM users, roles, and policies with permissions that have not been used in a configurable period (default 90 days). Drives least-privilege right-sizing.
- Policy Validation — when authoring a policy, Access Analyzer surfaces warnings and suggestions (overly broad resource, missing condition, etc.).
- Custom Policy Checks — newer feature that lets you assert "this policy does not grant
iam:PassRoleto anyone outside this OU" with mathematical certainty.
Access Analyzer is proactive — it tells you what is currently possible, not what already happened.
IAM Policy Simulator — what would happen
Policy Simulator evaluates whether a specific principal would be allowed to perform a specific action on a specific resource, without making the API call. It walks the same evaluation chain (SCP, identity, resource, permissions boundary) and returns:
- The decision:
allowedorexplicitDenyorimplicitDeny. - Which policy statement matched.
- Which condition keys were evaluated and what their values were.
- Which resource ARN matched.
Use cases:
- Pre-deployment testing: before attaching a new policy, simulate critical actions to ensure no regression.
- Access denied debugging: a developer reports
AccessDeniedException; simulate the exact action and resource and see which gate denied. - Cross-account simulation: simulate as a principal from another account against a resource in this account.
- "What already happened?" → CloudTrail. (Past tense.)
- "What is currently possible?" → IAM Access Analyzer. (Present tense, automated reasoning over all current policies.)
- "What would happen if X did Y?" → IAM Policy Simulator. (Hypothetical, evaluation chain walk.)
SOA-C02 test scenarios will phrase the question in past, present, or hypothetical tense — that grammar is your hint. Reference: https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_testing-policies.html
AWS Trusted Advisor Security Checks
Trusted Advisor is the cross-cutting AWS best-practices linter. SOA-C02 calls out the security checks specifically:
- MFA on root account — flags any account whose root user lacks MFA.
- IAM use — flags accounts where the root user is the only credential (recommends creating IAM users / roles).
- IAM password policy — flags missing or weak password policies.
- Security groups, specific ports unrestricted — flags security groups exposing SSH (22), RDP (3389), or other sensitive ports to 0.0.0.0/0.
- S3 bucket permissions — flags buckets with public read/write or "Authenticated AWS Users" grants.
- CloudTrail logging — flags accounts where CloudTrail is not enabled in all regions.
- Exposed access keys — scans public GitHub for leaked AWS access keys belonging to your account.
- Public snapshots — flags EBS or RDS snapshots shared publicly.
Trusted Advisor checks are categorized into Cost Optimization, Performance, Security, Fault Tolerance, and Service Limits. The full security check set requires Business or Enterprise Support plan; the free tier shows only a small subset.
The operational pattern: an EventBridge rule on Trusted Advisor Check Item Refresh Notification events routes Security category check changes to SNS, so the SysOps team is notified the moment a new bucket is exposed publicly or an MFA device is removed.
Region and Service Compliance Restrictions via SCPs
A common SOA-C02 compliance scenario: data residency requires the workload to run only in approved regions, or only specific services are allowed. SCPs are the SOA-sanctioned enforcement.
Region restriction SCP
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "DenyOutsideApprovedRegions",
"Effect": "Deny",
"NotAction": [
"iam:*", "organizations:*", "route53:*",
"cloudfront:*", "support:*", "sts:*",
"waf:*", "wafv2:*", "shield:*", "globalaccelerator:*"
],
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:RequestedRegion": ["ap-southeast-1", "ap-east-1"]
}
}
}]
}
The NotAction carve-out is essential — global services (IAM, Organizations, Route 53, CloudFront, STS) have no regional endpoint or use us-east-1 as their home region. Failing to carve them out breaks the entire account.
Service restriction SCP
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "DenyDisallowedServices",
"Effect": "Deny",
"Action": ["sagemaker:*", "comprehend:*", "rekognition:*"],
"Resource": "*"
}]
}
Useful for "this OU is for compliance-restricted workloads — no AI/ML services until approved by legal".
Mandatory encryption SCP
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "DenyUnencryptedS3Uploads",
"Effect": "Deny",
"Action": "s3:PutObject",
"Resource": "*",
"Condition": {
"StringNotEquals": {
"s3:x-amz-server-side-encryption": "aws:kms"
}
}
}]
}
Even if a developer's identity policy allows s3:PutObject, the SCP denies any upload that is not encrypted with KMS.
A persistent SOA-C02 distractor offers an answer that "uses an SCP to grant access to a service". SCPs cannot grant. They only filter what other policies (identity, resource) can authorize. To add a new permission, attach it to the principal's identity policy; the SCP simply must not block it. Reference: https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_scps.html
Scenario Pattern: "User X cannot read S3 bucket Y" — the troubleshoot order
This is the canonical SOA-C02 troubleshooting scenario. Walk the evaluation chain top-to-bottom.
- Capture the exact error.
AccessDeniedfrom the CLI typically includes the principal ARN, the action, and the resource. CloudTrail shows the same with more detail (errorCode=AccessDenied,errorMessage="..."). The error message often pins the problem to a specific policy layer. - Run the IAM Policy Simulator for the principal, action (
s3:GetObject), and resource (arn:aws:s3:::bucket/object). The simulator returns the decision and the matched (or unmatched) policy. Most SOA-C02 troubleshooting points at the simulator as the first tool. - Check the SCP if the principal is in an Organizations OU. SCPs are the most surprising blocker because they are configured at the org level, not visible from the account.
- Check the resource policy — is it a same-account or cross-account request? For cross-account, the bucket policy must explicitly allow the principal. For same-account, the bucket policy may still block via
Effect: Denywith conditions. - Check the identity policy — does the user/role/group have an attached policy granting the action?
- Check the permissions boundary — if the role has one, does it allow the action?
- Check session policy — if the request is from an assumed role with a session policy, does the session policy allow?
- Check condition keys —
aws:SourceVpc,aws:SourceIp,aws:MultiFactorAuthPresent,s3:x-amz-server-side-encryption— any of these can fail the request. - Check encryption — the bucket may require SSE-KMS but the request used SSE-S3 or no encryption header.
- Check IAM Access Analyzer to confirm whether the bucket policy permits the principal at all.
The single most common root cause across SOA-C02 simulated scenarios: an OU-level SCP denies the action, and the developer was looking at identity and bucket policies but not the SCP.
Scenario Pattern: "Auditor asks who had write access to the logs bucket"
This is the present-tense audit scenario. The right tool is IAM Access Analyzer, not CloudTrail.
CloudTrail tells you who did write — past tense. The auditor asked who could write — present tense. Access Analyzer's external access findings list every principal (cross-account or external) that could write to the bucket per the current bucket policy. For internal principals, the answer is to enumerate every IAM user and role in the account whose identity policy allows s3:PutObject on the bucket — programmatically via iam:GetAccountAuthorizationDetails plus offline simulation.
The complete operational answer:
- For external (cross-account) write access: IAM Access Analyzer external access findings on the bucket.
- For internal write access: list IAM users + roles in the account, run the Policy Simulator with
s3:PutObjectagainst the bucket for each, collect the allows. - For historical writes: enable S3 data events on the CloudTrail trail (off by default) and query
eventName=PutObjectagainst the bucket.
A SOA-C02 question phrased "who can access" or "who has access" is present-tense — Access Analyzer. A question phrased "who accessed" or "who did access" is past-tense — CloudTrail. A question phrased "if user X tried to do Y, would it succeed" is hypothetical — Policy Simulator. The grammar of the question pins the tool. Reference: https://docs.aws.amazon.com/IAM/latest/UserGuide/what-is-access-analyzer.html
Common Trap: Explicit Deny Wins — Always
The single most-tested IAM rule. An explicit Effect: Deny at any layer (SCP, identity, resource, permissions boundary, session policy) overrides every Allow everywhere. There is no override, no exception, no "but the bucket policy allows".
Operational consequence: if a developer is denied an action despite having AdministratorAccess, the cause is almost always an explicit deny — usually in an SCP, occasionally in the bucket policy, occasionally in a permissions boundary. The IAM Policy Simulator surfaces the matched deny statement directly.
Common Trap: Permissions Boundary Scope Is Often Misunderstood
A permissions boundary is the maximum permissions a user or role can have — not a guaranteed allow. The boundary acts like a filter: the effective permissions are the intersection of identity policies and the boundary. If the identity policy grants S3 read but the boundary doesn't include S3, the user gets nothing for S3.
Common SOA-C02 trap: a candidate attaches a permissions boundary expecting it to grant permissions. It does not. The boundary only caps what other policies can grant. To grant, attach an identity policy within the boundary's allowed set.
Common Trap: Session Policy Is the Maximum Scope, Never the Minimum
A session policy passed at AssumeRole time can only narrow the assumed session, never broaden it. If the role's identity policies grant s3:* and the session policy grants ec2:*, the effective permissions are the intersection — which is empty (no S3, no EC2). Candidates sometimes try to use session policies to grant additional permissions; this never works.
Common Trap: SCP Does Not Grant Access — It Only Constrains
SCPs are filters, not grants. Attaching FullAWSAccess SCP to an OU does not give principals in that OU any access — they still need identity policies. SCPs replace or augment the default FullAWSAccess SCP that AWS attaches at the root; without that default, a custom SCP that lists only specific allows would silently block everything else.
The correct mental model: SCPs answer "what is the maximum any principal in this OU can ever be allowed to do". Identity and resource policies answer "what is this principal actually allowed to do". The intersection is the effective permissions.
Common Trap: Management Account Is Not Subject to SCPs
The AWS Organizations management account (formerly master account) is not constrained by any SCP — even the SCPs you attach to the root or to the OU containing the management account. AWS architects this exception so that the management account can always recover the organization in an emergency.
The operational consequence: never run production workloads in the management account. Use it solely for AWS Organizations management, billing, and break-glass. Real workloads belong in member accounts where SCPs apply.
A SOA-C02 distractor: "the security team applied an SCP at the root denying all S3 actions, but a developer in the management account can still create buckets". The candidate looks for SCP misconfiguration; the answer is that the management account is exempt. SOA-C02 expects you to know this exemption and the operational mitigation: keep the management account workload-free. Reference: https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_scps.html
SOA-C02 vs SAA-C03: The Operational Lens for IAM
Both certifications test IAM, but the lenses differ.
| Question style | SAA-C03 lens | SOA-C02 lens |
|---|---|---|
| Selecting a policy type | "Which IAM policy type lets a SaaS access the customer's account?" | "Add an ExternalId condition to the trust policy to prevent confused deputy." |
| Cross-account access | "Use a resource policy in account B." | "The principal in A has the identity policy, the bucket policy in B allows them, but AccessDenied — check SCP, condition keys, KMS key policy." |
| MFA | "Enable MFA on the root account." | "Write the IAM policy that denies all actions except self-MFA-enrollment when aws:MultiFactorAuthPresent is false." |
| Federation | "SAML 2.0 for corporate AD users." | "Configure the SAML provider, role trust policy with SAML:aud condition, IdP-side group-to-role mapping." |
| Permissions boundaries | "Used for delegated admin." | "Write the iam:PermissionsBoundary condition that forces the new role to carry the boundary." |
| Access Analyzer | "Identifies external access." | "Distinguish External Access Findings from Unused Access Findings; archive vs remediate workflow." |
| Policy Simulator | Rarely tested. | "The developer reports AccessDenied; the simulator says implicitDeny; what gate is missing the allow?" |
| SCPs | "Restrict regions at the OU level." | "Write the SCP with NotAction carve-out for global services and aws:RequestedRegion condition." |
| Role chaining | Not tested. | "The script keeps expiring after 1 hour despite MaxSessionDuration=12h — chained AssumeRole hits 1h cap." |
| Audit | "CloudTrail records API calls." | "Auditor wants who can access — Access Analyzer; who did access — CloudTrail; who would access — Simulator." |
The SAA candidate selects the policy type. The SOA candidate writes the policy, debugs the denial with the simulator, audits the access path with Access Analyzer, and runs the rotation, federation, and break-glass procedures.
Exam Signal: How to Recognize a Domain 4.1 Question
Domain 4.1 questions on SOA-C02 follow predictable shapes. Recognize them and the answer is usually one of three or four standard tools.
- "AccessDeniedException" — the answer involves the IAM Policy Simulator first, then walking the evaluation chain (SCP, resource, identity, boundary, session, conditions).
- "Bucket / role / KMS key shared externally" — the answer is IAM Access Analyzer external access findings.
- "Federate corporate users into AWS" — the answer is SAML or OIDC identity provider + IAM role trust policy with audience condition.
- "Delegate admin without privilege escalation" — the answer is permissions boundary with
iam:PermissionsBoundarycondition forcing the boundary on created roles. - "Restrict regions / services at organization level" — the answer is SCP with
aws:RequestedRegionor service-action filter. - "Require MFA for sensitive actions" — the answer is condition
aws:MultiFactorAuthPresentplusaws:MultiFactorAuthAge. - "Third-party SaaS needs cross-account access" — the answer is role with
sts:ExternalIdcondition. - "Auditor wants list of principals with access" — present tense, Access Analyzer.
- "Compliance team wants list of API calls" — past tense, CloudTrail.
- "Pre-deployment validation of new policy" — Policy Simulator.
- "Stale credentials and key rotation audit" — IAM credentials report.
- "Trusted Advisor red flag on root MFA / public bucket" — Trusted Advisor security check + remediation.
Domain 4 is worth 16 percent of SOA-C02, and TS 4.1 covers the lion's share of that. Expect 8 to 10 questions in this exact territory. The two most-tested tools are IAM Policy Simulator and IAM Access Analyzer; the most-tested condition keys are aws:MultiFactorAuthPresent, aws:RequestedRegion, and sts:ExternalId. Reference: https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_testing-policies.html
Decision Matrix — IAM Construct for Each SysOps Goal
Use this lookup during the exam.
| Operational goal | Primary construct | Notes |
|---|---|---|
| Give 200 corporate employees AWS access | SAML 2.0 federation + IAM roles | Never per-employee IAM users. |
| Give a CI/CD pipeline AWS access | OIDC federation (e.g., GitHub Actions) + IAM role | No long-lived access keys. |
| Give a mobile app users AWS access | Cognito identity pool + IAM role | Short-lived credentials per user. |
| Give an EC2 instance AWS API access | IAM instance profile | Auto-rotating credentials. |
| Give a Lambda function AWS API access | Lambda execution role | Auto-rotating credentials per invocation. |
| Cross-account read access | Identity policy in source account + resource policy in target account | Both required. |
| Third-party SaaS cross-account | Role with sts:ExternalId condition |
Prevents confused deputy. |
| Restrict OU to specific regions | SCP with aws:RequestedRegion |
NotAction carve-out for global services. |
| Mandate encryption on uploads | SCP with s3:x-amz-server-side-encryption condition |
Org-wide guardrail. |
| Delegate IAM admin to senior dev | Permissions boundary + condition forcing boundary on created roles | Prevents privilege escalation. |
| Require MFA for sensitive APIs | Identity policy with aws:MultiFactorAuthPresent + aws:MultiFactorAuthAge |
Per-API enforcement. |
| Identify externally accessible resources | IAM Access Analyzer external access findings | Present-tense audit. |
| Identify unused permissions | IAM Access Analyzer unused access findings | Drives least-privilege. |
Debug AccessDenied |
IAM Policy Simulator | Walks evaluation chain. |
| List who did action X yesterday | CloudTrail event lookup | Past-tense audit. |
| Audit MFA, key rotation, password age | IAM credentials report | CSV export, cron weekly. |
| Detect leaked access keys | Trusted Advisor exposed access keys check | Scans public GitHub. |
| Rotate access keys without downtime | Two access keys, gradual cutover | Distribute new, deactivate old, delete after window. |
| Suppress action during maintenance | Conditional Allow with aws:CurrentTime |
Or temporarily detach the policy. |
| Cross-account role chaining | AssumeRole twice | Caps at 1 hour, plan rotation. |
| Validate new policy pre-deploy | IAM Policy Simulator + Access Analyzer policy validation | Two-tool combo. |
| Tag-based access control (ABAC) | Session tags + aws:PrincipalTag/key conditions |
Scales better than per-team roles. |
Common Traps Recap — IAM Identities, Policies, and Access Auditing
Every SOA-C02 attempt will see two or three of these distractors.
Trap 1: Permissions boundary grants permissions
It does not. A boundary only caps what identity policies can grant. The effective permissions are the intersection of identity policy and boundary.
Trap 2: Session policy can broaden the session
It cannot. A session policy only narrows. If the session policy grants more than the role's identity policy, the extra is ignored.
Trap 3: SCP grants access
SCPs only constrain. To add a permission, attach it to an identity or resource policy; the SCP simply must not block it.
Trap 4: The management account is constrained by SCPs
It is not. The management account is exempt. Never run real workloads there.
Trap 5: Cross-account access only needs identity policy in source
It needs both: identity policy in source and resource policy in target.
Trap 6: Role chaining inherits the target role's MaxSessionDuration
It does not. Role chaining caps at 1 hour regardless. To get longer sessions, eliminate the chain or refresh.
Trap 7: Detailed monitoring of access lives in CloudTrail data events by default
It does not. S3 data events and Lambda invoke events are off by default in CloudTrail trails — you must enable them per resource. Many "we don't have the audit log" problems trace to this.
Trap 8: Access Analyzer covers all resource types
It covers a specific list (S3, IAM roles, KMS keys, Lambda, SQS, Secrets Manager, EFS, RDS snapshots, EBS snapshots, ECR repositories). Other resources require different audit tools.
Trap 9: aws:MultiFactorAuthPresent works without BoolIfExists
It can fail in some session contexts. Use BoolIfExists for safety in deny rules.
Trap 10: Federated users have IAM users
They do not. Federated users have a session backed by a role; there is no persistent IAM user record. Don't search for them in the IAM users console.
Trap 11: iam:PassRole is implicit when you can create resources
It is not. Creating a Lambda function or EC2 instance with a role attached requires the explicit iam:PassRole permission on that role's ARN. Many "I can create the resource but it fails to deploy" problems trace to a missing PassRole.
Trap 12: Inline policies are equivalent to managed policies
They are equivalent in evaluation but not in operations. Inline policies cannot be reused, complicate audit, and bloat the principal's policy size budget. Default to managed policies.
FAQ — IAM Identities, Policies, and Access Auditing
Q1: A developer reports AccessDeniedException on s3:GetObject. Where do I look first?
The first tool is the IAM Policy Simulator. Plug in the developer's principal ARN, the action s3:GetObject, and the exact resource ARN. The simulator returns the decision and which gate denied. If the decision is explicitDeny, the matched statement is the cause — typically an SCP or a bucket policy Deny. If the decision is implicitDeny, no statement allowed the action — check identity policy, then bucket policy (if cross-account), then permissions boundary, then SCP. The simulator saves you from making 10 console clicks across 5 IAM screens. CloudTrail's errorMessage field is also useful when the simulator does not match the live request because of session tags or condition values that the simulator did not include.
Q2: How is a permissions boundary different from an SCP?
Both cap maximum permissions, but at different layers. An SCP lives in AWS Organizations and applies to all principals in an OU or account; you control it as the org admin. A permissions boundary is attached to a specific IAM user or role; you control it as the IAM admin within the account. SCP scope is the OU/account; permissions boundary scope is the single IAM user or role. Common pairing: the org admin sets a region-restriction SCP at the OU; the account admin sets a permissions boundary on each developer-created role to prevent privilege escalation; together they form a multi-layered cap that no identity policy can break through. SOA-C02 questions about delegating admin within an account use permissions boundary; questions about org-wide guardrails use SCP.
Q3: When should I use a session policy instead of an identity policy?
Use a session policy when the scope is per-session and known only at runtime. Examples: a SaaS application assumes a customer role and presents a session policy listing only the operations the customer requested for this transaction; an identity broker scoping access by per-user attributes; a CLI script running a one-off restricted operation via aws sts assume-role --policy '...' . Use an identity policy for the role's standing permissions across all sessions. The session policy is the right tool for "narrow this specific session further than the role allows in general"; it is never the right tool for granting permissions, since it can only intersect.
Q4: How do I audit "who could have written to the logs bucket in the last 90 days"?
Two questions inside one. For external (cross-account) write access, run IAM Access Analyzer external access findings on the bucket; the report lists every cross-account principal that the bucket policy allows to write. For internal write access (principals in the same account), enumerate IAM users and roles via iam:GetAccountAuthorizationDetails and run the IAM Policy Simulator for each with s3:PutObject on the bucket; collect the allows. To answer "who did write", enable CloudTrail data events on the bucket (off by default) and query eventName=PutObject. The auditor's actual question is usually some combination — ask whether they want present-tense capability or past-tense activity, then pick the tool.
Q5: How do I prevent a developer with iam:CreateRole from creating an admin role?
Use a permissions boundary plus a condition on iam:PermissionsBoundary. Create a boundary policy DeveloperBoundary that lists the maximum permissions any developer-created role may have. Attach an identity policy to the developer that grants iam:CreateRole, iam:AttachRolePolicy, etc., with a condition StringEquals: { iam:PermissionsBoundary: "arn:aws:iam::...:policy/DeveloperBoundary" }. Now the developer can create roles freely, but every role they create must carry the boundary, and the boundary caps the role's effective permissions. Even if the developer attaches AdministratorAccess to their new role, the boundary clips it down. This is the SOA-C02 canonical "delegated admin without privilege escalation" answer.
Q6: My CI/CD job's AssumeRole session keeps expiring after 1 hour despite MaxSessionDuration=12h. Why?
You are hitting the role chaining 1-hour cap. Role chaining is when an already-assumed-role session calls AssumeRole again — the second AssumeRole is hard-capped at 1 hour, regardless of the target role's MaxSessionDuration. AWS imposes this cap as a security boundary on escalating privilege through chains. Two fixes: (a) eliminate the chain by having the CI/CD assume the final role directly via SAML or OIDC federation (single AssumeRoleWithSAML / AssumeRoleWithWebIdentity, no chain); (b) refresh the credentials in your script every 50 minutes by calling AssumeRole again from the original federated session. Option (a) is cleaner and the SOA-C02 preferred answer.
Q7: How do I require MFA for the AWS console but not for service-to-service API calls?
Service-to-service calls use IAM roles (instance profile, Lambda execution role, ECS task role) and never have a human-typed MFA code, so aws:MultiFactorAuthPresent is always false for them. The pattern is: scope your MFA enforcement policy to human IAM users / federated users, not roles. Attach the MFA-enforcement policy to a group Humans that contains your IAM users and the federated user role. Do not attach to instance profiles, Lambda execution roles, or service-linked roles. For SAML/OIDC federated humans, the IdP enforces MFA as part of authentication, and the resulting AWS session inherits the MFA-present context — so aws:MultiFactorAuthPresent=true if your SAML assertion includes the MFA attribute (e.g., ADFS AuthnContextClassRef set to the multi-factor reference). Configure the IdP correctly and the AWS-side condition just works.
Q8: What does IAM Access Analyzer actually do that the Policy Simulator cannot?
Access Analyzer uses automated reasoning (mathematical proof over the policy logic) to enumerate every principal that has access to a resource, given the current policies. It is exhaustive — no missed combinations. Policy Simulator answers a single hypothetical: "would principal X be allowed to do Y on Z?" — not exhaustive. Use Access Analyzer when the question is "list everyone who can do X", which the simulator cannot answer because it requires you to specify the principal upfront. Use the Simulator when the question is "would this specific principal succeed", which Access Analyzer cannot answer because it does not target a single principal. The two tools answer different questions and are complementary; SOA-C02 sometimes asks you to pick the right one for the scenario.
Q9: How does the ExternalId condition prevent the confused deputy problem?
The confused deputy problem is when a SaaS provider (the deputy) is tricked into using its privilege on behalf of a malicious caller. Without ExternalId: a malicious customer of the SaaS, with their own AWS account that has set up the SaaS integration, knows the SaaS's role-assume pattern (the SaaS assumes a role in the customer's account using its own AWS account as principal). The malicious customer creates a role in their own account that trusts the SaaS, then asks the SaaS to perform an operation against your AWS account ARN. The SaaS, seeing the target ARN, attempts to assume your role — which fails because your trust policy lists the SaaS, not the malicious customer. With ExternalId: the SaaS assigns a unique ExternalId to each customer (often the customer's tenant ID) and presents that ExternalId on every AssumeRole. Your trust policy requires the specific ExternalId. The malicious customer's AWS account would need to know your ExternalId to forge a request — they don't, because the SaaS only uses your ExternalId when calling your role. Result: the malicious customer cannot trick the SaaS into accessing your account.
Q10: Why does the management account ignore SCPs, and what should I do about it?
AWS designed the management account to be always recoverable — the security team can always log into the management account and fix any organization-wide misconfiguration, even if a careless SCP would otherwise lock everyone out. Therefore SCPs do not apply to the management account regardless of where in the OU tree it sits. The operational consequence: never run production workloads in the management account. Use it solely for AWS Organizations management, billing aggregation, and break-glass procedures. All real workloads live in member accounts, where SCPs, Control Tower guardrails, and Config rules apply normally. SOA-C02 routinely tests this exemption with scenarios like "the security team applied an SCP at the root denying all S3 actions, but a developer in the management account can still create buckets" — the answer is the management account exemption, and the remediation is to move the developer's workload out of the management account.
Further Reading and Related Operational Patterns
- AWS IAM User Guide — Introduction
- IAM Policies and Permissions
- IAM Policy Evaluation Logic
- IAM Permissions Boundaries
- Service Control Policies — AWS Organizations
- Session Policies
- Multi-Factor Authentication in AWS
- IAM Account Password Policy
- SAML 2.0 Federation
- Cross-Account AssumeRole
- STS AssumeRole API Reference
- Testing IAM Policies with the Policy Simulator
- IAM Access Analyzer
- AWS Global Condition Context Keys
- AWS Trusted Advisor Security Checks
- AWS CloudTrail User Guide
- AWS SOA-C02 Exam Guide v2.3 (PDF)
Once IAM is in place, the next operational layers are: Multi-Account Strategy with Organizations and Control Tower for organization-wide enforcement of the SCPs and federation patterns from this note, Data Protection: KMS, ACM, Secrets Manager for the encryption layer that complements IAM access controls, CloudTrail and AWS Config for Audit and Compliance for the configuration recording and rule-based detection that turns IAM events into actionable signals, and VPC Configuration and Connectivity for the network-layer access controls that combine with IAM condition keys (aws:SourceVpc, aws:SourceVpce) to restrict where API calls can originate.
- Multi-Account Strategy with Organizations and Control Tower — SOA-C02 Study Notes
- Data Protection: KMS, ACM, Secrets Manager — SOA-C02 Study Notes
- CloudTrail and AWS Config for Audit and Compliance — SOA-C02 Study Notes
- VPC Configuration, Subnets, NACLs, and Private Connectivity — SOA-C02 Study Notes