Skip to content

iam-ape: resolve principal-based deny conditions (ARN/account/tag) (CIEM-685)#37

Open
yanas-orca wants to merge 4 commits into
mainfrom
CIEM-685/ape-resolve-principal-arn-conditions
Open

iam-ape: resolve principal-based deny conditions (ARN/account/tag) (CIEM-685)#37
yanas-orca wants to merge 4 commits into
mainfrom
CIEM-685/ape-resolve-principal-arn-conditions

Conversation

@yanas-orca

@yanas-orca yanas-orca commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Problem

iam-ape does not evaluate IAM conditions — it carries them symbolically. A conditional deny is treated as a partial deny: the action stays in the effective allow-set (with the condition merged in) rather than being removed. Only an unconditional deny fully removes an action.

That's correct for request-context conditions a static evaluator can't know (region, MFA, source IP, resource tags, time). But conditions keyed on the principalaws:PrincipalARN, aws:PrincipalAccount, aws:PrincipalTag/*are decidable at evaluation time, because ape evaluates per-principal and holds the principal's entity object.

Impact (CIEM-685): customer root SCPs deny bedrock:Create/Update/DeleteGuardrail conditionally — e.g. ArnNotLike aws:PrincipalARN […SSO admin roles], and a second SCP with StringNotEquals aws:PrincipalAccount […] AND ArnNotLike aws:PrincipalARN […]. Because the denies are conditional, ape leaves the guardrail actions in a non-exempt role's effective allow-set, so the "unauthorized guardrail management" alert keeps firing even though the role is denied those actions in the cloud.

Fix

Resolve deny conditions built only from known principal facts against the evaluated principal:

  • aws:PrincipalARN, aws:PrincipalAccount (parsed from the ARN), aws:PrincipalTag/<k> (from the principal's Tags).
  • Operators: ArnLike/ArnNotLike/ArnEquals/ArnNotEquals + String* equivalents.
  • Resolution outcome per statement: applies → enforce as an unconditional deny; doesn't apply → drop; undecidable (any non-principal key like region/MFA, an unmodeled operator, or an absent tag) → keep symbolic (unchanged behavior).

Why resolve before expansion

The policy expander splits a multi-operator Condition block into independent single-operator deny entries (expand_policy._append_action). Resolving after expansion would treat A AND B as two separate denies and could strip an action based on one clause alone — wrongly closing alerts. So conditions are resolved on the whole statement, before expansion, preserving AND semantics (verified against the real p-gh487uoz account+arn deny).

How it stays fast

SCPs are partitioned in __init__:

  • the static baseline (the FullAWSAccess * allow + any statement we can't resolve) is expanded once, as today;
  • only principal-conditional Deny statements are kept raw and resolved + expanded per principal — these are targeted (specific actions), so there's no per-principal re-expansion of *.

The should_deny / permission-boundary hot path is unchanged — resolved denies simply arrive unconditional.

Tests

tests/test_principal_conditions.py (10 cases): unconditional removal; ARN condition applies/skips (non-exempt vs exempt); account condition applies/skips; mixed account+ARN AND-semantics (applies for non-allowed account, skips for allowed); principal-tag applies/skips; absent-tag stays symbolic; region condition stays symbolic; mixed principal+region stays symbolic.

All 48 tests pass (incl. existing test_e2e). black / isort / flake8 clean; mypy clean for evaluator.py. Version bumped 1.1.7 → 1.1.8.

Validated on the customer's real SCPs (p-6pwmb6if + p-gh487uoz): a non-exempt role loses the guardrail actions (alert clears); the exempt SSO admin roles keep them (alert correctly still fires for those — a separate rule-tuning question).

Notes for reviewers (IAM team)

  • Changes how every deny with a principal-only condition resolves, fleet-wide (more accurate; matches AWS). Worth a blast check on real accounts before release.
  • Conservative by construction: anything not provably decidable stays symbolic, so errors fail toward keeping permissions / firing alerts, never toward wrongly clearing them.
  • Deliberately out of scope: aws:PrincipalOrgID/OrgPaths (not in get-account-authorization-details), aws:username/userid (runtime-session ambiguity for roles), and all request-context keys (undecidable statically).
  • Still pending for CIEM-685: confirm the customer's actual deployed guardrail-deny SCP (conditional vs unconditional) to decide whether this change is required for their alert or just a correctness improvement.

Effective-permissions evaluation now resolves deny-statement conditions that
depend solely on aws:PrincipalARN against the evaluated principal, instead of
carrying them symbolically. A provable deny becomes unconditional (action
removed); a provably inapplicable one is skipped; conditions referencing any
other key are left untouched (prior behavior).

Bump 1.1.7 -> 1.1.8; add tests.

@orca-security-eu orca-security-eu Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Orca Security Scan Summary

Status Check Issues by priority
Passed Passed Secrets high 0   medium 0   low 0   info 0 View in Orca
Passed Passed Vulnerabilities high 0   medium 0   low 0   info 0 View in Orca

… + tag)

Widen deny-condition resolution to aws:PrincipalARN, aws:PrincipalAccount and
aws:PrincipalTag/*, and resolve the whole Condition BEFORE the policy expander
splits multi-operator blocks into independent entries -- so AND semantics are
preserved (a mixed account+arn deny like the real p-gh487uoz resolves correctly).

SCPs are partitioned in __init__: the static baseline (incl. FullAWSAccess *) is
expanded once; only principal-conditional Deny statements are resolved+expanded
per principal, so no per-principal re-expansion of *. should_deny/boundary hot
path is unchanged. Undecidable clauses (region/MFA/tags-absent/...) stay symbolic.
@yanas-orca yanas-orca changed the title iam-ape: resolve aws:PrincipalARN-only conditions on denies (CIEM-685) iam-ape: resolve principal-based deny conditions (ARN/account/tag) (CIEM-685) Jul 8, 2026
@sonarqubecloud

sonarqubecloud Bot commented Jul 8, 2026

Copy link
Copy Markdown

@yanas-orca yanas-orca marked this pull request as ready for review July 8, 2026 14:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants