IAM Policy Evaluation Logic Explained - How Implicit Deny Loses to Explicit Allow
A step-by-step explanation of how IAM evaluates requests to allow or deny them, covering the priority of implicit deny, explicit allow, and explicit deny, as well as intersection logic for SCPs, permission boundaries, and session policies.
IAM Evaluation Produces Only Three Outcomes
IAM policy evaluation may seem complex, but the final outcome is always one of three: explicit deny, explicit allow, or implicit deny. The priority is always: explicit deny > explicit allow > implicit deny. An implicit deny is the state where no policy contains an Allow statement. Since IAM denies all actions by default, any action not explicitly allowed is automatically denied. This is the technical foundation of the principle of least privilege. An explicit allow occurs when a policy's Effect is Allow and the request conditions match the policy conditions. An explicit deny occurs when a policy's Effect is Deny and the conditions match; it always takes precedence regardless of any Allow in other policies. This "deny always wins" rule is the cornerstone of IAM's security model. Even if an administrator accidentally grants a broad Allow, a Deny policy can reliably block specific actions.
Evaluation Flow Within a Single Account
Policy evaluation within a single account is a multi-stage process that evaluates multiple policy types in sequence. First, Organizations SCPs (Service Control Policies) are evaluated. SCPs act as a "ceiling" for the organization's accounts; any action not permitted by the SCP is denied regardless of any Allow in account-level policies. Next, resource-based policies (such as S3 bucket policies and Lambda resource policies) are evaluated. If a resource-based policy explicitly allows access, the request is permitted even without an Allow in identity-based policies. This rule applies only within the same account; for cross-account access, both policies must contain an Allow. Then, permission boundaries are evaluated. A permission boundary is an "upper limit" set on an IAM user or role; only actions allowed by both the identity-based policy and the permission boundary (the intersection) are permitted. Finally, identity-based policies (IAM policies) are evaluated.
Intersection Logic of SCPs and Permission Boundaries
Both SCPs and permission boundaries set an "upper limit" on permissions, but they apply at different levels. SCPs apply at the Organizations account level, while permission boundaries apply at the IAM entity (user/role) level. When both are configured, the actions ultimately permitted are the intersection of "allowed by SCP AND allowed by permission boundary AND allowed by identity-based policy." Here is a concrete example. If the SCP allows s3:* and ec2:*, the permission boundary allows s3:* and lambda:*, and the identity-based policy allows s3:GetObject and lambda:InvokeFunction, then only s3:GetObject is actually permitted. lambda:InvokeFunction is denied because it is not allowed by the SCP. This intersection logic enables multi-layered permission management: the security team sets organization-wide guardrails with SCPs, development team leads set permission ceilings for team members with permission boundaries, and individual developers are granted necessary permissions through identity-based policies.
Condition Key Evaluation - Commonly Overlooked Pitfalls
The Condition block in IAM policies is a powerful feature that controls access based on request attributes (source IP, request time, tag values, etc.), but there are pitfalls in the evaluation logic. The most common misunderstanding is the behavior when a condition key does not exist. For example, if you set a policy restricting access by aws:SourceIp, internal calls from AWS services (such as Lambda accessing S3) may not include aws:SourceIp. When a condition key does not exist, the condition evaluates as "not matched." If an Allow policy's condition does not match, the Allow is not applied, resulting in an implicit deny. If a Deny policy's condition does not match, the Deny is not applied, and other policies' Allows take effect. Without understanding this asymmetry, you may encounter issues like "setting an IP restriction blocked access from Lambda as well." The solution is to use the aws:ViaAWSService condition key to exclude requests made through AWS services. You also need to accurately understand the difference between StringEquals and StringLike (wildcard handling) and the logical relationship between IpAddress and NotIpAddress when designing policies.
Cross-Account Access Evaluation Rules
Cross-account access evaluation rules differ from those within a single account. When a principal in Account A accesses a resource in Account B, both the identity-based policy in Account A and the resource-based policy in Account B must contain an Allow. Within a single account, an Allow in the resource-based policy alone is sufficient, but this "one-side-only OK" rule does not apply to cross-account access. However, there is an exception. IAM role trust policies (AssumeRole) function as resource-based policies even for cross-account access, and the trust policy's Allow alone is sufficient to permit AssumeRole. Actions after AssumeRole follow the role's identity-based policies. When allowing cross-account access via an S3 bucket policy, you must specify Account A's ARN as the Principal in the bucket policy and also allow s3:GetObject in Account A's identity-based policy. This dual-permission requirement is a security mechanism that prevents unintended access by either account's administrator. For a systematic study of IAM design patterns, specialized books on Amazon can be a helpful reference.