Skip to content

sec(iac/aws): reject the all-zeros OIDC thumbprint in the aws-target bundles - #1678

Open
cristim wants to merge 3 commits into
mainfrom
sec/1615-oidc-thumbprint
Open

sec(iac/aws): reject the all-zeros OIDC thumbprint in the aws-target bundles#1678
cristim wants to merge 3 commits into
mainfrom
sec/1615-oidc-thumbprint

Conversation

@cristim

@cristim cristim commented Jul 29, 2026

Copy link
Copy Markdown
Member

Closes #1615.

Read this first: the reachability is narrower than the issue states. The issue describes an authentication bypass reachable by MITM/DNS-hijacking the issuer's JWKS endpoint. That is not what the all-zeros thumbprint does under current AWS behaviour. The parameter is still wrong and still needs fixing, but for a different reason, and the fix is different from the one the issue proposes. Details in What I verified and Corrected reachability.

What I verified about AWS thumbprint handling

Two current AWS docs, quoted verbatim.

Obtain the thumbprint for an OpenID Connect identity provider:

AWS secures communication with OIDC identity providers (IdPs) using our library of trusted root certificate authorities (CAs) to verify the JSON Web Key Set (JWKS) endpoint's TLS certificate. If your OIDC IdP relies on a certificate that is not signed by one of these trusted CAs, only then we secure communication using the thumbprints set in the IdP's configuration. AWS will fall back to thumbprint verification if we are unable to retrieve the TLS certificate or if TLS v1.3 is required.

CreateOpenIDConnectProvider, on ThumbprintList:

This parameter is optional. If it is not included, IAM will retrieve and use the top intermediate certificate authority (CA) thumbprint of the OpenID Connect identity provider server certificate.

Required: No

AWS::IAM::OIDCProvider says the same for the CloudFormation property, and lists it as Update requires: No interruption. aws_iam_openid_connect_provider documents thumbprint_list as (Optional).

