Domain 1 of 4 · Chapter 1 of 3

Secure Access to AWS Resources

The four-tier IAM evaluation: SCP → boundary → identity policy → resource policy

Every AWS API call is evaluated against four layers of policy, in a fixed order. Understanding the order is the difference between debugging access in five minutes and three hours.

Layer 1 — Service Control Policy (SCP). Attached at AWS Organizations root, OU, or account. Applies to every principal in the account, including the root user. If an SCP denies the action, evaluation stops — no further layer can grant it. SCPs are deny-by-default in spirit: an Allow SCP doesn't grant; it only widens what the account could do if downstream layers also allow. A missing SCP is permissive (no restriction).

Layer 2 — Permission boundary. Attached to an individual IAM user or role. Acts as a ceiling on the maximum permissions the principal can have. The boundary itself grants nothing. If the boundary Allow doesn't list the action, the call is denied even if the identity policy says yes.

Layer 3 — Identity policy. Attached to the user / role / group performing the action. Must contain an explicit Allow for the API action and resource. Identity policies are additive — multiple attached policies are unioned.

Layer 4 — Resource policy. Attached to the target resource (S3 bucket, KMS key, SQS queue, etc.). For cross-account access, the resource policy must explicitly allow the calling principal (or its account). For same-account access, a resource policy Allow can substitute for an identity-policy Allow only for resources that support it.

Decision algorithm (simplified):

  1. Any explicit Deny in any layer? → DENY.
  2. SCP missing required Allow? → DENY.
  3. Permission boundary missing required Allow? → DENY.
  4. Cross-account? → Both identity policy AND resource policy must Allow.
  5. Same-account? → Identity policy Allow is sufficient (resource policy Allow is optional unless required by service).
  6. All else? → DENY (default).

Common failure modes:

  • 'Admin user can't access S3 bucket': resource policy on bucket explicitly denies their principal. Explicit Deny beats anything.
  • 'New developer with PowerUser permissions can't create role': SCP at OU level denies iam:CreateRole for that OU.
  • 'Lambda execution role has admin but still can't decrypt KMS key': KMS key policy doesn't grant the role; KMS treats key policy as the primary access mechanism.

Exam shortcut: when a question lists multiple policies and asks what happens, evaluate from outer (SCP) to inner (resource) and stop at the first hard Deny. See the official IAM policy evaluation reference[13] for the canonical algorithm.

Trust policy authoring: cross-account, OIDC, SAML scenarios with ExternalId

A trust policy is the resource-based policy on an IAM role that controls who can assume it. Getting it right is the most common cross-account exam topic.

Anatomy:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "AWS": "arn:aws:iam::222222222222:root" },
    "Action": "sts:AssumeRole",
    "Condition": {
      "StringEquals": { "sts:ExternalId": "unique-tenant-id-xyz" },
      "Bool": { "aws:MultiFactorAuthPresent": "true" }
    }
  }]
}

Principal forms:

  • "AWS": "arn:aws:iam::222222222222:root" — any IAM principal in account 222... (broadest; account-level trust)
  • "AWS": "arn:aws:iam::222222222222:role/Build" — only this specific role
  • "Federated": "cognito-identity.amazonaws.com" — Cognito Identity Pool
  • "Federated": "arn:aws:iam::111...:oidc-provider/token.actions.githubusercontent.com" — GitHub Actions OIDC
  • "Federated": "arn:aws:iam::111...:saml-provider/Okta" — SAML
  • "Service": "lambda.amazonaws.com" — AWS service (for service roles)

