chaowen.← Notes

Cloud platforms / identity and access control deep dive

2026-09-29

Cloud IAM policy evaluation: how an explicit deny wins across policy layers

A cloud request can be validly authenticated and still be denied—or unexpectedly allowed—because authorization combines several policy sources. This lesson follows one AWS request through identity, resource, boundary, organization, and session policies, showing where “allow” is insufficient, what an explicit deny means, and how to debug decisions without guessing.

1. Authentication is not authorization

Suppose a workload has obtained valid temporary credentials and calls s3:GetObject on arn:aws:s3:::reports-prod/monthly.csv. The signature verifies: AWS knows which principal made the request. Yet the request can fail because the principal lacks an applicable allow, a bucket policy denies it, an organization policy caps permissions, or a condition such as source network or encryption does not match.

The inverse mistake is dangerous too: an engineer sees a narrow identity policy and assumes the principal cannot do more. A resource policy may independently grant access to a principal, subject to the relevant guardrails. Authorization is not a single policy lookup; it is a decision over request context and multiple policy classes.

Useful mental model: authentication answers “who presented these credentials?” Authorization answers “may this principal perform this exact action on this exact resource under this context?” Never infer the second from the first.

2. The basic decision

IAM policies are JSON statements. A statement can match an action, resource, principal (where supported), and conditions; matching statements contribute an Allow or Deny. In the ordinary identity-policy case, the default is implicit deny: no matching allow means no permission. An applicable explicit deny overrides allows.

Request = (principal, action, resource, context) 1. Is there a matching explicit Deny? yes → DENY 2. Is there a sufficient applicable Allow? no → DENY 3. Do every relevant permission ceiling permit it? no → DENY 4. Otherwise → ALLOW

This is a reasoning aid, not a replacement for AWS’s service-specific authorization rules. Some services add service control details, dependent actions, resource-policy semantics, or condition keys. A “matching statement” requires all its elements and conditions to match; matching the action alone is not enough.

IAM does not treat statement order as priority. JSON order does not make a later allow cancel an earlier deny. Policies are sets of applicable rules, and the deny precedence is a semantic rule.

3. Policy layers and their logic

Identity-based policies: grants attached to identities

These include user, group, and role policies. For a same-account request, applicable identity-based allows generally combine as a union: an allow in any relevant identity policy can supply a grant. But that union is still constrained by permissions boundaries, session policies, and organization controls where applicable.

Resource-based policies: grants attached to resources

A bucket policy, key policy, queue policy, or other resource policy names principals and resource-side conditions. It can grant access that is not expressed as an identity policy grant. Exactly how a resource-policy grant interacts with a role session, an IAM user, or a cross-account principal depends on principal form and policy type. In particular, a grant to a role ARN and a grant directly to an assumed-role session ARN do not have identical interactions with implicit denies from boundaries and session policies. Do not reduce this to “resource policy always bypasses the boundary”; consult the service and IAM principal-specific rules.

Permission boundaries and session policies: ceilings, not grants

A permissions boundary defines the maximum permissions an identity-based policy can grant to a principal; attaching a boundary does not itself grant those permissions. A session policy supplied when assuming a role similarly restricts the resulting session. In the common identity-policy path, effective permissions are the intersection of the grant with each applicable ceiling. A boundary saying “allow S3 read” cannot create S3 read if no grant exists.

Service control policies: organization guardrails

An AWS Organizations SCP limits the maximum permissions available to principals in member accounts. It does not grant access and does not apply to every principal in every organization configuration (for example, the management account has special treatment). An action blocked by an SCP cannot be rescued by an account administrator adding a broader identity policy.

Conditions: predicates on the request

Conditions inspect context keys such as principal tags, requested region, source IP, TLS, or resource tags. A missing key is not equivalent to a matching key. Operators such as StringEquals, ArnLike, and Bool have distinct semantics; negated operators and IfExists need particularly careful review. Conditions are evaluated as part of whether a statement applies, not as a later “override.”

Cross-account access usually requires authorization on both sides: the caller’s account must permit the request, and the resource-owning account must trust or grant that principal, with applicable organization controls still in force. A principal having credentials from account A does not make account B’s resource policy irrelevant.

4. Follow one request through the system

  1. Establish principal. Signature verification and credential state determine the caller identity, often an assumed-role session with session tags and an issuer role.
  2. Normalize request. The service identifies action, resource ARN, account/region, and available context keys. Some API calls require multiple actions; a visible API name is not necessarily the only authorization check.
  3. Collect applicable policies. The decision may consider identity policies, resource policy, boundary, session policy, SCP, and service-specific controls. Not every type applies in every case.
  4. Match statements. Action, resource, principal and conditions must match. Wildcards match patterns; they do not make unrelated policy fields disappear.
  5. Resolve precedence and ceilings. Applicable explicit deny wins. Otherwise, a sufficient allow must exist and all applicable permission ceilings must permit it.
  6. Return service result. The API may report an authorization failure, but many services intentionally avoid revealing whether a resource exists. The caller often cannot identify the failing policy from the error alone.

