fix(terraform): CKV_AWS_358 & CKV_AWS_393 - inspect every value of a multi-value OIDC sub condition - #7665
Open
Dashtid wants to merge 1 commit into
Open
Conversation
…multi-value OIDC sub condition
IAM evaluates the values of a single condition key with a logical OR:
"each context key must resolve to true for at least one key value for
the desired Allow or Deny effect to be invoked" (IAM User Guide,
Single-valued context key policy examples). So in a GitHub Actions OIDC
trust policy the loosest value in the list sets the trust boundary, and
a tight value beside it constrains nothing:
"Condition": {"StringLike": {"token.actions.githubusercontent.com:sub":
["repo:myOrg/myRepo:ref:refs/heads/MyBranch", "*"]}}
Both checks stop after one value of that list.
CKV_AWS_393 is the clear case. _evaluate_sub_conditions returns the
verdict of the first value it classifies, including PASSED, so every
later value is unreachable. assume_role_policy is jsonencode'd, which
reaches the check as one opaque string, so the author's JSON order
survives intact: measured on main @ d8aec9d through the Runner, an
aws_iam_role whose sub condition is
["repo:myOrg/myRepo:ref:refs/heads/MyBranch", "*"] -> PASSED, and the
same list with "workflow:github-actions:*" as the second value ->
PASSED, while each of those loose values on its own -> FAILED. A bare
wildcard in any non-first position is invisible.
CKV_AWS_358 reads condition_value[0] and breaks, but its exposure is
narrower and worth stating plainly rather than leaving for a reviewer
to discover: clean_parser_types_lst (checkov/terraform/modules/
module_utils.py:224, reached from tf_parser.parse_file) sorts string
values lexicographically before any check runs. That incidentally drags
"*" and "environment:*" to index 0, so those are caught today. What is
missed is any unsafe value that sorts after every safe one - of the six
gh_abusable_claims only "workflow" sorts after "repo:", plus a misused
"repo:<owner-with-no-slash>" whose owner sorts high, e.g.
["repo:myOrg/myRepo:ref:refs/heads/MyBranch", "repo:zzz-org*"] ->
PASSED. Narrower than CKV_AWS_393, still a miss. The sort is left
untouched: it is load-bearing across the parser and is not the bug.
AWS's create-time validation does not close this either. IAM verifies
only that the sub condition key "is present and that its value is not
solely a wildcard character (* and ?) or null" (IAM User Guide,
Configuring a role for GitHub OIDC identity provider) - a documented
presence-and-shape check that says nothing about the remaining values
of a list. A static check is the layer that can catch the poisoned one.
Fix: classify EVERY string value of a sub condition - FAILED on the
first unsafe value, PASSED only once all values of that condition are
safe. Deliberately unchanged: single-value semantics; the intentional
repo:org/* pass (bridgecrewio#7221), including the pass-fm-customer regression
fixture; and the existing "first sub condition decides" behaviour when
several condition blocks or operators each constrain :sub, since IAM
ANDs those and an unrelated loose block does not make a tightly-pinned
policy unsafe.
Tests: there is no multi-value coverage upstream today.
fail-multivalue-wildcard, fail-multivalue-abusable and
pass-multivalue-pinned added to both suites, labels mirrored across the
data and resource variants. Reverting the two check files alone fails
the new assertions.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
Description
IAM evaluates the values of a single condition key with a logical OR — "each context key must resolve to true for at least one key value for the desired
AlloworDenyeffect to be invoked" (IAM User Guide, single-valued context key policy examples). In a GitHub Actions OIDC trust policy the loosest value in the list therefore sets the trust boundary, and a tight value beside it constrains nothing:Both AWS GitHub-OIDC checks stop after one value of that list.
CKV_AWS_393(GithubActionsOIDCTrustPolicyOnRole.py) is the clear case._evaluate_sub_conditionsreturns the verdict of the first value it classifies — includingPASSED— so every later value is unreachable.assume_role_policyisjsonencode'd and reaches the check as one opaque string, so the author's JSON order survives intact. Measured onmain@d8aec9dbthrough the TerraformRunner:token.actions.githubusercontent.com:subvalues["repo:myOrg/myRepo:ref:refs/heads/MyBranch", "*"]["repo:myOrg/myRepo:ref:refs/heads/MyBranch", "workflow:github-actions:*"]["*"]["workflow:github-actions:*"]A bare wildcard in any non-first position is invisible.
CKV_AWS_358readscondition_value[0]and breaks, but its exposure is narrower and worth stating up front rather than leaving for a reviewer to rediscover:clean_parser_types_lst(checkov/terraform/modules/module_utils.py:224, reached fromtf_parser.parse_file) sorts string values lexicographically before any check runs. That incidentally drags"*"and"environment:*"to index 0, so those are caught today. What is missed is any unsafe value that sorts after every safe one — of the sixgh_abusable_claimsonlyworkflowsorts afterrepo:, plus a misusedrepo:<owner-with-no-slash>whose owner sorts high:values(HCL)["repo:myOrg/myRepo:ref:refs/heads/MyBranch", "workflow:github-actions:*"]["repo:myOrg/myRepo:ref:refs/heads/MyBranch", "repo:zzz-org*"]Narrower than
CKV_AWS_393, still a miss. The sort is deliberately left untouched — it is load-bearing across the parser and is not the bug.AWS's create-time validation does not close this either: IAM verifies only that the
subcondition key "is present and that its value is not solely a wildcard character (* and ?) or null" (IAM User Guide, configuring a role for GitHub OIDC identity provider) — a presence-and-shape check that says nothing about the remaining values of a list. A static check is the layer that can catch the poisoned one.Fix
Classify every string value of a
subcondition:FAILEDon the first unsafe value,PASSEDonly once all values of that condition are safe.Deliberately unchanged:
repo:org/*pass (fix(terraform): Added support in restricting to a specific GitHub organization for GithubActionsOIDCTrustPolicy #7221), including thepass-fm-customerregression fixture;subcondition decides" behaviour when several condition blocks or operators each constrain:sub— IAM ANDs those, so an unrelated loose block does not make a tightly-pinned policy unsafe. A parity fixture covering that shape returns the same verdicts as unfixedmain.Tests
There is no multi-value coverage upstream today.
fail-multivalue-wildcard,fail-multivalue-abusableandpass-multivalue-pinnedare added to both suites, with labels mirrored across the data and resource variants. Reverting the two check files alone, with the fixtures in place, fails the new assertions — so they are regression guards, not just added coverage.Checklist:
Sequencing: this is independent of #7610 and #7627, but it touches the same two AWS test suites as #7610. The fixture additions are append-only, so if #7610 lands first I will rebase this — no need to hold either on the other's account.
It is also the sharper half of the open question left in #7610's body. That one asked whether the org-wide
repo:myOrg/*pass is intended long-term; this is the case where the answer does not depend on that judgement at all, since a list can pair a fully-pinned value with one that is unambiguously unsafe. Is there a reason the value loop was scoped to a single element, or would failing closed on any unsafe value in the list be the behaviour you would want here?