Critical conditions:

  • sts:ExternalId[12] — required defence against the confused deputy problem. When a third-party SaaS assumes your role, only pinning the principal ARN is unsafe: another customer of the same SaaS could be tricked into assuming your role. The SaaS supplies a per-customer ExternalId; your trust policy validates it.
  • aws:MultiFactorAuthPresent — require MFA on the assuming session. For human-assumed roles (break-glass, admin elevation), this is the right default.
  • aws:SourceIp / aws:SourceVpc — restrict where the assume can originate (e.g. only from your VPN's IP range).
  • aws:PrincipalOrgID — require the caller's account to be in your Organization[15]. Cleaner than listing individual account ARNs.

OIDC for GitHub Actions — full pattern (uses the sts:AssumeRoleWithWebIdentity[6] API):

{
  "Effect": "Allow",
  "Principal": {
    "Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com"
  },
  "Action": "sts:AssumeRoleWithWebIdentity",
  "Condition": {
    "StringEquals": {
      "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
    },
    "StringLike": {
      "token.actions.githubusercontent.com:sub": "repo:my-org/my-repo:ref:refs/heads/main"
    }
  }
}

The sub claim restricts to a specific repo + branch. Wildcards (repo:my-org/*) let any repo in the org assume the role — sometimes desired, often a foot-gun.

Exam traps:

  • Principal: "*" in a trust policy = anyone in any AWS account can assume your role. Almost always wrong unless paired with restrictive conditions (and even then, suspect).
  • Forgetting sts:ExternalId for third-party integrations.
  • Long MaxSessionDuration (12h) when human elevation should be 1h or less.

Permission boundaries vs SCPs: matching the tool to the unit of governance

Both permission boundaries[9] and Service Control Policies[10] restrict what a principal can do. Both are ceilings, never grants. The difference is the unit of governance.

Aspect Permission boundary Service Control Policy
Attaches to Individual IAM user or IAM role OU, account, or Organization root
Affects The single principal it's attached to Every principal in every account in scope (including root user)
Use case Delegating role creation safely — "developer can create roles, but not above this ceiling" Organization-wide guardrail — "no IAM user with console access anywhere"
Set by Account admin Organization management account
Bypassable by root user? N/A (boundary scope is per-principal) NO — SCPs apply to root too
Default if absent No ceiling — identity policy + resource policy determine access Implicit Allow * at root (no restriction)

Pattern 1 — Boundary for developer delegation:

Team X wants developers to create their own IAM roles for their Lambda functions, without granting full iam:*. Solution:

  1. Define a boundary policy DevBoundary that allows only the actions developer-created roles should be able to use (e.g. S3 read-only on team buckets, DynamoDB CRUD on team tables, CloudWatch Logs).
  2. Grant developers a policy that includes iam:CreateRole + iam:PutRolePolicy with the condition iam:PermissionsBoundary = arn:.../DevBoundary — they must attach the boundary when creating roles.
  3. Now the developer can iam:CreateRole a Lambda role, but every role they create is capped by DevBoundary. They can't accidentally grant themselves admin.

Pattern 2 — SCP for organization-wide guardrail:

Company policy: no IAM user with console access can exist anywhere in the organization. Solution:

  1. SCP attached at organization root:
{ "Effect": "Deny", "Action": "iam:CreateLoginProfile", "Resource": "*" }
  1. Now no IAM user in any member account can have a console password — even the management account's root user can't override it.

Pattern 3 — Combining both:

SCP at OU 'Production' denies ec2:TerminateInstances for any principal except the role EmergencyAdmin. Inside one production account, a developer needs to manage their team's instances. Their role has a permission boundary that allows ec2:Start*, ec2:Stop*, ec2:Reboot* but NOT Terminate* — boundary reinforces the SCP, and even if their identity policy granted ec2:*, both layers deny Terminate.

Common exam distractor: the question presents a scenario where central security wants to restrict an entire OU, and the option offering 'Permission boundary on every role in the OU' is plausible but wrong — boundaries don't propagate; SCP is the right tool. The reverse is also tested: 'delegate role creation to one team' is a permission-boundary scenario, not SCP.

IAM Identity Center deep dive: permission sets, account assignments, IdP wiring

IAM Identity Center (formerly AWS SSO) is the modern primitive for workforce access. It replaces per-account IAM federation with centralised user + group management.

Core concepts:

  • Identity source. Where the users live. Three options:
    • Identity Center directory (built-in; OK for small teams).
    • Active Directory (on-prem AD via AWS Managed Microsoft AD or AD Connector).
    • External IdP (Okta, Entra ID, Google Workspace, OneLogin, etc.) via SAML 2.0 + SCIM for user/group sync.
  • Permission set. A blueprint of IAM policies that defines a role identity-center will create in target accounts when assigned. Think of it as 'reusable role template'.
  • Account assignment. Binding: (Group X) + (Account Y) + (Permission Set Z). When a user logs in, the user picks one of their assigned account + permission-set combinations; Identity Center provisions a temporary session via AssumeRole into the target.

Architecture:

IdP (Okta) ──SAML──> IAM Identity Center
                            │
                            │  (SCIM for user/group sync)
                            │
                            ▼
   ┌────────────────────────────────────────────┐
   │  Account assignments (group × account ×   │
   │  permission set)                          │
   └────────────────────────────────────────────┘
                            │
                            │  on user click in portal
                            ▼
   AssumeRole into target account → temporary creds (1-12h)

Where Identity Center lives:

  • One region per organization — pick carefully; can't be moved post-setup.
  • Must be enabled from the management account of the AWS Organization.
  • Requires AWS Organizations (cannot run standalone).

Permission set scoping:

  • Up to 50 managed policies + 1 customer-managed policy + inline policy per permission set.
  • Session duration: 1 to 12 hours (controlled at permission-set level).
  • Permission sets can include a permissions boundary — adds another ceiling on top of the policies.

Common patterns:

  • Just-in-time admin elevation: regular users have read-only permission set; on-call engineer gets a 'BreakGlassAdmin' permission set assigned for a 1-hour shift. Trail logs every assumption.
  • Multi-account access: one user account in Identity Center; assignments to 30 AWS accounts with role-appropriate permission sets in each. No per-account IAM users to manage.
  • Audit access: read-only permission set assigned to the audit team across all accounts; no production write capability ever.

Anti-patterns the exam will probe:

  • Creating IAM users in each member account 'for the developer to log in' — wrong; permission set + account assignment is the answer.
  • Using Identity Center for application end-user authentication — wrong; that's Cognito User Pools. Identity Center is for workforce (employees) accessing AWS.
  • Trying to enable Identity Center from a non-management account — fails by design; must be management account. See What is IAM Identity Center?[7] for the canonical setup walkthrough.

Workload identity patterns: IRSA (EKS), OIDC for GitHub Actions, EC2 IMDSv2

Workload identity = how a non-human process (container, Lambda, on-prem CI runner) gets temporary AWS credentials without storing a long-lived secret.

1. EC2 — Instance Metadata Service (IMDS).

The EC2 instance has an instance profile (an IAM role[1]); the AWS SDK on the instance fetches temp credentials from 169.254.169.254/latest/meta-data/iam/security-credentials/<role-name>. Credentials rotate automatically.

IMDSv1 vulnerability: Server-Side Request Forgery (SSRF) attacks can trick a vulnerable app into requesting credentials from the metadata service and leaking them.

Fix — enforce IMDSv2[2]: requires a session token (PUT request) before any credentials fetch. SSRF can't follow the multi-step protocol. Enforce via:

  • New instances: launch template / RunInstances with HttpTokens=required.
  • Existing instances: aws ec2 modify-instance-metadata-options --http-tokens required.
  • Org-wide: SCP denying instance launches without HttpTokens=required.

2. ECS — Task Role[4].

Every task in a task definition can have an IAM role. The ECS agent fetches temp credentials and exposes them at a task-local endpoint (169.254.170.2/v2/credentials/<id>). The SDK auto-discovers via env var AWS_CONTAINER_CREDENTIALS_RELATIVE_URI.

Distinct from execution role:

  • Execution role: ECS agent uses this to pull container images from ECR, write logs to CloudWatch. Operational.
  • Task role: the application inside the container uses this to call AWS APIs. App-specific.

Common mistake: putting app-needed permissions on execution role. Wrong layer.

3. EKS — IAM Roles for Service Accounts (IRSA)[5].

Kubernetes pods can have an IAM role mapped via the pod's service account. The pod gets a projected token; the AWS SDK exchanges it for temp credentials via sts:AssumeRoleWithWebIdentity[6].

Setup:

  1. Enable OIDC provider on the EKS cluster (eksctl utils associate-iam-oidc-provider).
  2. Create IAM role with trust policy permitting the OIDC provider + a specific service account namespace.
  3. Annotate the Kubernetes service account: eks.amazonaws.com/role-arn: arn:aws:iam::...:role/MyRole.
  4. Pods using that SA automatically get credentials.

Much better than the alternative: kube2iam or instance-profile inheritance (which means every pod on the node has the same permissions — least-privilege violation).

4. GitHub Actions — OIDC web-identity.

GitHub Actions runners can request a JWT from GitHub's OIDC provider. AWS trusts that provider via an IAM OIDC identity provider in your account, then runners call sts:AssumeRoleWithWebIdentity directly.

Setup:

  1. In your AWS account, create an OIDC identity provider for https://token.actions.githubusercontent.com (thumbprint and audience pre-known).
  2. Create an IAM role with a trust policy pinning the OIDC issuer + audience + sub (repo + branch).
  3. In the Actions workflow, use aws-actions/configure-aws-credentials@v4 with role-to-assume — no AWS access keys in repo secrets.

Result: short-lived credentials (default 1h) bound to a specific repo + branch. Compromised workflow = limited blast radius.

5. Lambda — execution role[3].

Triviall the simplest: set role at function creation. Credentials available via env vars and the SDK auto-discovers. Rotated per invocation by the Lambda runtime.

The pattern across all five: AWS trusts the issuer (IMDS, EKS OIDC, GitHub OIDC) and the workload identity is validated against that trust. No long-lived secret ever lives in the workload.

Common cross-account antipatterns and the policies that fix them

Antipattern 1 — Sharing an IAM user's access keys across accounts.

Wrong: 'Developer needs to deploy to dev and prod, so we put their keys in both.' This violates the IAM security best practices[17] recommendation against long-lived credentials.

Right: Create roles DeployDev in dev account, DeployProd in prod account. Trust policies permit the developer's IAM principal in their home account. They aws sts assume-role to switch context. Audit trails show which role was used in which account.

Antipattern 2 — Principal: * in bucket policies for 'partner' access.

Wrong: 'Partner needs to upload to our S3 bucket, so we made it public for writes.'

Right: Resource policy permits the partner's specific account (or role):

{ "Effect": "Allow",
  "Principal": { "AWS": "arn:aws:iam::PARTNER_ACCOUNT:root" },
  "Action": "s3:PutObject",
  "Resource": "arn:aws:s3:::our-bucket/uploads/*",
  "Condition": { "StringEquals": { "s3:x-amz-acl": "bucket-owner-full-control" } }
}

The bucket-owner-full-control condition ensures uploaded objects are owned by us, not the partner — otherwise we can't read what they upload.

Antipattern 3 — Forgetting sts:ExternalId for third-party SaaS roles.

Wrong: SaaS vendor (monitoring, backup, security) gives you a CloudFormation template that creates a role with trust policy Principal: arn:aws:iam::VENDOR:root. Anyone at the vendor with your role ARN can assume it.

Right: Trust policy includes Condition: { StringEquals: { sts:ExternalId: "your-unique-id" } }. Vendor supplies a per-tenant ExternalId; impossible to assume your role without it.

Antipattern 4 — Listing individual partner account ARNs in 50 places.

Wrong: 'We have 30 partner accounts; we maintain a list in every bucket policy and trust policy.'

Right: If partners are all in the same AWS Organization, use aws:PrincipalOrgID:

"Condition": {
  "StringEquals": { "aws:PrincipalOrgID": "o-abc123def4" }
}

New partner joins the org → automatically covered.

Antipattern 5 — Granting full IAM admin to developers 'for convenience'.

Wrong: 'Developers need to make IAM roles for their Lambdas, so they have iam:*.'

Right: Developers have iam:CreateRole / iam:PutRolePolicy but their identity policy includes a condition requiring iam:PermissionsBoundary on every role they create:

"Condition": {
  "StringEquals": {
    "iam:PermissionsBoundary": "arn:aws:iam::ACCOUNT:policy/DevBoundary"
  }
}

Now they can create roles, but every role they create has DevBoundary capping its powers.

Antipattern 6 — 'Centralised security account' that uses a hand-rolled cross-account scheme.

Wrong: Custom Lambda + custom roles + custom rotation script.

Right: AWS Organizations auto-creates OrganizationAccountAccessRole in every member account at the time they join the org. The management account (and any account given the right SCP scope) can assume it directly. Use this; don't build your own.

Identity primitives — when each is the right answer

PrimitiveCredential lifetimeHow it's obtainedBest forCommon trap
IAM userPermanent until rotated/deletedAccess key + secret created in console / CLILast-resort exception only (legacy SaaS, break-glass)Rotation discipline; orphaned keys; no MFA on programmatic users
IAM role (assumed)Temporary; default 1h, max 12h via MaxSessionDuration`sts:AssumeRole` + trust policy permits callerCross-account access, workload identity, just-in-time elevationForgetting to set MaxSessionDuration explicitly; trust policy too permissive
Instance profile (EC2)Temporary; rotated by IMDSAttached at instance launch or via Modify-Instance-AttributeEC2 instances calling AWS APIsIMDSv1 is SSRF-vulnerable — enforce IMDSv2 (`HttpTokens=required`)
Lambda execution roleTemporary; rotated per invocationSet at function creation; one role per functionLambda functions calling AWS APIsShared mega-role across many functions defeats least-privilege
ECS task roleTemporary; rotated per taskSet in task definition (distinct from execution role)Containers in tasks calling AWS APIsConfusing task role (app permissions) with execution role (image pull + logs)
Federated user (IAM Identity Center)Temporary; session duration capped by both IdP and roleIdP authentication → Identity Center → AssumeRoleWorkforce SSO at any scaleForgetting Identity Center is region-bound; pick the home region carefully
Federated user (OIDC)Temporary; per-token`sts:AssumeRoleWithWebIdentity` + IdP issuer + audienceGitHub Actions, IRSA on EKS, on-prem CITrust policy must pin `aud` and `sub` — wildcards = trust any caller
Cognito identity (end-user)Temporary; via Identity PoolUser Pool token exchanged at Identity PoolWeb/mobile end-users with direct AWS access (uncommon)Almost always wrong on SAA — prefer API Gateway + Lambda + IAM role

Decision tree

Workload or workforce?WorkloadWorkforceRuns inside AWS compute?YesNo< 5 AWS-only users?YesNoInstance profile/ Lambda role/ ECS task roleSupports OIDC?YesNoIAM users+ MFA + consoleaccess onlyIAM IdentityCenter + IdP(SAML / OIDC)OIDC + AssumeRoleWithWebIdentity(GitHub, EKS, etc.)IAM user keys(last resort —rotate, audit)Always: MFA on root + console; SCPs at org root; Access Analyzer enabled

Sharp facts the exam loves — give these one last read before exam day.

Cheat sheet

Sharp facts the exam loves — scan these before test day.

Workload identity: roles, never static keys

EC2 → instance profile, Lambda → execution role, ECS → task role, EKS → IRSA, GitHub Actions → OIDC AssumeRoleWithWebIdentity. Static access keys are last resort only (legacy SaaS without OIDC). Roles deliver short-lived auto-rotating credentials; keys are permanent secrets that leak once and stay compromised.

4 questions test this
Workforce identity: federate via IAM Identity Center

For 5+ humans, centralize in your IdP (AD, Okta, Entra, Google) and bind via Identity Center permission sets to (group × account × permission set). Local IAM users are a 2-3-person startup pattern at most; Cognito User Pools are for end-customers, not workforce. AWS rebranded AWS SSO to IAM Identity Center[7] in 2022. On the exam, treat 'AWS SSO' and 'IAM Identity Center' as synonymous — same service. Identity Center replaces federated IAM for workforce access in any new design.

Permission boundaries cap, they don't grant

A permission boundary is the ceiling on what an IAM principal can do — never a grant. Effective permissions = identity Allow ∩ boundary Allow ∩ no Deny ∩ no SCP Deny. Use boundaries to delegate role creation to developers without iam:* escalation.

6 questions test this
Cross-account = trust policy + AssumeRole

Never share access keys across accounts. Account B creates a role whose trust policy permits principals in account A; A calls sts:AssumeRole. For third-party SaaS add sts:ExternalId to defeat confused-deputy; for human-assumed roles add aws:MultiFactorAuthPresent.

10 questions test this
Cross-account confused-deputy needs sts:ExternalId

When a third-party SaaS assumes a role in your account, ONLY pinning the vendor's principal ARN in the trust policy is unsafe — a different customer of the same vendor can be tricked into using your role ARN. Pin sts:ExternalId[12] in the trust policy's Condition. The vendor supplies a unique ID per customer; your account validates it on every AssumeRole.

5 questions test this
SCPs are guardrails, not grants

Service Control Policies[10] attached at the AWS Organizations root or OU level can only RESTRICT what member accounts can do. They never grant permission. Common trap: 'How do I let account B access a bucket in account A?' — SCP is wrong; the answer is a bucket policy in A + a role in A assumed by a principal in B.

1 question tests this
Service-linked roles are AWS-managed, hands-off

When you enable Auto Scaling, Organizations, ECS, Lambda@Edge, etc., AWS auto-creates a service-linked role[11] in your account. You can't author or scope it. Exam tip: if the question asks 'how do I grant Service X permission to act on my behalf', the answer is usually 'service-linked role already exists' — not a new role you create.

Enforce IMDSv2 on every EC2 instance

Instance Metadata Service v1 is SSRF-vulnerable — a flaw in a webapp can trick the server into requesting credentials from 169.254.169.254 and leaking them. IMDSv2[2] requires a session token (PUT) before any GET, breaking SSRF chains. Enforce via launch template HttpTokens=required, or org-wide via SCP denying instance launches without it.

Policy evaluation: explicit Deny always wins

Across all four layers (SCP, permission boundary, identity policy, resource policy), an explicit Deny anywhere short-circuits the entire evaluation[13]. No Allow can override it. Common debug pattern: 'Admin user can't access bucket' → check resource policy for an explicit Deny on their principal.

1 question tests this
STS session duration: 1h default, 12h max via MaxSessionDuration

Roles default to a 1-hour session; raise via the role's MaxSessionDuration[14] (1-12 hours). IAM Identity Center permission sets have their own session duration (1-12h). Federation sessions are capped by BOTH the IdP token life AND the role's MaxSessionDuration — the shorter wins.

AWS Organizations: OrganizationAccountAccessRole is the auto-trust role

When a new account joins (or is created in) AWS Organizations[15], an OrganizationAccountAccessRole is auto-provisioned with full admin in the new account, trusted by the management account. Pattern: central security account assumes this from management account → no per-account setup.

Access Analyzer surfaces unintended cross-account / public exposure

IAM Access Analyzer[16] scans resource policies (S3 buckets, IAM roles, KMS keys, Lambda functions, SQS queues, Secrets Manager) and flags grants to principals outside your account / org. Free to enable per region; finds the "this bucket policy accidentally allows the internet" problem.

aws:PrincipalOrgID restricts access to AWS Organization members

The aws:PrincipalOrgID global condition key in bucket policies (or other resource policies) ensures that only principals whose accounts are members of the specified AWS Organization can access the resource. It automatically includes any new accounts added to the organization without requiring policy updates. This is the recommended alternative to listing every account ID in the Principal element, and it scales automatically as the organization grows.

5 questions test this
Cross-account S3 access requires both a bucket policy and an IAM policy

When the requesting identity and the S3 bucket are in different AWS accounts, access requires permissions in both accounts: a bucket policy in the resource account granting the specific action to the cross-account principal, and an IAM identity-based policy in the requesting account granting access to the bucket ARN. Neither policy alone is sufficient for cross-account access.

9 questions test this

Also tested in

References

  1. Official AWS guide to IAM roles
  2. Configure instance metadata options for existing instances
  3. Lambda execution role
  4. Task IAM role (Amazon ECS)
  5. IAM roles for service accounts (EKS / IRSA)
  6. AssumeRoleWithWebIdentity — AWS STS API reference
  7. What is IAM Identity Center?
  8. Amazon Cognito user pools
  9. IAM permissions boundaries for IAM entities
  10. Service control policies (SCPs)
  11. Using service-linked roles
  12. How to use trust policies with IAM roles (confused-deputy + ExternalId)
  13. IAM JSON policy evaluation logic
  14. Modify the maximum CLI/API session duration for a role
  15. What is AWS Organizations
  16. What is IAM Access Analyzer
  17. Security best practices in IAM