IAM Policy Evaluation Logic Step-by-Step - Explicit Deny, SCP, RCP, Resource Policy, Identity Policy, Permission Boundary, and Session Policy

First Published:
Last Updated:

This article walks through the AWS IAM policy evaluation logic stage by stage, introducing a single naming frame, The IAM Decision Diamond, that the rest of the article treats as canonical. The Diamond names seven evaluation stages — Explicit Deny, SCP, RCP, Resource-Based Policy, Identity-Based Policy, Permission Boundary, and Session Policy — and explains why each stage either short-circuits to deny, gates the next stage, or allows the request to pass through.

The article is a deliberate companion to the IAM Policy Simulator (Offline) on this site. Every walk-through case carries a Reproduce in Simulator? badge (YES, PARTIAL, or NO) and, for YES cases, a three-step recipe to replay the case in the Simulator. The NO cases are exactly the situations where the Simulator's documented scope ends — SCP, RCP, Permission Boundary, Session Policy — and the article spells out how to reason through them with public AWS documentation when an online sandbox is not available.

If you want the evaluation order, see §2 and the IAM Decision Diamond figure. If you came here to walk twelve concrete request shapes through the Diamond, jump straight to §3. If you want to wire the Simulator into your review workflow, jump to §6.


1. Why IAM Policy Evaluation is Confusing

AWS IAM evaluation now spans seven layers — six configurable policy types (Identity-Based, Resource-Based, Permission Boundary, SCP, RCP, Session) plus the universal Explicit Deny check that fires across all of them — accumulated over fifteen years of production hardening, and each layer was added to solve a real production problem. The result is a system where a single API request — one principal, one action, one resource — is filtered through up to seven separate evaluations before the request returns Allow or AccessDenied. The complexity is not accidental. It reflects fifteen years of layered defense-in-depth requirements. But it is unforgiving: misread any one of the seven layers and you ship a policy that looks correct in review and evaluates to the opposite of what you intended.

This section addresses why the confusion persists, lists the five most common misconceptions we see in real reviews, and shows the Scope Map that tells you, at a glance, which parts of evaluation can be reproduced in our offline Simulator and which parts cannot.

1.1 Seven Evaluation Layers in One Request

When an AWS service evaluates an API call, every applicable layer below is checked. None is skipped, even if not configured — an unconfigured ceiling simply does not deny. The canonical evaluation order is shown in §2 and the IAM Decision Diamond figure; the list below introduces each layer category-by-category.

  1. Identity-based policies — IAM user, group, and role policies attached to the calling principal.
  2. Resource-based policies — bucket policies, KMS key policies, SQS access policies, Secrets Manager resource policies, Lambda resource policies, IAM role trust policies.
  3. Permission Boundary — a managed policy that caps the maximum effective permissions of an IAM user or role.
  4. Service Control Policy (SCP) — an Organizations-level policy that caps the maximum permissions for principals in an account or OU.
  5. Resource Control Policy (RCP) — an Organizations-level policy that caps what can be done to resources in an account or OU. New since November 2024.
  6. Session policy — a policy passed at AssumeRole time as the Policy or PolicyArns parameter, which intersects with the role's effective permissions for the resulting session.
  7. Implicit / Explicit Deny check — not a policy type itself but a cross-cutting evaluation step: the request is denied implicitly when nothing allows it, or explicitly when any of the above contains a matching "Effect": "Deny".
A junior engineer who reads a single Identity policy and concludes "this grants S3:GetObject" is reading exactly one of seven layers. The other six can each independently deny the request. The mental shortcut "if the policy attached to my role allows X, the role can do X" is wrong in the general case.

1.2 Five Common Misconceptions

In code review and incident review, the same five misconceptions account for more than half of all IAM-related findings:

  1. "If I have an Allow, the request goes through." A single Allow is necessary but not sufficient. Any SCP, RCP, Permission Boundary, or Session Policy that does not include the action will quietly cause an Implicit Deny at that ceiling. Conversely, any Explicit Deny anywhere in the seven layers overrides every Allow.
  2. "Deny only matters when I write it." The default for AWS IAM is Implicit Deny. A statement that does not grant anything is, in effect, denying everything that is not granted elsewhere. Engineers who internalize "explicit Allow" frequently forget that "nothing matches" produces the same outcome as "Effect": "Deny" — and the two are usually indistinguishable in CloudTrail.
  3. "SCP Allow means access is granted." An SCP is a filter, not a grant. The default FullAWSAccess SCP says "do not constrain the account", not "everyone in the account has full AWS access". You still need an identity policy or resource policy that grants the specific action.
  4. "Identity policy and resource policy fight." They do not fight; they combine. In same-account access, the request is allowed if either policy allows it (union semantics). In cross-account access, both sides must allow (intersection semantics). The context-dependent switch between union and intersection is one of the largest single sources of confusion.
  5. "Permission Boundary is just another permission set." A Permission Boundary does not grant anything. If a role has Identity Policy s3:PutObject and Permission Boundary s3:GetObject, the effective permission is nothing. The intersection of {PutObject} and {GetObject} is empty. Treating the Boundary as a permission grant rather than a ceiling produces silent Implicit Denies that are extremely hard to debug.

1.3 Scope Map — What This Article and the Simulator Cover

The companion IAM Policy Simulator (Offline) on this site evaluates Identity Policy + Resource Policy + Condition keys, but does not model SCP, RCP, Permission Boundary, or Session Policy. That is a deliberate scope decision: the Simulator is a single-page offline tool you can run with no AWS account, no IAM permissions, and no network call. The price is that four of the seven Diamond stages must be reasoned about by hand.

This article fills in the missing four. Every walk-through case carries one of three coverage badges:

  • YES — the case can be fully reproduced in the Simulator.
  • PARTIAL — the Simulator returns a result, but the result alone is not the full story (typically: cross-account, where Simulator does not enforce both-sides-required).
  • NO — the case depends on a Diamond stage the Simulator does not model. The article tells you what AWS Policy Simulator (the AWS-hosted official one) or a sandbox account would show, and walks through the reasoning.
The 12 cases in §3 are distributed deliberately: 6 YES, 2 PARTIAL, 4 NO. The intent is that the YES cases give you immediate hands-on confirmation, the PARTIAL cases force you to notice where the offline Simulator under-models reality, and the NO cases anchor the cross-Org / boundary / session reasoning that no offline single-page tool can reach.

2. The IAM Decision Diamond

