Right, let’s demystify the single most important concept in AWS IAM: how it decides whether to let you do something. This isn’t magic; it’s a brutally logical, step-by-step evaluation process. Get this wrong, and you’ll be staring at AccessDenied errors wondering what you did to anger the cloud gods. Get it right, and you feel like a wizard. So let’s become wizards.

The core of IAM policy evaluation is a simple flowchart that runs every time you make a request to AWS. It checks every policy that could possibly apply to your request—identity-based policies, resource-based policies, permissions boundaries, and so on. But its logic boils down to a few ironclad rules.

The Cardinal Rule: An Explicit DENY Always Wins

Forget everything else for a second. Burn this into your memory: if any policy that applies to your request includes an explicit "Effect": "Deny" for that specific action and resource, the request is BLOCKED. Full stop. No appeals. It doesn’t matter if seventeen other policies scream "Allow". Deny is the ultimate veto power. This is the highest priority in the evaluation logic, and for good reason—it’s your primary tool for locking down access, no matter how badly someone messes up their Allow policies.

The Evaluation Logic Flow

Here’s the official, slightly dry, sequence. I’ll juice it up with some commentary afterward.

  1. Any explicit Deny? The evaluation checks all relevant policies for a Deny. If found, access is denied.
  2. Any explicit Allow? If no explicit Deny is found, it checks for any explicit Allow in the relevant policies. If found, access is allowed.
  3. Implicit Deny (the default state). If the request made it through steps 1 and 2 without a single explicit Allow or an explicit Deny, it is implicitly denied. This is the default, secure state of AWS. You are denied until you are explicitly allowed.

Think of it like a bouncer at an exclusive club. Their instructions are:

  1. “Is this person on the ‘ABSOLUTELY NOT’ list (Explicit Deny)?” -> “Get lost.”
  2. “If not, are they on the ‘Guest List’ (Explicit Allow)?” -> “Come on in!”
  3. “If I can’t find their name on either list?” -> “Sorry, not tonight.” (Implicit Deny).

The Silent Killer: Implicit Deny

This is the one that trips everyone up. You write a beautiful, generous policy for your user, give them full S3 access, and they still can’t read a file in a specific bucket. You’re baffled. “But I allowed s3:GetObject!” you shout at the console.

The problem is almost certainly an implicit deny. Your user’s identity-based policy allows the action, but the resource-based policy on the S3 bucket (the bucket policy) doesn’t explicitly allow their ARN. Since there’s no explicit Allow from the bucket’s perspective, the result is an implicit deny. The evaluation doesn’t see a green light from all necessary angles, so it’s a red light.

Let’s look at code. Imagine you have an IAM user with this policy attached. It’s well-intentioned but poorly scoped.

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

Now, that user tries to read a file in arn:aws:s3:::production-data-bucket/financial-records.xlsx. The identity policy says “Allow”. But unbeknownst to you, the S3 bucket has a ridiculously restrictive bucket policy that only allows a specific role:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::123456789012:role/DataProcessorRole"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::production-data-bucket/*"
        }
    ]
}

From the bucket’s point of view, your user isn’t on its guest list. It doesn’t need to issue an explicit Deny; it just doesn’t issue an Allow. The evaluation logic hits step 2 for the identity policy (Allow!) but then must also consider the resource policy. It finds no explicit Allow for the user there. The final result? No explicit Allow from all parties -> Implicit Deny. The user gets a 403 Access Denied. The fix is to either add the user’s ARN to the bucket policy or, better yet, put them in a group that’s allowed.

Best Practices and Pitfalls

  1. Start Restrictive, Then Allow: The implicit deny default is your best friend. It means a new user or role has no permissions until you deliberately grant them. This is secure by design. Embrace it.
  2. Use Explicit Deny Sparingly and Thoughtfully: An explicit Deny is a sledgehammer. Use it for true exceptions, like “This group can access all S3 buckets except the one containing payroll.” If you start scattering Deny statements everywhere, you’ll create a nightmare of conflicting rules that are impossible to debug.
  3. Remember the Distinction Between Identity and Resource Policies: For a request to succeed, it usually needs permission from both the identity (user/role) and the resource (if it has a policy). Cross-account access is the classic example that makes this necessary.
  4. Simulate Policies in IAM: Before you even think about deploying something, use the IAM Policy Simulator. It lets you mock a request and shows you exactly which policies contributed to the decision and why. It’s the single best tool for untangling this mess. It’s like a debugger for your permissions.