Skip to content

sec(iac): setup-gcp-wif.sh creates WIF providers with no attribute condition and grants pool-wide SA impersonation #1544

Description

@cristim

Reviewed commit: be11bdcb5. Note: origin/main moved to 3e9660d06 during the review; re-verify against current main before changing code.

Severity: CRITICAL. This template is deployed by customers into their own cloud accounts, so the blast radius is their infrastructure, not ours.

Two defects in one script, filed together because they share a fix session and a root cause: the shell path lacks the guard its Terraform sibling enforces.

Defect A: AWS mode creates the WIF provider with no attribute condition, then grants pool-wide impersonation

Where: arm/CUDly-CrossSubscription/setup-gcp-wif.sh:99-103 (provider create) and :133-139 (IAM binding)

What: the --provider-type aws branch calls gcloud iam workload-identity-pools providers create-aws with only --account-id, and never passes --attribute-condition. The script then binds roles/iam.workloadIdentityUser to principalSet://iam.googleapis.com/projects/<n>/locations/global/workloadIdentityPools/<pool>/*, i.e. every identity in the pool.

The Terraform sibling iac/federation/gcp-target/terraform/main.tf:137-147 carries an explicit precondition forbidding exactly this state, with the error text: "Without it the attribute_condition is null and any IAM role in the AWS account can federate as the GCP service account."

Failure scenario: a customer runs the documented AWS-mode command. Any IAM principal in the referenced AWS account (a developer's sandbox role, a CI runner, an EC2 instance profile on a compromised host, anything that can call sts:GetCallerIdentity) exchanges its AWS credentials at sts.googleapis.com and impersonates the customer's GCP service account, which holds compute.commitments.create/update (spend authority) or, in the impersonation bundle, commerceorgpolicy.commitmentAdmin. No CUDly involvement is required.

Fix: require a --aws-role-name argument in AWS mode and emit --attribute-condition="attribute.aws_role.contains('assumed-role/<role>/')", and narrow the binding to principalSet://.../attribute.aws_role/..., matching the Terraform module's mandatory precondition.

Defect B: OIDC mode warns instead of failing, and the documented usage omits the guard

Where: arm/CUDly-CrossSubscription/setup-gcp-wif.sh:110-126; documented usage example at :20-27

What: in OIDC mode, when --subject-condition is empty the script prints a WARNING to stderr and proceeds to create the provider with no --attribute-condition (:123-125 only appends the flag when the variable is non-empty). The header's copy-paste example uses --issuer-uri https://token.actions.githubusercontent.com and does not include --subject-condition.

Failure scenario: a customer copy-pastes the documented OIDC command verbatim. Because token.actions.githubusercontent.com is a globally shared public issuer, any GitHub Actions workflow in any repository on GitHub can request an OIDC token, exchange it through the customer's pool, and impersonate the customer's CUDly service account with commitment-purchase rights. A stderr warning on a set -euo pipefail script that still succeeds is not a control.

Fix: make --subject-condition mandatory for --provider-type oidc (exit 1 like the other required-arg checks at :59-74) and fix the header example to include it.

Related

Part of the onboarding-path drift class filed separately: the Terraform path is hardened and the shell/CFN/ARM path for the identical onboarding job is not.

Activity

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