Course outline

2.2

How AWS Decides: Explicit Deny, Implicit Deny, Allow

Evaluate any request by hand, including a user in several groups with conflicting policies.

18 min Reading + animations

Tip: tap or hover any dotted-underlined term to see what it means.

By the end of this lesson you will be able to

  • Explain the three possible results of evaluating a request: explicit deny, implicit deny and allow, and why an explicit deny always wins.
  • Work out by hand what a user in several groups with conflicting policies can and cannot do.
  • Decide when a policy statement “matches” a request: action, resource, condition and principal.
  • List the policy types AWS reads for a request in one account, and say which ones grant permissions and which ones only limit them.
  • Follow the full evaluation flow (Deny, RCP, SCP, resource-based, identity-based, boundary, session) and know when the result is a union and when it is an intersection.
  • Write Deny guard rails with conditions and NotAction, such as a Region guard, without locking out global services.
  • Find out why a request was denied, using the error message, an encoded authorization message, CloudTrail and the policy simulator.

#Every request gets a verdict

In lesson 2.1 you wrote policies: JSON documents that say which actions a principal may perform on which resources. A policy on its own does nothing. It matters at the moment someone sends a request: “Alice wants to run s3:GetObject on arn:aws:s3:::reports/q3.csv”.

At that moment AWS has to answer one question: allow or deny? It does this for every single API call, whether it comes from the console, the CLI, an SDK or another AWS service, millions of times a second. The part of AWS that answers is often called the policy engine (AWS’s documentation calls it the enforcement code). It works in three stages:

  1. Authenticate: check who signed the request (a user, a role session, the root user). A few services, such as S3 with a public bucket, also accept anonymous requests.
  2. Build the request context: gather everything the policies might ask about. The principal (who), the action (what), the resource (on what), and the environment: source IP address, time, Region, whether MFA was used, tags on the principal and the resource, whether the call came over TLS.
  3. Evaluate: read every policy that applies to this context and reach exactly one of three results.
ResultWhat it meansHow to change it
Explicit denyA Deny statement matches the request.Only by changing or removing that Deny. No Allow anywhere can override it.
Implicit denyNo statement denies it, but no statement allows it either. This is the default.Add an Allow in the right place.
AllowAt least one statement allows it, nothing denies it, and every limit (SCP, RCP, boundary, session policy) lets it through.—

There are two ways to be refused, and they need opposite fixes. Keep that in mind for the whole lesson.

#The three rules

Everything in this lesson comes back to three rules. Learn them first.

#Rule 1: by default, everything is denied

A brand-new IAM user or role can do nothing. Not list buckets, not view the console dashboard, not even change their own password, unless a policy allows it. Every request starts as “denied” and needs an Allow to change.

This is the implicit deny. “Implicit” means nobody wrote it: it is simply what happens when no policy says yes. A policy that only talks about EC2 says nothing about S3, so for S3 it is silent, and silence means no.

#Rule 2: an explicit Allow lifts the default

A statement with "Effect": "Allow" that matches the request turns the default deny into an allow. It does not matter where the Allow comes from (the user’s own inline policy, a managed policy, any of the user’s groups, or a resource-based policy on the resource), as long as nothing denies it and no limit blocks it.

#Rule 3: an explicit Deny beats every Allow

A statement with "Effect": "Deny" that matches the request ends the evaluation with Deny. It does not matter how many Allows exist, how specific they are, or where they come from.

There is no priority between policies. A Deny for s3:* beats an Allow for exactly s3:GetObject on exactly this object. People often expect “the more specific rule wins” from firewalls or CSS. In AWS IAM it does not: a matching Deny always wins.

#A user in several groups

This is the example from the course notes, and the exam’s favourite form of this topic.

A user can belong to several groups (up to 10). Groups cannot contain other groups. A user’s identity-based permissions are every policy attached to the user plus every policy attached to every group the user is in, all put into one pile. AWS does not rank groups: there is no group with higher privilege, no first or last group, no “most recently added wins”. The pile is evaluated with the same three rules.

Alice is in Group A and Group B.

