Skip to content

sec(iac): served GCP WIF script swallows provider-create failure + unvalidated CEL subject; OIDC cred-config bug #1661

Description

@cristim

Found while fixing #1544 (PR #1651). These are in the served GCP onboarding script and the ARM script's OIDC branch, and are out of that PR's scope.

Scope note: this issue originally also covered the Terraform module's pool-wide grant. That turned out to be a live bypass rather than a hardening gap and has been split into its own p0, #1667. This issue is now only the two items below.

1. Served onboarding script swallows provider-create failures

internal/iacfiles/templates/gcp-wif-cli.sh.tmpl:50-57 - the script the federation API hands to customers.

gcloud iam workload-identity-pools providers create-oidc "${PROVIDER_ID}" \
  ... --attribute-condition="assertion.sub == '${CUDLY_FEDERATED_SUBJECT}'" \
  || echo "  (provider may already exist)"

The template is not vulnerable to either #1544 defect: it always passes --attribute-condition and binds principal://.../subject/..., and its AWS-STS path was deliberately removed (asserted by templates_test.go's mustNot: "create-aws"). But || echo swallows every failure of a security-critical create under set -euo pipefail. If a provider of that name already exists with a weaker condition, or none at all, the script continues, grants the binding, and prints "=== Done ===". Same shape on the pool creation at :43-47.

This is the exact gap closed on the ARM script in PR #1651 (commit 21af9cc): on create failure, describe the existing provider and compare its attributeCondition to the expected string, dying on mismatch rather than reusing it.

Lower severity than #1544 because the issuer is CUDly's own OIDC deployment rather than a public shared issuer, and the subject is the fixed cudly-controller.

2. CUDLY_FEDERATED_SUBJECT is interpolated into CEL with no validation

internal/iacfiles/templates/gcp-wif-cli.sh.tmpl:31:

CUDLY_FEDERATED_SUBJECT="${CUDLY_FEDERATED_SUBJECT:-cudly-controller}"

It is environment-overridable and lands inside a CEL string literal at :55. A value containing ' closes the literal: CUDLY_FEDERATED_SUBJECT="x' || true || '" renders assertion.sub == 'x' || true || '', an always-true condition admitting every subject the CUDly issuer will sign. It also flows into the principal://.../subject/ identifier at :75.

Self-inflicted (the operator sets it on their own machine), so severity is low, but it is the same break-out validate_principal_value() was added to stop in PR #1651, and the same class as #1667(c). Apply the same character rejection: ', ", \, backtick, *, $.

3. create-cred-config called with no credential source in OIDC mode

arm/CUDly-CrossSubscription/setup-gcp-wif.sh:275-278 (pre-existing; unchanged by #1651).

Google's reference states exactly one of --aws, --azure, --credential-source-file, --credential-source-url, --executable-command, --credential-cert-path must be given. The AWS branch passes --aws; the OIDC branch passes none, so gcloud should reject the invocation and set -e aborts after every mutation has been made - the customer ends up with a configured pool and no credential config.

This suggests OIDC mode has never been run end to end. Not verified against a live gcloud (no GCP credentials in the fix session), so confirm before acting: either forward a credential source for OIDC, or drop OIDC mode from that script if the served template supersedes it.

Activity

  1. changed the title [-]sec(iac): GCP WIF pool-wide grant in Terraform path + silent provider-create failure in served script[/-] [+]sec(iac): served GCP WIF script swallows provider-create failure + unvalidated CEL subject; OIDC cred-config bug[/+] on Jul 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions