How to Evaluate IAM Policy Allow vs Deny in Python

Evaluate an AWS-style IAM policy dict with explicit deny overriding allow and default deny.

Medium Python 3.9+ Aug 9, 2026 Cloud + Python 14 views 0 copies

Python code

36 lines
Python 3.9+
import json


def evaluate_policy(action, resource, policy):
    """Evaluate an IAM-like policy dict.
    Explicit deny wins over allow. Default is deny.
    """
    for statement in policy.get("Statement", []):
        effect = statement.get("Effect")
        actions = statement.get("Action", [])
        resources = statement.get("Resource", [])
        action_match = action in actions
        resource_match = resource in resources
        if action_match and resource_match:
            if effect == "Deny":
                return "deny"
            if effect == "Allow":
                decision = "allow"
    return decision if "decision" in locals() else "deny"


if __name__ == "__main__":
    iam_policy = {
        "Statement": [
            {"Effect": "Allow", "Action": ["s3:GetObject"], "Resource": ["arn:aws:s3:::demo-bucket/*"]},
            {"Effect": "Deny", "Action": ["s3:GetObject"], "Resource": ["arn:aws:s3:::demo-bucket/secret/*"]},
        ]
    }
    cases = [
        ("s3:GetObject", "arn:aws:s3:::demo-bucket/report.txt"),
        ("s3:GetObject", "arn:aws:s3:::demo-bucket/secret/keys.txt"),
        ("s3:GetObject", "arn:aws:s3:::demo-bucket/report.txt"),
    ]
    for action, resource in cases:
        result = evaluate_policy(action, resource, iam_policy)
        print(f"{action} on {resource} -> {result}")

Output

stdout
s3:GetObject on arn:aws:s3:::demo-bucket/report.txt -> allow
s3:GetObject on arn:aws:s3:::demo-bucket/secret/keys.txt -> deny
s3:GetObject on arn:aws:s3:::demo-bucket/report.txt -> allow

How it works

The evaluate_policy function iterates through each statement in the policy's Statement list. It checks if both the action and resource match the statement's Action and Resource arrays. An explicit Deny effect returns immediately, ensuring deny wins over any prior allow. If an Allow matches, the decision is saved but not returned immediately. At the end, if no deny matched, the last allow or the default deny is returned. The locals() check safely handles the case where no allow was found.

Common mistakes

  • Using `in` with a string for Action/Resource when the policy may use a single string instead of a list
  • Returning an allow on the first match without checking for later deny statements
  • Forgetting that IAM policies can have wildcards like `s3:*` which this simple check does not handle

Variations

  1. Use a normalized comparison that expands wildcards using `fnmatch` to support `*` patterns in actions/resources.
  2. Add a condition block evaluation to check for `StringEquals` or other IAM condition operators.

Real-world use cases

  • Unit-testing IAM policy logic in CI pipelines before deploying to AWS.
  • Simulating access decisions for a custom authorization service that mirrors AWS IAM semantics.
  • Building a security linter that flags overly permissive policies in infrastructure-as-code repositories.

Sponsored

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Cloud + Python

Related tutorials and quizzes for this topic.