The Diamond is a single seven-stage flowchart that names every stage of AWS IAM evaluation. We use the name The IAM Decision Diamond because the shape of the evaluation is genuinely diamond-like: the request enters at the top, passes through a fixed sequence of decision boxes, and exits at the bottom either as Allow or as Deny (Implicit or Explicit). At every stage, an Explicit Deny short-circuits the entire evaluation to Deny without looking at later stages.

The figure below shows the canonical order. Carry this figure in your head. Most IAM debugging time is spent at one of these seven nodes.

Fig. 1: The IAM Decision Diamond — seven-stage IAM policy evaluation order
Fig. 1: The IAM Decision Diamond — seven-stage IAM policy evaluation order

2.1 How to Read the Diamond

Two visual conventions matter:

  • Vertical flow — top to bottom in the order Explicit Deny, SCP, RCP, Resource Policy, Identity Policy, Permission Boundary, Session Policy. Every request passes through every stage that applies.
  • Side arrows to DENY — any stage can short-circuit the evaluation to Deny. Some stages can produce Implicit Deny (no match) and some can produce Explicit Deny (a matching Deny statement). The distinction matters for debugging, even though both produce AccessDenied in the API response.
Two logical conventions matter:

  • Intersection (ceiling) stages — SCP, RCP, Permission Boundary, Session Policy. These do not grant; they cap. The request must be allowed by every applicable ceiling.
  • Union (granting) stages — Resource Policy and Identity Policy. In same-account access, either one is sufficient. In cross-account access, both are required.

2.2 Stage 1: Explicit Deny

Explicit Deny is the only universal short-circuit in the entire system. If any of the seven policy types contains a matching "Effect": "Deny" statement, the evaluation halts and returns AccessDenied immediately. There is no "Allow that overrides Deny", anywhere, ever. This is the one absolute rule in IAM.

A pedagogical note: AWS does not perform a separate "Deny-only" pre-pass before evaluating other stages. Explicit Deny is checked within each of the other six stages as their policy documents are parsed. We label it Stage 1 in the Diamond because it short-circuits the rest of the evaluation the moment it matches at any layer — treating it as the first thing to look for is the right reading order when debugging an AccessDenied.

The implication is that Deny is the strongest possible IAM primitive. Use it sparingly and use it precisely — a broad "Resource": "*" Deny in an Identity policy will produce confusing failures across actions you did not intend to restrict. But a tight, narrow Explicit Deny is the most reliable way to guarantee a behavior across an organization. SCPs and RCPs are built on top of this guarantee.

2.3 Stage 2: Service Control Policy (SCP)

SCPs apply at the AWS Organizations level (root, OU, or account). They cap the maximum permissions for principals in the affected account: every IAM user, role, and federated identity in that account is constrained by the union of the SCPs at the root, the OU chain, and the account itself.

Two SCP modes exist:

  • Allow-list mode — the SCP enumerates allowed actions. Everything not listed is denied. The default FullAWSAccess policy is an allow-list with "Action": "*", "Resource": "*".
  • Deny-list mode — the SCP enumerates denied actions. Everything else passes the SCP filter. Most production SCPs are deny-list.
An SCP does not grant. Removing FullAWSAccess from an OU does not "remove the default access"; it makes every action in that OU implicitly denied at the SCP layer, regardless of what identity policies say. The first principle of SCP design: keep FullAWSAccess attached at the root unless you understand exactly which actions will become impossible.

2.4 Stage 3: Resource Control Policy (RCP)

RCPs are the resource-side counterpart of SCPs, introduced in November 2024. Where SCPs constrain what principals in an Organization account can do, RCPs constrain what can be done to resources in an Organization account. The set of services that support RCPs is smaller than SCPs (S3, STS, KMS, SQS, and Secrets Manager at launch) and growing.

The mental model: SCP is a perimeter around the caller; RCP is a perimeter around the resource. If the caller's account has an SCP that allows s3:PutObject and the resource's account has an RCP that denies s3:PutObject from external principals, the request fails at the RCP stage, not the SCP stage.

RCPs are particularly useful for stopping accidental public-bucket configurations across an entire OU without depending on every account's bucket policy being correct.

2.5 Stage 4: Resource-Based Policy

Resource-based policies attach to the resource rather than the principal. The canonical examples are S3 bucket policies, KMS key policies, SQS queue policies, Secrets Manager resource policies, Lambda resource policies, and IAM role trust policies. (The role trust policy is technically a resource policy attached to the role-as-resource.)

A resource policy lists Principal explicitly. Without an explicit Principal matching the caller, the resource policy does not grant the caller anything.

The key behavior:

  • Same-account access — the request is allowed if either the resource policy or the identity policy allows it. Union semantics.
  • Cross-account access — the request requires both a resource policy on the resource side (the resource owner explicitly grants access to the external principal) and an identity policy on the caller side. Intersection semantics.
This same-account-vs-cross-account split is the single biggest source of confusion in IAM. We dedicate §4 to it.

2.6 Stage 5: Identity-Based Policy

Identity-based policies attach to the principal: an IAM user, IAM group (which is a membership shortcut, not a real principal), or IAM role. Identity-based policies do not list Principal — the principal is the entity the policy is attached to.

In same-account access, an Identity policy alone can authorize an action (no resource policy needed) for most services. The notable exception is AWS KMS: the key policy must explicitly enable IAM delegation (the "Enable IAM User Permissions" statement that grants kms:* to arn:aws:iam::<account>:root) before any identity policy can authorize an action on that key. Always check the service's authorization documentation for the specific service-level rules.

Effective permissions across multiple attached identity policies are the union of all statements. Attaching ReadOnlyAccess plus a custom PutObject policy gives you both read-only-everywhere and PutObject on the specified resources.

2.7 Stage 6: Permission Boundary

A Permission Boundary is a managed policy attached to an IAM user or role as that principal's ceiling. The effective permissions of the principal are the intersection of:

  • The union of all identity-based policies attached to the principal, and
  • The Permission Boundary.
If the identity policy grants s3:* and the Permission Boundary grants s3:Get*, the principal can only do s3:Get*. If the identity policy grants ec2:* and the Permission Boundary grants s3:*, the principal can do neither — the intersection of {ec2:*} and {s3:*} is empty.

Permission Boundaries are critical for delegated administration. The pattern is: grant developers iam:CreateRole permission, but require that any role they create has a specified Permission Boundary attached. This bounds what the developers' roles can do without bounding what the developers can write into the identity policy.

2.8 Stage 7: Session Policy