Group A saysGroup B saysAlice can read S3?Why
Allow S3 read and writeDeny S3NoExplicit deny in Group B beats Group A’s Allow.
Allow S3 read and writenothing about S3YesGroup B is only an implicit deny (silence). Group A’s Allow is enough.
nothing about S3nothing about S3NoNobody allows it: implicit deny.
Allow S3 read and writeDeny only s3:DeleteObjectRead: yes. Delete: noA Deny only blocks what it matches.

The last row is a deliberate, common design: give a team broad rights in one group and put the “never do this” rules (delete backups, disable logging, leave the organization) in a Deny that everyone shares.

One user in two groups

Alice is in Group A and Group B. Watch how AWS combines both groups’ policies for one request, and why the order of the groups never matters.

Scenario

The notes’ example: one group allows, the other explicitly denies.

AliceIAM user
Policy engineAWS
Group AS3 read and write
Group BvariesDeny s3:*
S3 bucketreports
read reports/q3.csv
Step 1 of 5
1

Alice signs a request to read an object. Before S3 does anything, AWS has to decide whether Alice may do this.

Self-checkQuestion

Bob is in three groups. Developers allows ec2:*. Testers allows ec2:Describe*. Contractors denies ec2:TerminateInstances. Can Bob stop an instance? Can he terminate one?

#When does a statement match?

“A Deny that matches” and “an Allow that matches” hide a precise rule. A statement applies to a request only if all of these fit:

