SCS-C03 · Security

How AWS decides a request: why an explicit deny always wins

Every request starts denied. An Allow lets it through, and one Deny in any policy ends it. The order AWS reads policies in, and how to find the Deny.

5 min readVideo comingChecked against the AWS documentation on Ends at Deny Wins, free

A role allows s3:GetObject on a whole bucket, and every request it makes for a file comes back AccessDenied. Someone suggests attaching a second policy with the same Allow. Someone else suggests a managed policy with full S3 access. Neither will change anything, and the reason is the first rule of AWS policy evaluation: an explicit deny overrides an explicit allow, wherever the deny is written.

Three rules

The IAM User Guide sums up the evaluation logic in three lines:

  • “By default, all requests are implicitly denied with the exception of the AWS account root user, which has full access.”
  • Requests must be explicitly allowed by a policy or set of policies to be allowed.
  • “An explicit deny overrides an explicit allow.”

So there are two kinds of no. An implicit deny happens when “there is no applicable Deny statement but also no applicable Allow statement”. You fix it by adding an Allow in the right place. An explicit deny happens when an applicable policy includes a Deny statement, and then “the Deny statement trumps the Allow statement”. You fix it only by changing the Deny, or by changing the request so the Deny no longer matches it. Adding Allows does nothing.

The order AWS reads policies in

The guide describes the steps the enforcement code takes for each request. Any step can end the evaluation:

  1. Deny evaluation. AWS looks through every policy that applies: “AWS Organizations SCPs and RCPs, resource-based policies, identity-based policies, IAM permissions boundaries, and session policies”. In the guide’s words, “If the enforcement code finds even one explicit deny that applies, the enforcement code returns a final decision of Deny.”
  2. Resource control policies (RCPs). They apply to resources in the accounts they are attached to. If no RCP statement allows the request, it is denied. When RCPs are enabled, the AWS managed policy RCPFullAWSAccess is attached everywhere and can’t be detached, so there is always an Allow to find here.
  3. Service control policies (SCPs). They apply to principals in the accounts they are attached to, with the same rule: no applicable Allow in the SCPs, final Deny. RCPs cover only the services AWS lists as supporting them, and neither RCPs nor SCPs restrict the management account or service-linked roles.
  4. Resource-based policies, such as a bucket policy or a secret’s policy. Within one account, an Allow here can be enough on its own, depending on the principal it names. How that works, and why it changes across accounts, is a subject of its own.
  5. Identity-based policies attached to the user or role. With no statement that allows the action, the request is implicitly denied, unless a same-account resource-based policy already granted the access directly to that user or session (step 4).
  6. Permissions boundaries. If the principal has one and it does not allow the action, the request is implicitly denied.
  7. Session policies. For a role session or federated user session that carries a session policy, that policy must allow the action too.

Two consequences follow. First, SCPs, RCPs and permissions boundaries set limits; they do not add access. For a resource with no resource-based policy, the guide describes the result as the intersection of the user’s policies, the SCPs and the RCP: “an action must be allowed by all three policy types”. Second, the deny step comes first and reads every policy type. A Deny in a bucket policy stops a request before anyone looks at the role’s Allow.

Where the Deny hides

Most “but I have an Allow” puzzles end in a policy nobody opened. People check the role’s identity policy. The Deny sits in the bucket policy, an SCP or RCP, a permissions boundary or a VPC endpoint policy.

A common source is a lockdown statement. AWS documents a pattern to use instead of NotPrincipal: a Deny with "Principal": "*" and an ArnNotEquals condition on aws:PrincipalArn, which denies every principal except the one named in the condition. It does exactly what it says. If whoever wrote it named only the application’s role, every other role in the account is now denied on those objects, however many Allows it holds.

The error message helps you find it. Most access denied messages take the form User user is not authorized to perform action on resource because context. For an explicit deny, the context contains with an explicit deny in a type policy, such as with an explicit deny in a resource-based policy. For an implicit deny it contains because no type policy allows the action action. So the message tells you whether to look for a Deny or for a missing Allow, and in which policy type. Two caveats from the guide: when several policy types deny a request, the message names only one of them, and some services do not use this format.

What the exam guides ask

The SCS-C03 guide asks you to “Design, interpret, and implement IAM policies by following the principle of least privilege (for example, permission boundaries, session policies)” (Skill 4.2.3) and to “Analyze authorization failures to determine causes or effects (for example, IAM Policy Simulator, IAM Access Analyzer)” (Skill 4.2.4). The SAA-C03 guide covers it under “Design secure access to AWS resources” (Task 1.1), and the SOA-C03 guide under “Implement IAM features (for example, password policies, multi-factor authentication [MFA], roles, federated identity, resource policies, policy conditions)” (Skill 4.1.1).

A scenario on this topic usually gives you a principal with an Allow that is still refused, and somewhere else a policy with a Deny that matches the request. The wrong answers are built from more permission:

  • Attach a second, broader identity policy. More Allow, same Deny.
  • Attach a managed policy with full access to the service. Still only an Allow.
  • Change an unrelated control, such as Block Public Access or an ACL. Neither removes a Deny.

The answer that works changes the Deny itself: narrow it, add the principal to its exception, or remove it if nothing needs it. When a question mentions an SCP or a permissions boundary, check those for a missing Allow as well, because either can refuse a request that every permissions policy allows.

When you troubleshoot for real, walk the same order: read the error message, search every policy type for a Deny that matches the action and resource, then check that each limiting policy allows the action and that some permissions policy grants it.

Play it

The free stage below puts you in charge of Larkspur Mutual’s claims bucket. The adjusters’ role allows s3:GetObject on the whole bucket, and every claim file refuses them. A contractor locked the bucket down last week “so only the claims application can touch claim files”. In the Policy Stepper you run each request through the evaluation order, and the Access matrix shows which gate decided and the error AWS would return. The application must keep writing, audit needs reports it cannot reach yet, a nightly job is refused a secret, and the brochure must stay private. Find the statement that decides, and the debrief explains why it won before any Allow was read.

Video comingA short walk-through of this stage in the game is being recorded. Until it is here, the stage itself is one press away.