Note the trusted-CA rule is no longer scoped to a named list of IdPs (the older "Auth0, GitHub, GitLab, Google, S3-hosted JWKS" phrasing that both this repo's Terraform module and much of the community folklore are built on). It now applies to any issuer whose JWKS endpoint presents a publicly trusted certificate.

Corrected reachability

There is no fail-open value for this parameter. The thumbprint is a comparison target for a certificate fingerprint. Unlike OIDCSubjectClaim in #1602, it is not part of an IAM policy document, so there is no ${...} policy-variable expansion, no wildcard, and no value that makes AWS accept an arbitrary certificate. Every non-matching value fails closed.

Split by path:

Path When What all-zeros does
Primary JWKS endpoint's TLS certificate chains to a CA in AWS's trusted-root library. Covers both documented issuers and every realistic hosted IdP. Not read. A correct thumbprint is equally not read. The value is inert.
Fallback Certificate does not chain to a trusted CA, AWS cannot retrieve it, or the endpoint requires TLS 1.3. Compared against the retrieved certificate's SHA-1. All-zeros is not the fingerprint of any certificate, so it matches nothing and every AssumeRoleWithWebIdentity call fails.

So the issue's scenario does not hold. An attacker who MITMs or DNS-hijacks the JWKS endpoint still has to present a certificate signed by a CA in AWS's trusted-root library, and that bar does not move with this parameter — it is identical whether the thumbprint is all-zeros, correct, or absent. The Terraform module's stated rationale, "the all-zeros value bypasses the CA-chain check entirely", describes a mechanism that does not exist.

What is actually wrong is narrower, and is a defect in all three of the ways the parameter was specified:

  1. The all-zeros value is the one value guaranteed never to match a real certificate. On the fallback path it does not weaken verification, it nulls it out — converting "verify" into "always fail" — while reading like a deliberate configuration.
  2. The documentation asserted a security property the value does not carry: "Leave as zeros only for well-known providers where AWS auto-validates the chain" tells the operator that setting it correctly matters for other issuers, when in fact it is unread for every publicly-trusted issuer and actively breaks private-CA ones.
  3. There was no way to express "let IAM retrieve the correct thumbprint", which is AWS's own default and the only correct configuration for the case where the thumbprint is actually consulted.

The severity is therefore availability and misleading documentation on a customer-deployed template, not takeover of the commitment-purchasing role. I have not relabelled the issue; that is the maintainer's call.

The fix

Empty is now the default and means omit ThumbprintList entirely, so IAM retrieves and uses the issuer's real top intermediate CA thumbprint — what the IAM console does by default. An operator with a private-CA issuer can still pin a real thumbprint. The placeholder is rejected.

CloudFormation (iac/federation/aws-target/cloudformation/template.yaml):

  • OIDCThumbprint defaults to ""; a HasThumbprint condition resolves ThumbprintList to AWS::NoValue when it is empty.
  • Two independent layers reject the placeholder:
    • AllowedPattern: "^$|^[0-9a-fA-F]{0,39}[1-9a-fA-F][0-9a-fA-F]{0,39}$" plus MaxLength: 40 — empty, or up to 40 hex characters of which at least one is non-zero.
    • An unconditional Rules assertion comparing against the literal. Rules are evaluated before any resource is created and abort the operation on failure.
  • The parameter documentation is replaced with the behaviour above.

Terraform (iac/federation/aws-target/terraform/): brought to the same rule, because the issue names this module as the reference implementation and its guard rests on the rationale corrected above. Leaving the two paths contradicting each other on the same defect is the drift #1547 tracks. thumbprint_list defaults to [] and is passed as null when empty; the all-zeros entry is rejected unconditionally rather than for non-allowlisted issuers; the "must contain at least one thumbprint" validation is dropped, since empty is now correct.

Why the pattern is not a tautology

The #1602 precedent was an AllowedPattern that rejected a harmless value (*, compared literally by StringEquals) while admitting the fail-open one (${...}, expanded by IAM). Enumerating the same way here:

  • Fail-open values: none. See above — no value of this parameter causes AWS to accept a certificate it would otherwise reject.
  • Operationally broken values: the all-zeros placeholder, and any wrong-but-well-formed thumbprint. Both fail closed. Only the first is a shipped default reachable by doing nothing, and only that one is rejected.
  • Malformed values: non-hex, wrong length, whitespace, wildcards, policy variables. Rejected by the pattern, except 1–39 hex characters, which the IAM API rejects on its fixed length of 40 — fail-closed, with the stack rolling back.

The guard is an exact-match rejection over a fully enumerated domain, not a heuristic: within "empty or 40 hex characters", 0 * 40 is the only all-zero string, and 0 has no case variants.

The one asymmetry worth flagging: the pattern rejects 0 * 40 but accepts 0 * 39 + 1, which is equally bogus. That is deliberate — the goal is to make the shipped placeholder unreachable, not to guess which supplied thumbprints are real, which is not decidable from the string.

Upgrade path (deliberate break, same call as #1602)

ThumbprintList is Update requires: No interruption, so switching between the two forms never replaces the OIDC provider or changes its ARN, and no re-onboarding is needed.

  • CloudFormation stacks currently holding the placeholder will fail their next update until OIDCThumbprint is cleared or set to a real value. aws cloudformation deploy reuses previous parameter values for anything not in --parameter-overrides, so it will resubmit 0000… and the change set will be rejected with the ConstraintDescription. The remedy is one flag: --parameter-overrides "OIDCThumbprint=".
    This is deliberate. Such a stack is either on the primary path (where the value was never read, so clearing it changes nothing) or on the fallback path (where it cannot be authenticating today at all). Nothing that currently works regresses.
  • Terraform: thumbprint_list is Optional+Computed in the AWS provider, so clearing it does not clear a value already stored on an existing provider — Terraform keeps reading back what is there. Existing providers must be corrected out of band with aws iam update-open-id-connect-provider-thumbprint, or replaced. Documented in main.tf and known_issues/13_iac_aws_target.md.
  • Generated onboarding bundles are unaffected: aws-wif-cf-params.json.tmpl, aws-cfn-deploy.sh.tmpl and aws-wif.tfvars.tmpl never pass a thumbprint, so they pick up the new empty default. Unlike sec(iac/aws): require OIDC subject claim in aws-target CloudFormation #1602, this introduces no newly-required parameter.

Verification

  • Regression tests (internal/api/handler_federation_test.go, following the existing convention in that file of reading ../../iac/federation/... from disk). The AllowedPattern, MaxLength and Default are parsed back out of the committed template rather than retyped, then evaluated the way CloudFormation evaluates them (anchored full match and the length cap), over 8 must-accept and 12 must-reject inputs — including the non-zero digit in first, middle and last position, and as both an upper- and lower-case hex letter. A second test asserts the resource wiring and that the placeholder appears exactly once in the template, in the Rules assertion that rejects it and nowhere else.
    Both tests fail against the pre-fix template (confirmed by checking out origin/main's copy and re-running) and pass after. Full package green: 2018 tests.
  • Terraform, exercised against the real binary: terraform validate succeeds; terraform fmt -check clean; terraform plan rejects ["0000…"] even for the accounts.google.com issuer that the old guard allowed, rejects a short value, and accepts both [] and a real thumbprint.
  • scripts/check-aws-iam-parity.sh: green. It compares IAM action lists across the CFN/TF/CLI onboarding flavors and has no thumbprint or trust-policy dimension, so there was nothing to extend for this change; the missing parity mechanism is sec(iac): onboarding paths drift - shell/CFN/ARM templates lack the guards their Terraform siblings enforce #1547.
  • cfn-lint: cannot lint this template, on main and on this branch alike. It bails at parse time with E0000 Unhashable type "{'Fn::Sub': '${OIDCIssuerHost}:aud'}" — the trust policy legitimately uses !Sub as a mapping key. Pre-existing and out of scope here; the template is instead parsed and asserted on by the Go tests above.
  • Full pre-commit on every commit, no --no-verify.

Out of scope

internal/iacfiles/templates/aws-wif-cli.sh.tmpl:33 hardcodes the same placeholder into aws iam create-open-id-connect-provider. That is the CLI bundle generator tracked by #1640, so it is left alone here and noted on that issue instead of widening this diff.

cristim added 3 commits July 29, 2026 11:26
…dFormation

`OIDCThumbprint` defaulted to the all-zeros placeholder and was validated only
as 40 hexadecimal characters, which that placeholder satisfies.

Current AWS behaviour makes that value wrong in both directions. AWS verifies
the JWKS endpoint's TLS certificate against its own library of trusted root
CAs and consults the configured thumbprint only when that certificate does not
chain to one of them, when AWS cannot retrieve it, or when the endpoint
requires TLS 1.3. For both issuers this template documents
(login.microsoftonline.com and accounts.google.com) the thumbprint is
therefore never read at all. On the fallback path the all-zeros string is not
the SHA-1 of any certificate, so it matches nothing and every
AssumeRoleWithWebIdentity call fails. It does not bypass chain verification --
it nulls out the only verification that path has, while reading like a
deliberate configuration.

`ThumbprintList` is optional on AWS::IAM::OIDCProvider: when omitted, IAM
retrieves and uses the issuer's real top intermediate CA thumbprint, which is
what the IAM console does by default. That is now the template's default.

- Default `OIDCThumbprint` to empty and omit `ThumbprintList` via
  `AWS::NoValue` when it is empty, so IAM retrieves the real thumbprint.
- Reject the placeholder in two independent layers: an `AllowedPattern`
  requiring at least one non-zero hex digit, and an unconditional `Rules`
  assertion naming the literal. Neither a regex simplification nor a
  submission path that skips one layer can readmit it alone.
- Replace the parameter documentation, which described the placeholder as safe
  for providers "where AWS auto-validates the chain".
- Add regression coverage that reads the `AllowedPattern`, `MaxLength` and
  `Default` back out of the committed template and evaluates them the way
  CloudFormation does. Both tests fail against the pre-fix template.

`ThumbprintList` updates with no interruption, so existing stacks switch
between the two forms without replacing the OIDC provider or changing its ARN.
Stacks currently holding the placeholder will fail their next update until the
parameter is cleared or set to a real thumbprint; that is deliberate, and such
a stack cannot be authenticating on the fallback path today in any case.

Closes #1615
…haviour

The `thumbprint_list` guard rejected the all-zeros placeholder for issuers
other than `login.microsoftonline.com` and `accounts.google.com`, justified in
the code as "the all-zeros value bypasses the CA-chain check entirely". That
justification does not match how AWS handles thumbprints, and the CloudFormation
sibling now encodes the opposite rule, so the two would otherwise contradict
each other on the same defect.

AWS verifies the JWKS endpoint's TLS certificate against its own library of
trusted root CAs and falls back to the configured thumbprints only when that
certificate does not chain to one of them, when AWS cannot retrieve it, or when
the endpoint requires TLS 1.3. So all-zeros bypasses nothing: on the primary
path AWS never reads it, and on the fallback path it is not the SHA-1 of any
certificate and matches nothing, failing role assumption outright. The issuer
allowlist was beside the point in both directions -- the thumbprint is unread
for every publicly-trusted issuer, not only those two, and the placeholder
breaks any private-CA issuer regardless of which one it is.

- Default `thumbprint_list` to `[]` and pass `null` when empty, so IAM
  retrieves and uses the issuer's real top intermediate CA thumbprint.
- Reject the all-zeros entry unconditionally instead of per-issuer, and drop
  the non-empty-list validation, since empty is now the correct default.
- Record the finding, the corrected reasoning and the upgrade path in
  `known_issues/13_iac_aws_target.md`, and mark the superseded entry.

Verified with `terraform validate` plus `terraform plan` over four inputs: the
all-zeros list is rejected even for `accounts.google.com` (previously allowed),
a short value is rejected, and both an empty list and a real thumbprint are
accepted.

Refs #1615
…guard test

Review follow-up to the two commits above, no behaviour change.

The parameter and variable documentation said the thumbprint is "never
consulted" for the two documented issuers. AWS also falls back to thumbprint
verification when it cannot retrieve the TLS certificate or when the endpoint
requires TLS 1.3, and neither of those is issuer-specific, so the value can be
read even for a publicly trusted issuer. Reworded to say it is read only when
one of the listed fallback conditions applies.

Also assert that the all-zeros placeholder appears exactly once in the
template, so it can live in the Rules assertion that rejects it and nowhere
else; a `Contains` check would have stayed green if it were reintroduced as a
default alongside the assertion.

Refs #1615
@cristim cristim added triaged Item has been triaged priority/p0 Drop everything; same-day fix severity/critical Major harm when it happens urgency/now Drop other things impact/all-users Affects every user effort/s Hours type/security Security finding labels Jul 29, 2026
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 41 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 53a74fa5-c01c-44d7-a1a7-66f5b526e909

📥 Commits

Reviewing files that changed from the base of the PR and between 6ded401 and 6d93bef.

📒 Files selected for processing (5)
  • iac/federation/aws-target/cloudformation/template.yaml
  • iac/federation/aws-target/terraform/main.tf
  • iac/federation/aws-target/terraform/variables.tf
  • internal/api/handler_federation_test.go
  • known_issues/13_iac_aws_target.md

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/s Hours impact/all-users Affects every user priority/p0 Drop everything; same-day fix severity/critical Major harm when it happens triaged Item has been triaged type/security Security finding urgency/now Drop other things

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sec(iac/aws): aws-target CloudFormation accepts the all-zeros OIDC thumbprint for any issuer

1 participant