Session policies are passed at AssumeRole time. The caller invokes sts:AssumeRole (or AssumeRoleWithSAML, AssumeRoleWithWebIdentity) with a Policy parameter (JSON-inline) or PolicyArns (managed policy ARNs). The resulting temporary credentials have effective permissions equal to the intersection of:

  • The role's identity policy and permission boundary (everything from stages 5 and 6), and
  • The session policy.
Session policies cannot grant anything the role does not already have. They can only narrow. The use case is least-privileged delegation: a CI/CD system assumes a powerful deployer role, but for a specific job, the CI system narrows the session to only the resources for that one deployment. If the job is compromised, the blast radius is the session policy, not the role.

3. Step-by-Step Walk-through with Concrete Examples

The twelve cases below walk through the Diamond in concrete terms. Each case is presented with the request shape (principal, action, resource, context), the policy JSON involved, a step-by-step trace through the Diamond stages, the final result, and the Reproduce in Simulator? badge with reproduction steps.

We group the cases by Simulator coverage:

  • 3.1 Fully Simulator-Reproducible (Cases 1–4 and 6–7) — Identity Policy + Resource Policy + Conditions only.
  • 3.2 Partial Simulator Coverage (Cases 5 and 12) — Resource policy patterns that hit the same-account-vs-cross-account boundary the Simulator does not model.
  • 3.3 Beyond the Simulator (Cases 8–11) — SCP, RCP, Permission Boundary, Session Policy.
The walkthrough uses a consistent format for every case so you can scan or skim. Read in order for the first four cases to build the trace pattern; after that, use the table of contents to jump to the case you need.

3.1 Cases 1-4 and 6-7: Fully Simulator-Reproducible

Case 1 — Basic Allow via Identity Policy YES

Scenario: An IAM user in account 111122223333 has an identity policy granting s3:GetObject on a specific bucket. The user invokes GetObject on that bucket. No resource policy is configured. No SCP, RCP, Permission Boundary, or Session Policy is in effect.

Policy JSON (Identity):

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

Diamond trace:

  1. Explicit Deny — no Deny statements anywhere. Pass.
  2. SCP — not configured. Pass.
  3. RCP — not configured. Pass.
  4. Resource Policy — not configured. Defers to identity policy.
  5. Identity PolicyAllowS3Read matches the action and resource. Allow.
  6. Permission Boundary — not configured. Pass.
  7. Session Policy — not an assumed-role session. Not applicable.
Result: Allow.

Reproduce in Simulator (3 steps):

  1. Open IAM Policy Simulator (Offline).
  2. Paste the Identity policy JSON above into the Identity-based Policies panel.
  3. Set Action = s3:GetObject, Resource ARN = arn:aws:s3:::example-bucket/file.txt, Principal ARN = arn:aws:iam::111122223333:user/alice. Click Evaluate. The badge will show Allow.
Teaching point: A simple Allow at the identity layer is the easiest case but also the one that lulls engineers into the misconception "if it works in identity policy, it works". Cases 8–11 below break that assumption.

Case 2 — Explicit Deny Overrides Allow YES

Scenario: The same IAM user as Case 1, but the identity policy also contains an Explicit Deny statement that overlaps the Allow. The user invokes GetObject. The Deny takes precedence.