Policy variables and request context make decisions dynamic. The same role can read one object but not another; succeed from one network path but fail from another; or work in a development account but fail in production due to an SCP. Therefore, test the exact principal, action, resource, and context—not merely a policy document in isolation.

5. Reproducible lab: prove deny precedence safely

Goal: use the AWS IAM policy simulator to observe an explicit deny defeating an allow without making a live S3 request. Prerequisites: AWS CLI v2 and credentials allowed to call iam:SimulateCustomPolicy. The simulator is a diagnostic, not a perfect replica of every live service path or every resource-policy interaction.

cat > /tmp/allow.json <<'JSON'
{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"s3:GetObject","Resource":"arn:aws:s3:::reports-prod/*"}]}
JSON

cat > /tmp/deny.json <<'JSON'
{"Version":"2012-10-17","Statement":[{"Effect":"Deny","Action":"s3:GetObject","Resource":"arn:aws:s3:::reports-prod/private/*"}]}
JSON

aws iam simulate-custom-policy \
  --policy-input-list file:///tmp/allow.json file:///tmp/deny.json \
  --action-names s3:GetObject \
  --resource-arns arn:aws:s3:::reports-prod/monthly.csv arn:aws:s3:::reports-prod/private/payroll.csv \
  --query 'EvaluationResults[].{Resource:EvalResourceName,Decision:EvalDecision,Matched:MatchedStatements[*].SourcePolicyId}' \
  --output table

Expected observation: the ordinary object evaluates allowed; the private object evaluates explicitly denied (CLI output may format the source identifier differently). If the CLI version rejects multiple policy documents or the shorthand options differ, pass the policies using its documented JSON parameter format; do not substitute a live production call.

Variation 1: remove deny.json from --policy-input-list. The private object becomes allowed because the wildcard allow covers it. This is why a narrow-looking deny elsewhere must be found, not assumed.

Variation 2: change the allow resource to arn:aws:s3:::reports-prod/public/*. The private object now has no matching allow and becomes implicitly denied; that differs diagnostically from explicit deny even though both fail authorization.

Context test: add a condition such as aws:SecureTransport to a policy and provide the corresponding context entry to the simulator. Compare true, false, and omitted context. The omitted-key behavior depends on the operator; this is a useful way to catch policies that accidentally treat absence as permission.

6. Debugging without policy roulette

  1. Capture the exact tuple: principal ARN and session, API/action, full resource ARN, region, source path, and relevant tags/context. Redact secrets; never collect access keys.
  2. Read the full error and CloudTrail event: CloudTrail can show the principal, action, resource, and whether the failure was an explicit deny in supported cases. Absence of an explicit-deny hint does not prove that no deny exists.
  3. Check grants first: find a matching allow for the exact action and resource. Verify wildcard scope and conditions.
  4. Then inspect ceilings: permissions boundary, role session policy, SCP hierarchy, and resource policy. Check the account and organization path, not just the role document.
  5. Use simulation appropriately: IAM Policy Simulator can test identity-policy effects and supported context. Access Analyzer policy validation identifies many policy errors and risky patterns. Neither proves every service-specific live request will succeed.
  6. Change one factor at a time: use a non-production principal/resource and a narrow temporary policy if needed. Avoid “AdministratorAccess until it works”; it hides the missing layer and expands blast radius.

Common traps include checking only the role policy while the SCP denies the action; testing the bucket ARN instead of the object ARN; forgetting an assumed-role session policy; condition keys unavailable in the request; and overlooking dependent actions such as KMS decrypt for encrypted objects. S3 access may require permission in both S3 and KMS, and the KMS key policy is another authorization boundary.

7. Security and production implications

Least privilege means designing small grants and independent guardrails, not piling arbitrary denies onto unknown policy paths. Denies can protect invariants—such as requiring TLS or preventing public ACLs—but broad conditions can unexpectedly lock out administrators or automation. Maintain a tested break-glass path, monitor its use, and understand which account principals an organization policy can affect.

Use organization-level controls for constraints that must survive account-level policy edits, workload roles for narrowly scoped grants, and resource policies for explicit cross-account trust. Review trust policies separately from permission policies: trust controls who may assume a role; permission controls what the resulting session may do. Treat external IDs, source identity, session tags, and role chaining as security-sensitive inputs.

Policy simulation and Access Analyzer are safer than trial-and-error against production, but they are not formal proofs. Keep policy changes in version control, run representative positive and negative tests, deploy incrementally, and inspect CloudTrail after rollout. Avoid logging credentials or sensitive resource names in broad diagnostic channels.

Takeaway

IAM authorization is a context-dependent decision over grants, resource-side permissions, and permission ceilings. Start from the exact request tuple, distinguish implicit from explicit deny, and check each applicable layer. An allow is a necessary grant in many paths, not a universal override; a boundary or organization guardrail can cap it, and resource-policy interactions depend on the principal and policy type. Model the real path, simulate safely, and validate with service evidence.

Primary sources

  1. AWS IAM: Policy evaluation logic.
  2. AWS IAM: Permissions boundaries.
  3. AWS Organizations: Service control policies.
  4. AWS IAM: Testing IAM policies with the simulator.
  5. AWS IAM: How IAM evaluates a request.
  6. AWS IAM Access Analyzer policy validation.