HackBlue

Tools · Cloud security · 02 / 24

How AWS IAM policy evaluation works: explicit deny, allow, SCPs, and boundaries

Follow the order AWS uses to decide whether a request is allowed, read a policy statement, and use the policy simulator to test a decision before it surprises you.

12 min read · practice only with authorization

The evaluation logic

Every AWS API request starts as an implicit deny. AWS then gathers all applicable policies: service control policies and resource control policies from AWS Organizations, resource-based policies such as a bucket policy, identity-based policies on the user or role, permissions boundaries, and session policies. An explicit Deny in any of them ends the evaluation with a deny. Otherwise, the request needs an Allow from the identity or resource policy and must not be blocked by an SCP, boundary, or session policy that fails to allow it.

Two practical consequences: you can never override an explicit deny with an allow, and an SCP that does not allow an action makes it impossible for anyone in the account, including administrators.

Reading a statement

A statement has Effect (Allow or Deny), Action (such as s3:GetObject), Resource (an ARN, possibly with wildcards), and optional Condition keys such as aws:SourceIp, aws:MultiFactorAuthPresent, or aws:PrincipalOrgID. NotAction and NotResource invert the match and are easy to misuse: Allow with NotAction grants everything except the listed actions.

Wildcards in Action like s3:* or * and in Resource like * are the first things to review in any policy.

Snippet
{
  "Version": "2012-10-17",
  "Statement": [
    { "Effect": "Allow", "Action": ["s3:GetObject"], "Resource": "arn:aws:s3:::reports-bucket/*" },
    { "Effect": "Deny", "Action": "s3:*", "Resource": "*",
      "Condition": { "Bool": { "aws:SecureTransport": "false" } } }
  ]
}

Lab: predict, then simulate

In a sandbox account, create a role with the policy above. Predict whether it can read reports-bucket/q3.pdf, list the bucket, and delete an object. Then test each with the IAM policy simulator from the CLI. Listing fails because s3:ListBucket is a separate action on the bucket ARN, not the object ARN, which is one of the most common surprises.

Add a permissions boundary that allows only s3:ListBucket and simulate again. GetObject is now denied too, because a boundary limits the maximum permissions even when the identity policy allows them.

Snippet
aws iam simulate-principal-policy --policy-source-arn arn:aws:iam::111122223333:role/reports-reader \
  --action-names s3:GetObject s3:ListBucket s3:DeleteObject \
  --resource-arns arn:aws:s3:::reports-bucket/q3.pdf arn:aws:s3:::reports-bucket \
  --query 'EvaluationResults[].{action:EvalActionName,resource:EvalResourceName,decision:EvalDecision}'

Debugging AccessDenied

AccessDenied error messages now often name the policy type that caused the denial, such as “with an explicit deny in a service control policy”. CloudTrail records the errorCode and the principal. Work through the order: is there an explicit deny, does an SCP or boundary allow it, does any policy allow it on the right resource ARN, and do the conditions match?

Fixing by adding a broad Allow often makes the account less secure without solving an explicit deny. Find the deny first.

Diagram

How AWS decides

  1. Start

    Implicit deny.

  2. Explicit deny?

    Any Deny in any applicable policy ends it: denied.

  3. Guardrails

    SCPs, RCPs, boundaries, and session policies must allow the action.

  4. Allow?

    An identity or resource policy must allow it on the right ARN.

Check yourself

1. An identity policy allows s3:* but an SCP has an explicit deny for s3:DeleteBucket. Can the user delete a bucket?

2. A role can GetObject but not list the bucket. Why?

3. What does a permissions boundary do?