ElementMatches when…Example
Action (or NotAction)the request’s action is in the list (wildcards allowed), or for NotAction, is not in the lists3:Get* matches s3:GetObject and s3:GetBucketPolicy
Resource (or NotResource)the resource ARN matches one of the patternsarn:aws:s3:::reports/* matches objects in reports, but not the bucket itself
Condition (optional)every condition in the block is true for the request context"aws:SourceIp": "203.0.113.0/24" matches only calls from that range
Principal (resource-based policies only)the caller is one of the named principals"AWS": "arn:aws:iam::111122223333:user/alice"

If any element does not fit, the statement simply does not apply: it is as if it were not there. That has an important consequence:

  • An Allow whose condition is false gives nothing. Result for that statement: no allow.
  • A Deny whose condition is false blocks nothing. Result for that statement: no deny.

A condition never turns an Allow into a Deny. If you want “deny unless X”, you must write a Deny with the opposite condition (“Deny when not X”), as the Region guard below does.

Two matching mistakes cause most real access denied errors:

  • Bucket versus objects. s3:ListBucket acts on the bucket ARN (arn:aws:s3:::reports); s3:GetObject acts on object ARNs (arn:aws:s3:::reports/*). A policy with only reports/* lets you read files you already know the names of, but you cannot list them.
  • The action you think versus the action the tool calls. The console and many tools call several actions for one click. A “view bucket” page may call ListBucket, GetBucketLocation and GetBucketPolicy. A missing Allow for any of them shows an error.

#Where policies live

A request in one account can meet up to six kinds of policy. They split into two families: those that grant permissions, and those that only limit them.

Policy typeAttached toGrants or limits?Taught in
Identity-based policy (managed or inline)a user, a group, or a rolegrantslesson 2.1
Resource-based policy (bucket policy, KMS key policy, SQS queue policy, Lambda function policy, role trust policy…)a resource; names a Principalgrantshere and later chapters
Permissions boundarya user or a role (not a group)limits (maximum)here, briefly; chapter 20
Session policypassed when assuming a role or creating a federated sessionlimits (for that session)here, briefly; lesson 2.4
SCP (service control policy)the organization root, an OU or an accountlimits principals in those accountschapter 20
RCP (resource control policy)the organization root, an OU or an accountlimits what anyone can do to resources in those accountschapter 20

The limiting kinds never give anything. A permissions boundary that allows s3:* does not let a user touch S3; it only means S3 is not taken away. An SCP that allows everything does not make anyone an administrator.

Older access control lists (S3 ACLs, VPC network ACLs) also exist, but S3 ACLs are disabled by default on new buckets and they do not change the logic here.

Which policies does a request meet?

Policies sit in different places: on the identity, on the resource, around the account. Pick a request and see which of them AWS reads.

Scenario

AWS OrganizationsSCPs and RCPs
Account 111122223333
SCPlimits principals
RCPlimits resources
AliceIAM user
Her groupsidentity-based
Boundaryceiling
App rolerole session
Policy engine
Bucketbucket policy
EC2no resource policy
s3:GetObject
Step 1 of 5
1

Alice calls s3:GetObject on a bucket in her own account.

#The full evaluation flow in one account

When a principal and a resource are in the same account, AWS evaluates the request in this order (from the IAM User Guide’s “Policy evaluation logic”, checked October 2026):

  1. 1. Look for any explicit Deny

    AWS reads every applicable policy of every type: identity-based, resource-based, permissions boundary, session policy, SCPs and RCPs. If any Deny statement matches, the final decision is Deny. Nothing later can change it.

  2. 2. RCPs

    If the account is in an organization that uses RCPs, they must allow the action on this resource. In practice an RCP called RCPFullAWSAccess is always attached and cannot be detached, so RCPs only ever stop a request through an explicit Deny, which step 1 already caught.

  3. 3. SCPs

    If the account is in an organization, the SCPs that apply to it must allow the action. If no SCP allows it, the decision is Deny (an implicit deny from the SCP). SCPs do not apply to the management account.

  4. 4. Resource-based policies

    If the resource has a resource-based policy that allows this principal, that Allow can be enough. For an IAM user named directly, it is: the decision is Allow even if the user’s own policies and boundary say nothing. For an IAM role named by its ARN, the Allow still has to get through the role’s permissions boundary and session policy (steps 6 and 7). If there is no such Allow, continue.

  5. 5. Identity-based policies

    The policies of the user (including all their groups) or the role must allow the action. If none does, and step 4 gave nothing, the decision is Deny (implicit).

  6. 6. Permissions boundary

    If the user or role has a permissions boundary, it must allow the action too. If it does not, the decision is Deny.

  7. 7. Session policy

    If the principal is a role session or a federated user session that was given a session policy, the session policy must allow the action too. If it does not, Deny. If there is no session policy, or the principal is not a session at all (an IAM user), the decision is Allow.

You do not need to memorise the exact order for the Associate exam. You do need these two facts that follow from it: an explicit Deny anywhere wins, and an Allow must survive every limit that applies.

#Union and intersection

Most questions come down to whether two policy types are added or overlapped:

Combination (same account)ResultIn words
Identity-based + resource-basedunionAn Allow in either one is enough.
User’s own policies + all group policiesunionThey are all identity-based policies of one user.
Identity-based ∩ permissions boundaryintersectionOnly what both allow.
Identity-based ∩ SCP (∩ RCP)intersectionOnly what all of them allow.
Role’s policies ∩ session policyintersectionThe session gets the overlap.
Any of the above with a matching DenydeniedExplicit deny beats everything.

For a request across accounts (Alice in account A reads a bucket in account B), the rule is different: both sides must allow it, account A’s identity policy and account B’s resource policy. A single Allow is no longer enough. Lesson 2.4 and chapter 20 cover this.

#Permissions boundaries in one paragraph

A permissions boundary is a managed policy set on a user or role as its ceiling. A typical use: you let developers create roles for their Lambda functions, but force every role they create to carry a boundary that excludes IAM and billing. However generous a policy they attach, the role can never do more than the boundary allows, so nobody can make themselves an administrator. Boundaries can be set on users and roles, not on groups. Chapter 20 covers them in depth.

Evaluate a request yourself

Someone asks to read an object from an S3 bucket in the same account. Pick a worked example, or set every policy yourself, and follow the request through the gates from left to right.

Worked examples
Who is asking
Identity-based (user, its groups, or the role)
Bucket policy (names this principal)
Permissions boundary
SCP from the organization
RCP from the organization
  1. 1. Any Deny?

    a Deny matches

  2. 2. RCP

    not reached

  3. 3. SCP

    not reached

  4. 4. Resource-based

    not reached

  5. 5. Identity-based

    not reached

  6. 6. Boundary

    not reached

  7. 7. Session

    not reached

Decision: Denied: explicit deny

i

Alice is in Group A (read and write S3) and Group B (deny S3). Both groups’ policies are her identity-based policies, so the set contains an Allow and a Deny. The Deny wins at the very first gate. A Deny statement in an identity-based policy matches the request. AWS looks for Deny in every policy type before anything else, and one match ends the evaluation. No Allow anywhere can undo it.

#Working through requests by hand

For any request, ask four questions in this order:

  1. Does any Deny match? Check every policy, including conditions and NotAction. If yes, stop: denied.
  2. Does every limit allow it? SCPs (if in an organization and not the management account), permissions boundary (if set), session policy (if it is a session with one). If one does not, stop: denied.
  3. Does at least one grant allow it? An identity-based policy, or (in the same account) a resource-based policy naming this principal. If none, denied (implicit).
  4. Otherwise: allowed.

#Worked example 1: the Deny with a condition

{
  "Version": "2012-10-17",
  "Statement": [
    { "Effect": "Allow", "Action": "s3:*", "Resource": "*" },
    {
      "Effect": "Deny",
      "Action": "s3:*",
      "Resource": "*",
      "Condition": { "NotIpAddress": { "aws:SourceIp": "203.0.113.0/24" } }
    }
  ]
}
  • Request from the office, 203.0.113.25: the Deny’s condition (“source IP not in 203.0.113.0/24”) is false, so the Deny does not apply. The Allow matches: allowed.
  • Request from home, 198.51.100.7: the condition is true, the Deny matches: denied, even though the Allow is still there.
  • Request made by an AWS service on Alice’s behalf (for example a stack she launched creating a bucket): the source IP is the service’s, not the office, so this Deny blocks it too. AWS’s own examples add "Bool": {"aws:ViaAWSService": "false"} to such a guard for this reason.

#Worked example 2: union across identity and resource

Carol has no S3 permissions. The bucket reports has this bucket policy, and Carol is in the same account:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "AWS": "arn:aws:iam::111122223333:user/carol" },
    "Action": "s3:GetObject",
    "Resource": "arn:aws:s3:::reports/*"
  }]
}
  • Carol reads reports/q3.csv: no Deny, the bucket policy names her: allowed. Same account, so one Allow is enough.
  • Carol lists the bucket: the bucket policy only allows GetObject on objects, her identity policy allows nothing: denied (implicit).
  • Carol reads from another bucket: no Allow anywhere: denied (implicit).

#Worked example 3: the boundary

Dave’s identity policy is AdministratorAccess (Allow * on *). His permissions boundary allows only ec2:*, cloudwatch:* and s3:*.

  • ec2:RunInstances: allowed by both: allowed.
  • iam:CreateUser: allowed by his policy, not by the boundary: denied.
  • s3:GetObject with a bucket policy that denies Dave: explicit Deny: denied.

#Worked example 4: the SCP

Erin is an administrator in a member account. Her OU has an SCP that allows everything except a Deny on ec2:* outside two Regions.

  • ec2:RunInstances in eu-west-1: no Deny matches, SCP allows, her policy allows: allowed.
  • The same in us-east-1: the SCP’s Deny matches: denied. Being an administrator does not help; the SCP is above the whole account.
  • The same request from a user in the management account: SCPs do not apply there: allowed.

#Worked example 5: a request that is denied twice

Frank has no policies at all, and Group B (his only group) denies s3:DeleteObject. Frank tries to delete an object. Is it an implicit or an explicit deny? Explicit: step 1 finds the matching Deny before anyone even looks for an Allow. The error message will say “explicit deny”, and adding an Allow will not help.

Self-checkQuestion

A user’s identity-based policy allows s3:* on all resources. Her permissions boundary allows only s3:GetObject. A bucket policy in the same account denies s3:PutObject to everyone. Which requests from her to that bucket succeed?

#Deny guard rails

Because an explicit Deny cannot be overridden, it is the right tool for rules nobody may break, whatever other permissions they get later. It is the wrong tool for ordinary access control: there, simply do not grant the permission. Too many Denies make policies hard to reason about.

Typical guard rails:

  • Require MFA for dangerous actions: Deny when aws:MultiFactorAuthPresent is false (lesson 2.3).
  • Office network only: Deny when aws:SourceIp is not your range (worked example 1).
  • Approved Regions only: Deny when aws:RequestedRegion is not in your list (below).
  • Protect the protections: Deny cloudtrail:StopLogging, cloudtrail:DeleteTrail, and deleting your backup vaults, for everyone.
  • Tag-based rules: Deny stopping instances unless the caller’s team tag matches the instance’s team tag.

#A Region guard with NotAction

You want nobody to create anything outside Ireland (eu-west-1) and Frankfurt (eu-central-1), for data residency and to keep costs under control. The obvious way does not work:

{
  "Effect": "Deny",
  "Action": "*",
  "Resource": "*",
  "Condition": { "StringNotEquals": { "aws:RequestedRegion": ["eu-west-1", "eu-central-1"] } }
}

Global services such as IAM, STS (global endpoint), Organizations, Route 53, CloudFront and Support handle their requests in us-east-1. So with Action: "*" this guard denies every IAM call, and administrators can no longer manage users or roles.

The fix is NotAction: “deny every action except these”, with the global services listed as the exceptions.

{
  "Sid": "DenyOutsideApprovedRegions",
  "Effect": "Deny",
  "NotAction": [
    "iam:*", "sts:*", "organizations:*", "route53:*", "cloudfront:*",
    "support:*", "budgets:*", "waf:*", "globalaccelerator:*"
  ],
  "Resource": "*",
  "Condition": {
    "StringNotEquals": { "aws:RequestedRegion": ["eu-west-1", "eu-central-1"] }
  }
}

Read it as: “if the Region is not one of these two, deny everything that is not a global service”. This guard is most often written as an SCP so it covers whole accounts (chapter 20), but it works the same in an identity policy.

A Region guard: Deny with NotAction

A policy denies every action outside eu-west-1 and eu-central-1, except for global services. Watch three requests, then the same guard written without the exception.

Scenario

DeveloperCLI
Policy engine
AWS serviceendpoint
ec2:RunInstances eu-west-1
Step 1 of 3
1

The developer launches an instance in Ireland (eu-west-1). Their identity policy allows EC2.

#NotAction and NotResource with Allow: be careful

NotAction with Allow means “allow every action except these”, including every action AWS adds in the future. The AWS managed policy PowerUserAccess is built this way: it allows everything except IAM, Organizations and account management. That is fine when you mean it, and dangerous when you only meant to exclude one or two things from a narrow list.

NotResource works the same way for resources. A Deny with NotResource (“deny everything except this bucket”) is a common way to fence a principal into one resource.

#When access is denied: finding out why

When something fails with an access denied error, work through these steps.

  1. Read the whole error message

    Most services now say which kind of policy blocked the request and whether it was implicit or explicit. For example: “User: arn:aws:iam::111122223333:user/alice is not authorized to perform: s3:GetObject on resource: … because no identity-based policy allows the s3:GetObject action”, or “… with an explicit deny in a service control policy”. Implicit means: add an Allow in the right place. Explicit means: find the Deny.

  2. Decode an encoded authorization message

    EC2 and some other services return an UnauthorizedOperation error with an encoded message instead of details, because details could leak information. Someone with permission for sts:DecodeAuthorizationMessage can run aws sts decode-authorization-message --encoded-message <blob>. The JSON shows the action, the resource, the full request context and whether an explicit Deny matched.

  3. Look in CloudTrail

    CloudTrail records API calls with the caller, the source IP, the action and the errorCode (AccessDenied, UnauthorizedOperation). It shows exactly what was tried, which is often not what the user thinks. Remember that management events are recorded by default, while S3 object calls and other data events appear only if a trail logs them.

  4. Test with the policy simulator

    The IAM policy simulator (in the console, or aws iam simulate-principal-policy) evaluates a user’s or role’s policies against an action and resource you describe, and shows the matching statement. Use it to test a new guard before attaching it, and to prove a fix.

  5. Wait a few seconds after a change

    IAM is eventually consistent. A policy you just changed can take a few seconds to take effect everywhere. Build retries into scripts that create a role and use it immediately.

Finding out why access was denied

Four tools answer “why was I denied?”: the error message itself, an encoded message you decode, CloudTrail, and the policy simulator.

Scenario

YouCLI
ServiceEC2 or S3
STS
CloudTrail
Policy simulator
s3:GetObject
Step 1 of 3
1

You try to download a file and it fails.

IAM Access Analyzer adds two more checks you will meet later: policy validation (it warns about errors and risky patterns, such as an Allow with NotAction, while you write a policy) and unused access findings.

#How it is used in system design and on the exam

  • Least privilege first, Deny second. Grant small, specific permissions. Add a shared Deny only for the few rules that must hold for everyone.
  • Separation of duties. A shared “never” Deny (delete backups, stop logging, leave the organization) in a group or an SCP protects you even if someone gets AdministratorAccess by mistake.
  • Delegation with boundaries. Let teams create roles, but force a permissions boundary on each so nobody can escalate.
  • Organization-wide guard rails. SCPs for Regions and forbidden services, RCPs for “no access to our data from outside the organization”.

#Try it yourself

With the CLI configured (lesson 1.3), try these. They change nothing.

# Will alice be allowed to read one object, and which statement decides?
aws iam simulate-principal-policy \
  --policy-source-arn arn:aws:iam::111122223333:user/alice \
  --action-names s3:GetObject s3:DeleteObject \
  --resource-arns arn:aws:s3:::reports/q3.csv \
  --query 'EvaluationResults[].[EvalActionName,EvalDecision]' --output table
# EvalDecision is allowed, implicitDeny or explicitDeny

# Test a draft policy and a context key without attaching anything
aws iam simulate-custom-policy \
  --policy-input-list file://region-guard.json \
  --action-names ec2:RunInstances \
  --context-entries ContextKeyName=aws:RequestedRegion,ContextKeyValues=us-east-1,ContextKeyType=string

# Decode an EC2 UnauthorizedOperation message
aws sts decode-authorization-message --encoded-message "<paste the blob>" \
  --query DecodedMessage --output text | python -m json.tool

# Recent denied management calls by one user (CloudTrail event history, last 90 days)
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=Username,AttributeValue=alice --max-results 50 \
  --query 'Events[].CloudTrailEvent' --output text | grep -o '"errorCode":"[A-Za-z.]*"'

Then set up the notes’ example in a test account: create Group A with AmazonS3FullAccess, Group B with an inline Deny for s3:*, put a test user in both, and try aws s3 ls with that user’s credentials. Remove the user from Group B and try again.

#Summary

Key points

  • Every request ends in explicit deny, implicit deny or allow. Everything starts denied; an Allow lifts the default; a matching Deny beats every Allow.
  • An implicit deny (silence) is not a veto. Only an explicit Deny is.
  • A user’s identity-based permissions are the union of the user’s and all their groups’ policies. Groups have no priority.
  • A statement applies only if its action, resource, condition (and principal) all match. A false condition makes the statement disappear; it never flips Allow into Deny.
  • Grants: identity-based and resource-based policies. Limits: permissions boundaries, session policies, SCPs, RCPs. Limits never grant anything.
  • Same account: identity + resource = union. Identity ∩ boundary, ∩ SCP, ∩ session policy = intersection. Across accounts: both sides must allow.
  • SCPs and RCPs never affect the management account. The root user needs no Allow, but SCPs (in member accounts) and explicit Denies in resource policies can still stop it.
  • Use explicit Denies for guard rails (MFA, IP, Regions, protecting logs). For a Region guard use NotAction to exclude global services, which run in us-east-1.
  • To debug: read the message (implicit or explicit, which policy type), decode EC2’s encoded message with STS, search CloudTrail, test in the policy simulator, and allow a few seconds for changes.