Policy JSON (Identity):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowS3Read",
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::example-bucket/*"
    },
    {
      "Sid": "DenyConfidential",
      "Effect": "Deny",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::example-bucket/confidential/*"
    }
  ]
}

Diamond trace (request: GetObject on arn:aws:s3:::example-bucket/confidential/secret.txt):

  1. Explicit DenyDenyConfidential matches. Short-circuit to Deny.
  2. The remaining stages are not evaluated.
Result: ExplicitDeny.

Reproduce in Simulator (3 steps):

  1. In the Simulator, paste the full identity policy with both statements.
  2. Set Resource ARN = arn:aws:s3:::example-bucket/confidential/secret.txt.
  3. Click Evaluate. The badge will show ExplicitDeny, and the trace will list the DenyConfidential statement as the matching deny.
Teaching point: Explicit Deny is the most reliable IAM primitive. Use it for resource subsets you want to guarantee inaccessible even if a future identity policy grants broader access.

Case 3 — MFA Condition Gate with BoolIfExists YES

Scenario: A role permits ec2:TerminateInstances only when the session was authenticated with MFA. Without MFA, the request must be denied. The policy uses BoolIfExists so that sessions without an MFA context (long-term access keys) are also blocked.

Policy JSON (Identity):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowTerminateWithMFA",
      "Effect": "Allow",
      "Action": "ec2:TerminateInstances",
      "Resource": "*"
    },
    {
      "Sid": "DenyTerminateWithoutMFA",
      "Effect": "Deny",
      "Action": "ec2:TerminateInstances",
      "Resource": "*",
      "Condition": {
        "BoolIfExists": { "aws:MultiFactorAuthPresent": "false" }
      }
    }
  ]
}

Diamond trace (request: TerminateInstances with aws:MultiFactorAuthPresent = false):

  1. Explicit DenyDenyTerminateWithoutMFA matches because MultiFactorAuthPresent is false. Short-circuit to Deny.
Result: ExplicitDeny.

Diamond trace (request: TerminateInstances with aws:MultiFactorAuthPresent = true):

  1. Explicit DenyDenyTerminateWithoutMFA condition does not match (MFA is present). Pass.
  2. Identity PolicyAllowTerminateWithMFA matches. Allow.
Result: Allow.

Reproduce in Simulator (3 steps):

  1. Paste the identity policy.
  2. Set Action = ec2:TerminateInstances, Resource ARN = *, and add Context Key aws:MultiFactorAuthPresent=false.
  3. Click Evaluate. Result: ExplicitDeny. Change the context key to true and re-evaluate: Allow.
Teaching point: The choice of Bool vs BoolIfExists is non-trivial. Bool would not match if aws:MultiFactorAuthPresent is absent from the session context (e.g., when using long-term access keys), so a Deny guarded by plain Bool leaves an open door. The IfExists suffix changes the missing-key behavior: BoolIfExists returns true when the key is absent, so the Deny still fires on long-term-key sessions. AWS recommends "BoolIfExists": {"aws:MultiFactorAuthPresent": "false"} in Deny statements for MFA gating.

Case 4 — IP Address Restriction YES

Scenario: A bucket should be readable only from the corporate IP range. Requests from outside that range get Implicit Deny because the Allow's condition does not match. There is no Explicit Deny — the condition failure produces no match at all.

Policy JSON (Identity):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowFromCorp",
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::corp-bucket/*",
      "Condition": {
        "IpAddress": { "aws:SourceIp": "203.0.113.0/24" }
      }
    }
  ]
}

Diamond trace (request: GetObject from aws:SourceIp = 198.51.100.5):

  1. Explicit Deny — no Deny statements. Pass.
  2. Identity PolicyAllowFromCorp condition does not match (IP outside CIDR). No statements match. No Allow.
Result: ImplicitDeny.

Reproduce in Simulator (3 steps):

  1. Paste the policy. Set Action = s3:GetObject, Resource ARN = arn:aws:s3:::corp-bucket/file.txt.
  2. Add Context Key aws:SourceIp=198.51.100.5.
  3. Click Evaluate. Result: ImplicitDeny. Change the IP to 203.0.113.42 to see Allow.
Teaching point: Implicit Deny is the silent killer. CloudTrail will log AccessDenied, but the cause is "no Allow matched", not "a Deny matched". When debugging, always check whether your Allow's Condition is matching the actual context.

Case 6 — ABAC with aws:ResourceTag YES

Scenario: A role can manage EC2 instances only when the instance's Team tag matches the role's Team session tag. This is the canonical Attribute-Based Access Control (ABAC) pattern.

Policy JSON (Identity):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ManageMyTeamInstances",
      "Effect": "Allow",
      "Action": ["ec2:StartInstances", "ec2:StopInstances", "ec2:DescribeInstances"],
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "aws:ResourceTag/Team": "${aws:PrincipalTag/Team}"
        }
      }
    }
  ]
}

Diamond trace (request: StartInstances with aws:PrincipalTag/Team = data and aws:ResourceTag/Team = data):

  1. Explicit Deny — none. Pass.
  2. Identity PolicyManageMyTeamInstances matches; condition aws:ResourceTag/Team == aws:PrincipalTag/Team evaluates data == data. Allow.
Result: Allow. If the resource tag were payments, the condition would not match and the result would be ImplicitDeny.

Reproduce in Simulator (3 steps):

  1. Paste the policy. Set Action = ec2:StartInstances.
  2. Add Context Keys aws:PrincipalTag/Team=data and aws:ResourceTag/Team=data.
  3. Click Evaluate. Result: Allow. Change aws:ResourceTag/Team to payments: ImplicitDeny.
Teaching point: ABAC scales because the same policy works for every team. The policy does not enumerate teams; it correlates the caller's tag with the resource's tag. The mental model: write the policy once, tag the resources and the principals, and IAM does the rest.

Case 7 — NotAction Misuse YES

Scenario: An engineer wants to grant "everything except IAM", and writes a NotAction Allow statement, intending "deny IAM, allow everything else". This is the wrong reading. NotAction with Allow means "Allow everything that is not in the NotAction list".

Policy JSON (Identity):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowEverythingExceptIAM",
      "Effect": "Allow",
      "NotAction": ["iam:*"],
      "Resource": "*"
    }
  ]
}

Diamond trace (request: s3:DeleteBucket on arn:aws:s3:::production-data):

  1. Explicit Deny — none. Pass.
  2. Identity PolicyAllowEverythingExceptIAM matches because s3:DeleteBucket is not in iam:*. Allow.
Result: Allow. (Likely the engineer's actual intent was to disallow IAM but keep all other actions controlled by a more restrictive policy — but NotAction with Allow and Resource: "*" is effectively "AllowAll except IAM", including destructive APIs.)

Reproduce in Simulator (3 steps):

  1. Paste the policy. Set Action = s3:DeleteBucket, Resource = arn:aws:s3:::production-data.
  2. Click Evaluate. Result: Allow.
  3. (Optional) Try Action = iam:CreateUser: ImplicitDeny (because the statement's NotAction excludes IAM, no match).
Teaching point: NotAction paired with Allow is rarely what you want. The two correct forms are:

  • NotAction paired with Deny ("Deny everything that is not in the list" — i.e., a default-deny allow-list inversion).
  • Explicit positive Action list ("Allow only these actions").
If you ever see NotAction + Allow + Resource: "*" in a code review, treat it as a finding.

3.2 Cases 5 and 12: Partial Simulator Coverage

Case 5 — Cross-Account Access via Resource Policy Only PARTIAL

Scenario: Account A's user wants to read an S3 bucket in Account B. The bucket policy in Account B grants s3:GetObject to Account A's user. Account A's user has no matching identity policy. The Simulator (which only looks at the policies you give it) returns Allow because the resource policy allows. The real AWS evaluation returns ImplicitDeny because cross-account access requires both sides to allow.

Policy JSON (Resource, on bucket in Account B):

{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "AllowAccountA",
    "Effect": "Allow",
    "Principal": { "AWS": "arn:aws:iam::111122223333:user/alice" },
    "Action": "s3:GetObject",
    "Resource": "arn:aws:s3:::accountB-bucket/*"
  }]
}

Diamond trace (real AWS, cross-account):

  1. Explicit Deny — none. Pass.
  2. Resource Policy (Account B side) — allows alice. Pass.
  3. Identity Policy (Account A side) — no identity policy grants s3:GetObject on accountB-bucket. Both-sides-required fails.
Result: ImplicitDeny (in real AWS).

Why the Simulator returns Allow: The Simulator does not have a concept of "which account a policy belongs to". It evaluates union semantics by default — if the Resource policy or Identity policy allows, the result is Allow. This is correct for same-account access (Case 12 below) but under-models cross-account.

How to validate the real behavior: Use the AWS-hosted Policy Simulator (which is account-aware), or test in a sandbox by attempting the cross-account GetObject and inspecting the CloudTrail event in Account A (errorCode: AccessDenied, errorMessage mentioning "no identity-based policy allows").

Teaching point: The same-account-vs-cross-account split is the single largest source of cross-org confusion. Memorize the rule: same-account = OR, cross-account = AND. See §4 for the full treatment.

Case 12 — Same-Account Resource Policy Allow with No Identity Policy PARTIAL

Scenario: An IAM user in account 111122223333 has no identity policy granting s3:GetObject. The bucket (also in 111122223333) has a resource policy granting the user s3:GetObject. The user reads. In real AWS, this is Allow because same-account semantics are union. The Simulator agrees, but the reason it gets the right answer here is that it always uses union — which is right for same-account and wrong for cross-account.

Policy JSON (Resource, on bucket in same account):

{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "AllowAlice",
    "Effect": "Allow",
    "Principal": { "AWS": "arn:aws:iam::111122223333:user/alice" },
    "Action": "s3:GetObject",
    "Resource": "arn:aws:s3:::same-account-bucket/*"
  }]
}

Diamond trace (same-account):

  1. Explicit Deny — none. Pass.
  2. Resource Policy — allows alice. Pass.
  3. Identity Policy — no matching statement. But because this is same-account, the resource-policy Allow is sufficient.
Result: Allow.

Reproduce in Simulator (3 steps):

  1. Paste the resource policy into the Resource-based Policies panel.
  2. Leave Identity Policies empty.
  3. Set Principal = arn:aws:iam::111122223333:user/alice, Action = s3:GetObject, Resource = arn:aws:s3:::same-account-bucket/file.txt. Result: Allow.
Teaching point: This case is PARTIAL rather than YES because the Simulator's "always union" behavior incidentally gets the same-account case right. If you change the Principal ARN to a different account (e.g., 222233334444), the Simulator will still return Allow, which is wrong for the cross-account case. The Simulator is a useful tool, but you cannot use it to test cross-account boundaries.

3.3 Cases 8-11: Beyond the Simulator

These cases involve Diamond stages the Simulator does not model. We trace the evaluation by hand and tell you what would happen in a real AWS environment.

Case 8 — SCP Blocks Despite Identity Policy Allow NO

Scenario: An OU-level SCP denies s3:DeleteBucket. A role in the OU has identity policy s3:* on *. The role calls DeleteBucket. Identity policy allows. SCP denies. SCP wins.

Policy JSON (SCP):

{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "DenyBucketDeletion",
    "Effect": "Deny",
    "Action": "s3:DeleteBucket",
    "Resource": "*"
  }]
}

Diamond trace:

  1. Explicit Deny — SCP contains an Explicit Deny matching s3:DeleteBucket. Short-circuit to Deny at the SCP layer.
Result: ExplicitDeny.

Why the Simulator does not model this: The Simulator does not have an SCP input. It evaluates only Identity and Resource policies. To test SCP behavior, use the AWS-hosted Policy Simulator (which integrates with your real Organizations configuration) or test in a sandbox account attached to a test OU.

Reasoning shortcut: An SCP deny in a deny-list SCP is equivalent to an Explicit Deny at every principal in every affected account. There is no way around it short of removing the SCP attachment.

Teaching point: When debugging "the role has admin permissions but the API call fails", check the SCPs attached to the account and OU chain. Use aws organizations list-policies-for-target --target-id <account-id> --filter SERVICE_CONTROL_POLICY to enumerate the effective SCPs.

Case 9 — Permission Boundary Caps a Role NO

Scenario: A role has identity policy s3:* on * but a Permission Boundary that only allows s3:Get*. The role calls s3:PutObject. Identity policy allows. Permission Boundary does not include PutObject. The effective permission is the intersection, which is empty for PutObject.

Policy JSON (Permission Boundary):

{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "BoundReadOnly",
    "Effect": "Allow",
    "Action": "s3:Get*",
    "Resource": "*"
  }]
}

Diamond trace:

  1. Explicit Deny — none. Pass.
  2. Identity Policys3:* allows PutObject. Pass.
  3. Permission Boundarys3:Get* does not include s3:PutObject. The intersection at this stage is empty.
Result: ImplicitDeny.

Why the Simulator does not model this: Permission Boundary is a separate policy slot in AWS but is not represented in the Simulator. To validate, use aws iam simulate-principal-policy --policy-source-arn <role-arn> (which is the AWS-hosted Policy Simulator API call) — it respects boundaries when the role has one attached.

Reasoning shortcut: Permission Boundary is the intersection on top of identity policy. If you want to test "what can my role actually do?", compute identity_policy ∩ permission_boundary by hand for the action set you care about.

Teaching point: Permission Boundaries are silent. There is no AWS API call you can make that returns "the boundary blocked this". You see AccessDenied. The boundary is exactly the kind of failure that produces "the policy says I can but I can't" tickets.

Case 10 — Session Policy Narrows AssumeRole NO

Scenario: A CI/CD system assumes a deployer role with identity policy s3:* on *, but passes a session policy that further narrows access to a single bucket. The session can write to that bucket, but not others.

Policy JSON (Session Policy, passed to AssumeRole):

{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "ThisDeploymentOnly",
    "Effect": "Allow",
    "Action": "s3:*",
    "Resource": "arn:aws:s3:::deploy-artifacts-2026-05-13/*"
  }]
}

Diamond trace (request: s3:PutObject on arn:aws:s3:::deploy-artifacts-2026-05-13/release.zip):

  1. Explicit Deny — none. Pass.
  2. Identity Policys3:* on * allows. Pass.
  3. Session Policy — allows s3:* on deploy-artifacts-2026-05-13. Matches. Pass.
Result: Allow.

Diamond trace (request: s3:PutObject on arn:aws:s3:::other-bucket/foo.zip):

  1. Identity Policy — allows.
  2. Session Policy — does not match (different bucket). Intersection is empty for this resource.
Result: ImplicitDeny.

Why the Simulator does not model this: Session policies are a runtime artifact, not a persistent policy. To validate, call aws sts assume-role --role-arn <role-arn> --role-session-name test --duration-seconds 900 --policy '<session-policy-json>', export the returned AccessKeyId, SecretAccessKey, and SessionToken as environment variables, and then attempt the action with those temporary credentials.

Reasoning shortcut: Session policy is an intersection on top of the role's effective permissions (after identity + boundary). It can never grant beyond what the role already has.

Teaching point: Session policies are the right tool for short-lived CI/CD blast-radius reduction. They are also the right tool for human "I'm about to do something risky, let me narrow my own permissions first" workflows.

Case 11 — RCP Blocks Public S3 Write NO

Scenario: An OU-level RCP denies s3:PutObject from any principal whose account is outside the Organization. An external account tries to write to a bucket in the OU. The bucket policy allows, but the RCP overrides at the resource side.

Policy JSON (RCP):

{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "DenyExternalWrite",
    "Effect": "Deny",
    "Principal": "*",
    "Action": "s3:PutObject",
    "Resource": "*",
    "Condition": {
      "StringNotEqualsIfExists": {
        "aws:PrincipalOrgID": "o-example12345"
      }
    }
  }]
}

Diamond trace:

  1. Explicit Deny — the RCP statement matches (the external principal's org ID is not o-example12345). Short-circuit to Deny at the RCP layer.
Result: ExplicitDeny.

Why the Simulator does not model this: RCPs are an Organizations-level resource-side perimeter. They are not represented in the offline Simulator. To validate, attempt the cross-org write to a sandbox account with the RCP attached and observe AccessDenied in CloudTrail.

Teaching point: RCPs are the right tool for "no S3 bucket in this OU can be written to by anyone outside our Org". You do not have to trust every bucket policy in every account; the RCP overrides at the resource side.

3.4 Tool Coverage Map

The following table summarizes which of the twelve cases above can be reproduced in the Simulator, and what the Simulator's badge will show.

* You can sort the table by clicking on the column name.
CaseTitleSimulator Reproduces?Simulator Badge
1Basic Allow via Identity PolicyYES fullAllow
2Explicit Deny Overrides AllowYES fullExplicitDeny
3MFA Condition Gate (BoolIfExists)YES fullExplicitDeny / Allow
4IP Restriction (IpAddress)YES fullImplicitDeny / Allow
5Cross-Account Resource Policy OnlyPARTIAL Sim returns Allow, real AWS deniesAllow (misleading)
6ABAC aws:ResourceTagYES fullAllow / ImplicitDeny
7NotAction MisuseYES fullAllow
8SCP Blocks Despite Identity AllowNO Simulator does not model SCP(cannot test)
9Permission Boundary Caps RoleNO Simulator does not model Boundary(cannot test)
10Session Policy Narrows AssumeRoleNO Simulator does not model Session Policy(cannot test)
11RCP Blocks Public S3 WriteNO Simulator does not model RCP(cannot test)
12Same-Account Resource Policy OnlyPARTIAL Sim correct for same-account onlyAllow

4. Cross-Account Evaluation Specifics

Cross-account access is the most context-dependent corner of IAM. The same Identity + Resource policy combination behaves differently depending on whether the caller and resource are in the same AWS account. Misreading this single dimension is the single largest source of confusion we see in real reviews.

StepSame-Account
(OR / union)
Either side allows = pass
Cross-Account
(AND / intersection)
Both sides must allow
Resource PolicyAllows(resource owner side)
MUST Allow
OperatorORAND
Identity PolicyAllows(caller side)
MUST Allow
ResultALLOWALLOW
(both required)

Fig. 2: Same-Account vs Cross-Account Evaluation — In the same account, either Resource Policy OR Identity Policy granting is sufficient (union). Across accounts, both sides must grant independently (intersection).

4.1 OR vs AND — The Switch

The rule is one line:

  • Same-account access: Identity Policy OR Resource Policy. Either is sufficient.
  • Cross-account access: Identity Policy AND Resource Policy. Both are required.
The Diamond does not change. The Resource Policy stage and Identity Policy stage are present in both cases. What changes is the combinator between them.

This rule is asymmetric in a non-obvious way. Same-account is permissive (union). Cross-account is restrictive (intersection). Engineers who learn IAM in a single-account environment internalize "Identity policy is the source of truth" and then are surprised when their cross-account setup silently denies.

4.2 The "Both Sides Must Allow" Rule

In cross-account access:

  • The resource owner's account must have a resource policy (bucket policy, KMS key policy, role trust policy, etc.) that explicitly grants access to the external principal. Use Principal: { "AWS": "arn:aws:iam::<external-account>:role/<role-name>" } or, for an entire account, Principal: { "AWS": "<external-account-id>" }.
  • The caller's account must have an identity policy attached to the calling principal that grants the action on the cross-account resource ARN.
If either side does not allow, the request fails with AccessDenied. The error message will hint at which side, but reading CloudTrail in both accounts is the reliable diagnostic path.

A common pitfall: the resource owner's bucket policy grants arn:aws:iam::AAA:user/alice, but Alice's identity policy in account AAA does not grant s3:GetObject on the cross-account ARN. Alice gets AccessDenied. The administrator of account BBB (the bucket owner) sees the bucket policy looks correct and is confused. The fix is in account AAA, not BBB.

4.3 SCP and RCP in Cross-Account

Both accounts' SCPs apply independently:

  • Caller's account SCP must allow the action (e.g., sts:AssumeRole, s3:GetObject on the cross-account ARN).
  • Resource's account SCP must allow the resource-side actions.
If either SCP denies, the request fails. Similarly, RCPs apply to the resource owner's account. An RCP on the resource side prevents external principals from accessing the resource, regardless of cross-account identity and resource policy configuration.

The practical implication: when designing cross-account access, you have four policies to coordinate:

  1. Caller's account SCP allows the action.
  2. Caller's principal's identity policy allows the action on the cross-account ARN.
  3. Resource owner's account RCP allows the principal (does not deny).
  4. Resource owner's resource policy grants the principal.
Get all four right, and the request passes. Get any one wrong, and it fails silently.

4.4 Trust Policy Design Checklist for Cross-Account Roles

For cross-account role assumption specifically (the AssumeRole case), the trust policy on the role acts as the resource policy. A practical checklist:

  • Principal scoped to a specific role or user, not an entire account, unless the external account is fully trusted (e.g., another account you own).
  • ExternalId is set when the caller is a third party (SaaS vendor). Not needed for first-party cross-account between your own accounts.
  • aws:PrincipalOrgID Condition restricts the trust to principals in your Organization.
  • MFA Condition (aws:MultiFactorAuthPresent) if a human will assume the role; sts:DurationSeconds with NumericLessThanEquals in the trust policy (or the role's MaxSessionDuration property) to bound session length.
  • Session tags are limited via aws:TagKeys Condition so the assumer cannot inject arbitrary tags.
  • Permission Boundary is attached to the role to bound the post-assumption identity.
The next figure shows the four nested perimeters that apply to cross-account requests: SCP wraps the caller, RCP wraps the resource, Permission Boundary narrows the principal, and Session Policy narrows the assumed session.

Fig. 3: Policy Type Containers — nested perimeters across caller and resource accounts
Fig. 3: Policy Type Containers — nested perimeters across caller and resource accounts

5. Common Pitfalls

The following eight pitfalls all stem from misreading the evaluation order itself, not from individual policy syntax errors. For a separate catalog of statement-level syntax mistakes, see the IAM Anti-Patterns - Real-World Mistakes and Their Root Causes article on this site.

5.1 "Resource-Side Deny Can Be Overridden by Identity-Side Allow"

Explicit Deny is absolute, regardless of which policy type contains it. A "Effect": "Deny" in a bucket policy is just as final as one in an identity policy. Engineers who internalize "identity policy is what I control, so it wins" run into this pitfall when a bucket owner adds a Deny to the bucket policy and "their" Allow stops working. The Deny is final.

5.2 "Without SCP, Permission Boundary is the Account-Level Ceiling"

A Permission Boundary applies to one principal. It is not a substitute for an SCP. An account without SCPs has no account-level ceiling; every principal's effective permissions are bounded only by their own identity policy (and their own boundary, if attached). If you want "no one in this account can disable CloudTrail", you need an SCP, not a Permission Boundary on every role.

5.3 "Permission Boundary Constrains Resource Policy Too"

Permission Boundaries intersect with identity policies only. A resource policy on a bucket can still grant access regardless of the boundary on the calling principal — for same-account access via union semantics. If you want to constrain what bucket policies can grant about your principal, you need a Service Control Policy or RCP, not a Permission Boundary.

5.4 "Session Policy Can Add Permissions to a Role"

Session policies intersect with the role's effective permissions. They cannot add anything. If you pass a session policy that allows s3:PutObject and the role's identity policy does not include s3:PutObject, the session still cannot do PutObject. Session policies are narrowing tools, not granting tools.

5.5 "Same-Account Resource Allow Cannot Be Stopped by SCP or RCP"

Same-account Resource Policy Allow is sufficient under union semantics, but Explicit Deny anywhere still wins. An SCP Deny on the caller's account will block the request even if the same-account bucket policy allows. An RCP Deny on the resource's account will block similarly. (Note: a Permission Boundary on the calling principal does not apply to a resource-policy-only allow path — see §5.3 — so SCP and RCP are the right tools for capping resource-policy access in same-account.) Explicit Deny is universal.

5.6 "Cross-Account Resource Policy Alone Is Enough"

Cross-account requires both sides. A cross-account bucket policy alone, without a matching identity policy in the caller's account, produces AccessDenied. The bucket owner's administrator will see the policy looks correct and be confused. The fix is in the caller's account.

5.7 "Explicit Deny Is Confined to the Layer It Appears In"

Explicit Deny short-circuits the entire evaluation, not just the current stage. A Deny in a Resource policy can stop a request that an Identity policy has already allowed. A Deny in an SCP at the OU level stops every principal in every account in the OU. There is no scoping of Deny to "just this layer".

5.8 "RCP Applies to Identities, Like SCP Does"

RCPs apply to the resource side. They constrain what can be done to resources in the affected accounts, regardless of where the caller is. SCPs constrain what callers in the account can do. The two are complementary perimeters: SCP guards outbound permissions; RCP guards inbound resource access. They are not interchangeable, and a deployment that uses only SCPs cannot fully replicate the resource-side guarantees an RCP provides.

6. Tool: IAM Policy Simulator (Offline) — Companion to This Article

The IAM Policy Simulator (Offline) on this site evaluates Identity Policies, Resource Policies, and Condition keys entirely client-side, with no AWS account, no network call, and no IAM permissions. It is the right tool for:

  • Quickly checking a policy you are about to commit to a PR.
  • Walking through Cases 1–4, 6, and 7 of this article hands-on.
  • Validating Condition logic (Bool, BoolIfExists, StringEquals, IpAddress, ArnLike, ForAllValues, ForAnyValue, etc.) on a sample request.
  • Teaching IAM evaluation to engineers who do not yet have an AWS sandbox.

6.1 What the Simulator Evaluates

The Simulator's documented scope:

  • Identity-based Policies — multiple policies can be loaded; they are unioned.
  • Resource-based Policies — multiple policies; matched by Principal ARN.
  • Conditions — over 20 operators including StringEquals, StringNotEquals, StringLike, Bool, BoolIfExists, IpAddress, NotIpAddress, DateGreaterThan, NumericLessThan, ArnEquals, ArnLike, plus ForAllValues: and ForAnyValue: modifiers, plus IfExists suffix.
  • Request context — Action, Resource ARN, Principal ARN, and arbitrary Context Keys (e.g., aws:SourceIp, aws:MultiFactorAuthPresent, aws:ResourceTag/Team).
The output:

  • A result badge: Allow, ExplicitDeny, or ImplicitDeny.
  • The list of matching statements (with Allow / Deny pills).
  • A full evaluation trace (expandable <details>) showing every statement and per-element match/skip with reasons.
  • A Copy Result button that serializes the decision, summary, and trace to the clipboard for sharing in a code review comment.

6.2 Reproducing Case 3 (MFA Condition Gate) Step-by-Step

As a concrete walkthrough, here is how to reproduce Case 3 in the Simulator:

  1. Open the Simulator at hidekazu-konishi.com/tools/iam_policy_simulator_tool.html.
  2. Paste the identity policy from Case 3 into the Identity-based Policies panel.
  3. Set the request fields:
    • Action: ec2:TerminateInstances
    • Resource ARN: *
    • Principal ARN: arn:aws:iam::111122223333:role/Operator
  4. Add a Context Key: key = aws:MultiFactorAuthPresent, value = false.
  5. Click Evaluate. The badge shows ExplicitDeny. Expand the trace to see DenyTerminateWithoutMFA listed as the matching deny.
  6. Change the context: edit the Context Key value to true. Click Evaluate again. The badge changes to Allow, and the matching statement shifts to AllowTerminateWithMFA.
This single round-trip demonstrates the BoolIfExists semantics in concrete terms, in under thirty seconds.

6.3 Simulator Limitations You Must Know

The Simulator does not model:

  • Service Control Policies (SCPs) — there is no SCP input slot. To test SCP behavior, use the AWS-hosted Policy Simulator or a sandbox account with the SCP attached.
  • Resource Control Policies (RCPs) — same as SCPs.
  • Permission Boundaries — there is no Boundary input slot. Use the AWS CLI aws iam simulate-principal-policy --policy-source-arn <role-arn> to test against a real role's boundary.
  • Session Policies — there is no Session Policy input slot. Session policies are a runtime artifact; test by actually assuming the role with the session policy attached.
  • Cross-account both-sides-required logic — the Simulator always uses union semantics, which is correct for same-account but incorrect for cross-account.
If your decision depends on any of these five limitations, do not rely on the offline Simulator's result alone. Use it as a fast sanity-check for the Identity + Resource + Condition layers, and validate the remaining layers in a real AWS environment.

For static analysis of overly permissive identity policies (without needing a request), use the companion IAM Policy Least Privilege Analyzer on this site. It is the right tool for spotting Action: * + Resource: *, dangerous iam:PassRole, conditionless wildcard statements, and other linter-style findings before they reach review.

7. Frequently Asked Questions

7.1 What is the difference between Implicit Deny and Explicit Deny?

Implicit Deny is the default outcome when no policy statement matches the request — there is simply no Allow. Explicit Deny is an active "Effect": "Deny" statement that matches the request. Both produce AccessDenied in the API response, but they have different causes: Implicit Deny means nothing granted the request; Explicit Deny means something actively blocked it. The distinction matters when debugging: Implicit Deny points to a missing grant, while Explicit Deny points to an active blocking statement, typically in a Permission Boundary, SCP, RCP, or a Deny in an Identity or Resource policy.

7.2 Does a Permission Boundary grant any permissions by itself?

No. A Permission Boundary defines the maximum allowed permission envelope, but it grants nothing on its own. The effective permissions are the intersection of the Boundary and the identity-based policies attached to the principal. If the Boundary contains s3:* but no identity policy grants s3:PutObject, the action is still denied. You need both the Boundary (ceiling) and the identity policy (grant). The Boundary's role is to ensure that any future broadening of the identity policy is still capped by the Boundary.

7.3 In the same AWS account, can a resource policy alone allow access without an identity policy?

Yes, with conditions. For same-account access, if a resource policy explicitly grants access to a principal in the same account and no Explicit Deny exists anywhere in the chain, the request is allowed — even without a matching identity policy. This is the union semantics for same-account access. Cross-account access is different: both the resource policy (on the resource side) and an identity policy (on the caller side) must grant access. See §4.

7.4 Can an SCP be used to grant permissions?

No. SCPs define the maximum permissions available within an account or OU, but they do not grant permissions. An SCP with "Effect": "Allow" for s3:* does not allow any principal to access S3 unless that principal also has an identity policy or resource policy granting the action. Think of SCPs as a filter that can only remove permissions, not add them. The default FullAWSAccess SCP must remain attached at the root unless you understand exactly which actions will become impossible; removing it makes every action implicitly denied at the SCP layer.

7.5 What happens if a Session Policy grants an action the role's identity policy does not have?

Nothing — the action remains denied. Session policies are intersected with the role's identity-based policies and Permission Boundary. If the identity policy does not include ec2:DescribeInstances, a Session Policy that grants ec2:DescribeInstances provides no additional access. Session policies can only narrow the role's existing permissions; they cannot expand them.

7.6 How does RCP differ from SCP?

SCPs are applied to the caller (principal) side and restrict what IAM identities within an OU or account can do. RCPs are applied to the resource side and restrict what can be done to resources within an OU or account, regardless of where the caller is. An RCP on a production OU prevents any external principal from writing to S3 buckets in that OU, even if the caller's account has no SCP restrictions. They are complementary: SCP guards outbound caller permissions; RCP guards inbound resource access.

7.7 Does the IAM Policy Simulator on this site model SCPs and Permission Boundaries?

No. The offline Simulator evaluates Identity Policies, Resource-based Policies, and Context Key Conditions. It does not model SCPs, RCPs, Permission Boundaries, or Session Policies. A result of Allow from the offline Simulator means the identity and resource policy logic would allow the action — but in a real AWS environment, an SCP, RCP, Permission Boundary, or Session Policy could still deny it. To validate those layers, use the AWS-hosted Policy Simulator (account-aware) or a sandbox account.

7.8 Why does aws:MultiFactorAuthPresent evaluate differently with Bool vs BoolIfExists?

Bool requires the context key to be present. If aws:MultiFactorAuthPresent is absent from the session context (for example, when using long-term access keys), Bool treats the condition as unmatched — which for a Deny statement means the Deny does not fire, leaving an open door. The IfExists suffix changes the missing-key behavior: BoolIfExists returns true when the key is absent. So with "BoolIfExists": {"aws:MultiFactorAuthPresent": "false"} on a Deny statement, a session that has no MFA context evaluates the condition as true and the Deny fires — closing the open door. AWS recommends this exact form in Deny statements so that long-term-key sessions are blocked along with non-MFA console sessions.

7.9 In cross-account role assumption, which account's SCP applies?

Both accounts' SCPs apply independently. The caller's account SCP must allow sts:AssumeRole. The target account's SCP must allow the actions the assumed role will perform. If either SCP blocks the relevant action, the request fails — even if the trust policy and identity policies are correctly configured. RCPs on the resource account also apply independently to any resource operations performed after the role assumption.

7.10 If an identity policy says Allow and a resource policy says Deny, which wins?

The resource policy's Explicit Deny wins, always. Explicit Deny from any policy type overrides Allow from any other policy type, regardless of evaluation order. This is the one absolute rule in IAM evaluation. Think of Explicit Deny as a global short-circuit that fires the moment it is encountered.

8. Summary

The IAM Decision Diamond is one diagram and one rule:

  • The diagram: seven evaluation stages — Explicit Deny, SCP, RCP, Resource Policy, Identity Policy, Permission Boundary, Session Policy — every request passes through every applicable stage.
  • The rule: Explicit Deny short-circuits everywhere; ceiling stages (SCP / RCP / Boundary / Session) intersect; granting stages (Resource / Identity) union for same-account and intersect for cross-account.
The twelve walk-through cases show the Diamond in concrete terms. Six of them are fully reproducible in the offline Simulator on this site; two are partially reproducible (and the gap is informative); four require reasoning beyond what an offline tool can do.

The same-account-vs-cross-account split is the single largest source of confusion. Memorize one line: same-account = OR, cross-account = AND.

For statement-level syntax mistakes (separate from evaluation order misreadings), see the IAM Anti-Patterns - Real-World Mistakes and Their Root Causes catalog. For the timeline of when each of the seven stages was introduced into IAM, see AWS History and Timeline regarding AWS IAM. For 60 IAM term definitions cross-referenced with this article's sections, see the AWS IAM Glossary. For the practical setup of Identity Center, including the Session Policy and ABAC patterns Case 6 and Case 10 hint at, see the AWS IAM Identity Center Complete Setup Guide.

9. References

AWS Official Documentation

Related Articles on This Site

Companion Tools on This Site

  • IAM Policy Simulator (Offline) — client-side evaluator for Identity Policies + Resource Policies + Conditions. Companion tool for Cases 1–4, 6, 7, and 12.
  • IAM Policy Least Privilege Analyzer — static linter that detects Action: *, Resource: *, dangerous iam:PassRole, conditionless wildcard statements, and similar over-permissive patterns.

References:
Tech Blog with curated related content

Written by Hidekazu Konishi