Repository navigation
feat(query): add OIDC trust policy misconfiguration queries for AWS IAM - #8023
cx-antero-silva wants to merge 8 commits into
Conversation
…AM roles Adds two new Terraform/AWS queries addressing GitHub issue Checkmarx#6702: - IAM Role OIDC Trust Missing Sub Condition: detects AssumeRoleWithWebIdentity trust policies with no condition restricting the OIDC 'sub' claim, allowing any identity from the provider to assume the role. - IAM Role OIDC Trust Wildcard Sub Condition: detects AssumeRoleWithWebIdentity trust policies where StringLike is used with a 'sub' value that wildcards the repository/project segment (e.g. 'repo:*', 'project_path:*:...'), allowing any CI/CD pipeline on the OIDC provider to assume the role. Both queries cover aws_iam_role (inline JSON) and aws_iam_policy_document (HCL). Supports GitHub Actions, GitLab CI, CircleCI, and Bitbucket OIDC providers. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
c57d1e8 to
f71c0c8
Compare
|
At I ran the two sub-condition helpers on their own under opa 1.18.0, not a full scan: StringNotEquals on the sub key returns true, same as StringEquals, while an aud-only condition correctly does not match. Excluding the StringNot* operators would fix it without flagging StringEqualsIgnoreCase. Happy to add a positive fixture if useful, or to be corrected if this is meant to count. |
has_sub_condition_json/hcl counted any condition key ending in ":sub" as a valid restriction regardless of operator, so a trust policy whose only sub condition was StringNotEquals (or any other "Not" operator) passed the check even though it only excludes specific subs instead of restricting to an allow-list - every other identity from the OIDC provider can still assume the role. Now both paths also capture the operator and require it not contain "Not", so StringNotEquals/StringNotLike no longer satisfy the check, while StringEquals/StringEqualsIgnoreCase/StringLike still do. Adds positive5.tf/positive6.tf (StringNotEquals bypass, JSON and HCL) and negative3.tf/negative4.tf (StringEqualsIgnoreCase, confirming it's still correctly treated as a valid restriction) per review feedback from @Santoshkumarpuppala.
|
Good catch, thanks — fixed in 46f6341. Both `has_sub_condition_json` and `has_sub_condition_hcl` now capture the operator alongside the key/variable and require it not contain `"Not"`, so `StringNotEquals`/`StringNotLike` no longer satisfy the check, while `StringEquals`/`StringEqualsIgnoreCase`/`StringLike` still do, matching what you found testing the helpers directly. Added the fixtures you offered: `positive5.tf`/`positive6.tf` cover the `StringNotEquals` bypass (JSON and HCL paths), and `negative3.tf`/`negative4.tf` confirm `StringEqualsIgnoreCase` still correctly passes. Ran a full scan locally to confirm the new positives are caught and the negatives aren't. |
|
Hi @cx-antero-silva, I reviewed the queries and tested extra samples against them. The proposed version is a single commit on top of your branch:
To take it: Proposed Changes:
New findings (reproduced with samples)
I can share the full review document with the details if this is not enough. As a follow-up note, while reviewing the PR I noticed that our shared library functions ( Thanks for the effort on these queries! |
…verage Merge "IAM Role OIDC Trust Missing Sub Condition" and "IAM Role OIDC Trust Wildcard Sub Condition" into "IAM Role OIDC Trust Unrestricted Sub Condition". The missing allow-listing condition and the wildcard value cases are kept as separate policies with their own messages. - add get_condition and lowercase actions/action to terraform get_action - accept the lowercase "statement" key in common get_statement - match the action case-insensitively, as a string or a list - handle several "principals" blocks in aws_iam_policy_document - only count string, non-negated operators as an allow-list (not Null, Bool) - flag wildcards under StringLike, StringLikeIfExists and set-operator variants, including a leading wildcard and "?" - treat a specific allow-listing sub as restrictive when ANDed with a wildcard - set riskScore to 6.0 and merge the descriptions - rework the test samples to cover JSON/HCL, string/array action, principals, statements, conditions and operators, each with a one line comment
The merged descriptionText from the OIDC trust query consolidation was 576 characters, exceeding the metadata schema's 500-char limit and failing schema validation. Trimmed to 445 chars while keeping the same substance (negated/non-string operators don't restrict; StringLike wildcards are still too permissive).
|
Agreed on merging into a single query, and these are solid fixes — thanks for the thorough review and for preparing the branch, that made this easy to verify and take. Cherry-picked 1093960 as-is (d0b4f42 is the only thing I added on top). Before taking it I traced through `sub_conditions`/`is_allow_listing`/`is_wildcard`/`has_restrictive_sub` against the JSON-multi-operator, HCL-single-block, and HCL-array-of-blocks cases to convince myself the AND-across-operators and Null/Not exclusions were handled correctly in all three shapes, and checked the `get_statement`/`get_action` library changes are genuinely additive (only reached via `else` when the existing JSON-key branches don't match) — ran the other ~30 queries that call them to confirm no behavior change there. One issue I did find: the merged `descriptionText` was 576 chars, over the schema's 500-char limit, which would have failed `Validate Queries Metadata`. Trimmed it to 445 chars in d0b4f42, same substance. Ran the full scan locally against all 48 fixtures plus the search-line and metadata-schema checks — everything passes, 99% coverage on the merged query. |
cx-artur-ribeiro
left a comment
There was a problem hiding this comment.
Thanks for the effort, I'll ask a second maintainer for review!
Insane job @cx-antero-silva 🙏
|
condition {
test = "StringLike"
variable = "token.actions.githubusercontent.com:sub"
values = ["repo:octo-org*/octo-repo*:ref:refs/heads/main"]
}
The shape gets more likely after 2026-07-15. GitHub now issues A pattern like A separate case goes the other way. Is a partial owner wildcard meant to be in scope, and should Cognito identity pools be outside the
|
…pools
Two follow-up issues from review:
1. is_loose_sub_value only matched a wildcard immediately after the
provider prefix (e.g. "repo:*"), missing a wildcard placed after a
literal owner/group name (e.g. "repo:octo-org*/octo-repo*:..."),
which still lets anyone able to register a matching org/repo name
assume the role. Broadened the regex to also catch a wildcard
anywhere in the owner segment when scoped to "repo:"/"project_path:"
prefixes specifically, so Bitbucket's unrelated "{REPO_UUID}:{*}"
stays unaffected. Verified against both regexes directly: the new
pattern catches the bypass and leaves every existing fixture's
verdict unchanged.
Note: this also flags "repo:myorg*/*", which GitHub's 2026-07-15
move to immutable "OWNER@OWNER_ID" subject claims makes a plausible
one-org compatibility pattern going forward - the same shape as an
actual overly-broad org-prefix match, and not distinguishable from
it by pattern alone. Erring toward flagging here, consistent with
this query's existing bias (e.g. the Not-operator and Null-operator
exclusions already added).
2. is_oidc_trust matched any Federated principal, so AWS's documented
Cognito Identity Pool trust pattern (Federated:
cognito-identity.amazonaws.com, restricted via a StringEquals on
":aud", no ":sub" at all) was flagged as missing a sub condition.
Cognito doesn't use ":sub" for this; added is_cognito_identity_pool
to exclude it from is_oidc_trust entirely, since this query's
premise doesn't apply to that trust model.
Adds positive30.tf (owner-prefix wildcard bypass) and
negative19.tf/negative21.tf (Cognito trust policy, JSON and HCL forms).
|
Both real, thanks — fixed in 02f55a6. Cognito: yes, out of scope — added `is_cognito_identity_pool` to exclude it from `is_oidc_trust` entirely. AWS's documented Cognito pattern doesn't use `:sub` at all (it restricts via `:aud`/`:amr` instead), so this query's premise just doesn't apply to that trust model. Added JSON and HCL negative fixtures (negative19.tf/negative21.tf) using the exact pattern from the Cognito dev guide. Partial owner wildcard: took your proposed regex as-is (verified it directly against Go's regexp/RE2, same engine Rego's On "is a partial owner wildcard meant to be in scope": I don't think we can resolve the Ran the full local scan plus the metadata/content test suite - all 51 fixtures pass, 98% coverage. |
Summary
Closes #6702
Adds two new Terraform/AWS queries to detect OIDC role trust policy misconfigurations between AWS IAM and CI/CD providers (GitHub Actions, GitLab CI, CircleCI, Bitbucket).
IAM Role OIDC Trust Missing Sub Condition (
HIGH): DetectsAssumeRoleWithWebIdentitytrust policies with no condition restricting the OIDCsub(subject) claim, or a condition that only checksaudbut notsub. Without this guard, any identity that can obtain a token from the OIDC provider can assume the role.IAM Role OIDC Trust Wildcard Sub Condition (
HIGH): Detects trust policies whereStringLikeis used on thesubclaim with a value that wildcards the org/project segment — e.g.repo:*(any GitHub repo) orproject_path:*:ref_type:branch:ref:*(any GitLab project on any branch). Allowsrepo:myorg/myrepo:ref:refs/heads/*as it scopes to a specific repo.Both queries cover
aws_iam_role(inline JSON heredoc) anddata "aws_iam_policy_document"HCL data sources.I submit this contribution under the Apache-2.0 license.