feat(gcp): switch GCP cloud connector to OIDC + WIF (matches Nullify backend)#42
Merged
Conversation
…backend) The Nullify backend mints OIDC JWTs (subject_token_type=urn:ietf:params:oauth:token-type:jwt, signed by the platform oidc-gcp Lambda's RSA key in SSM, with `tenant_id` as a custom claim) and exchanges them via Google STS. The customer-facing terraform module was creating an AWS-typed WIF provider, which Google STS rejects on the subject token type — every customer install failed at Verify. This PR rewrites the WIF provider to OIDC, using `nullify_oidc_issuer_uri` and `nullify_tenant_id` as the new required inputs. The pool's `attribute_condition` pins trust to the customer's specific Nullify tenant id, so even if Nullify's signing key were stolen, an attacker could not mint a token accepted by another tenant's provider. The IAM binding moves from `attribute.aws_role/...` to `attribute.tenant_id/...` for the same reason. Other changes: - New `apis.tf` enables prerequisite Google Cloud APIs (iam, iamcredentials, sts, cloudresourcemanager, cloudasset, serviceusage) on the host project so the first `terraform apply` against a fresh project doesn't 403 at pool-creation time. `disable_on_destroy = false` so we don't break unrelated resources on `terraform destroy`. - Removes `roles/viewer` from the granted roles. Per the internal architecture doc, it grants data-plane reads (compute.instances.getSerialPortOutput, cloudbuild.builds.get) that leak secrets; granular per-service viewer roles + roles/cloudasset.viewer cover the same surface without those. - README: drops the stale `tenant_external_id` reference, adds Prerequisites (installer IAM roles + APIs) and Troubleshooting sections. - gcloud install.sh: switches to `create-oidc`, enables APIs up front, drops AWS env vars in favour of NULLIFY_OIDC_ISSUER_URI + NULLIFY_TENANT_ID. - host_project_id now validated against the GCP project-ID regex. Verified locally: - terraform fmt -check / terraform validate clean for the module + every example dir - shellcheck clean for install.sh + uninstall.sh Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- main.tf: drop stale "Nullify AWS principal" section header; WIF provider now says "OIDC Provider trusting Nullify's OIDC issuer" matching the code below. - main.tf: fix broken reference to non-existent `modules/nullify-gcp-integration/README.md` — point at the real file `../../docs/permissions.md` instead. - install.sh: wrap the first IAM call after `gcloud services enable` in a 5-attempt retry with 10s backoff. The IAM API's "enabled" state can take 10-30s to propagate on a fresh host project, and the previous straight-through invocation would fail with a cryptic 403 on the first install for customers. - README.md: add a Prerequisites bullet clarifying that `scope = "projects"` requires `roles/resourcemanager.projectIamAdmin` on EVERY project in `project_ids`, not just `host_project_id`. Without this the predefined-role bindings fail on each sibling project. All three are nits individually but each is the kind of thing that derails a live customer install demo. Verified: - `terraform fmt -check -recursive` clean - `terraform validate` clean on module + every example - `shellcheck scripts/install.sh` clean Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
vik-nullify
approved these changes
Apr 19, 2026
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.
Summary
The Nullify backend mints OIDC JWTs and exchanges them via Google STS as
subject_token_type=urn:ietf:params:oauth:token-type:jwt. The customer-facing terraform module was creating an AWS-typed WIF provider (aws { account_id = ... }block,attribute_mappingkeyed onassertion.arn/assertion.account). Google STS rejects the JWT against an AWS provider on the subject-token-type check — so today every customer install fails at Verify.This PR rewrites the WIF provider to OIDC, with
nullify_oidc_issuer_uriandnullify_tenant_idas the two new required inputs (replacingnullify_aws_principal_arnandnullify_aws_account_id). The pool'sattribute_conditionpins trust to the customer's specific Nullify tenant id, so even if Nullify's signing key were stolen, an attacker could not mint a token accepted by another tenant's provider.Changes
modules/nullify-gcp-integration/main.tf:aws { account_id = var.nullify_aws_account_id }withoidc { issuer_uri = var.nullify_oidc_issuer_uri }.attribute_mappingto mapgoogle.subject = assertion.subandattribute.tenant_id = assertion.tenant_id.attribute_conditionwithassertion.tenant_id == \"${var.nullify_tenant_id}\".principalSetto bind byattribute.tenant_id/${var.nullify_tenant_id}instead ofattribute.aws_role/....local.nullify_aws_role_name.roles/viewerfromlocal.predefined_viewer_roles(per the internal architecture doc — it grants data-plane reads likecompute.instances.getSerialPortOutputandcloudbuild.builds.getthat leak secrets; the granular per-service viewers +roles/cloudasset.viewercover the same surface without those).modules/nullify-gcp-integration/apis.tfenables prerequisite APIs on the host project (iam,iamcredentials,sts,cloudresourcemanager,cloudasset,serviceusage) so firstterraform applyagainst a fresh project doesn't 403 at pool-creation time.disable_on_destroy = falseso unrelated resources aren't broken onterraform destroy. WIF pool / provider / SA alldepends_onthe API enables.modules/nullify-gcp-integration/variables.tfandterraform/variables.tf:nullify_aws_principal_arn,nullify_aws_account_id.nullify_oidc_issuer_uri(validation:https://...no trailing slash) andnullify_tenant_id(validation: 1-100 chars[A-Za-z0-9_-]).host_project_idagainst the GCP project-ID regex.modules/nullify-gcp-integration/outputs.tf: updateworkload_identity_providerreference (nullify_aws→nullify_oidc).terraform.tfvars.example,examples/{organization,folder,single-project}/main.tf: drop AWS vars, add OIDC vars.terraform/README.md: rewrite — drop staletenant_external_idrow, add Prerequisites (installer IAM roles + APIs) and Troubleshooting sections (covering org-policy WIF allowlist, missingroles/iam.workloadIdentityPoolAdmin, issuer-URL mismatch, tenant-id mismatch, per-project PERMISSION_DENIED,iam.disableServiceAccountCreation).scripts/install.sh: switch togcloud iam workload-identity-pools providers create-oidcwith the new attribute mapping/condition; enable APIs up front; replaceNULLIFY_AWS_PRINCIPAL_ARN/NULLIFY_AWS_ACCOUNT_IDenv vars withNULLIFY_OIDC_ISSUER_URI/NULLIFY_TENANT_ID; updateprincipalSetto bind by tenant id; removeroles/viewerfrom the granted roles.scripts/uninstall.sh: same role-list trim, defaultPROVIDER_IDupdated tonullify-oidc.docs/permissions.md: update Trust model section (OIDC instead of AWS), droproles/viewerrow.Breaking changes
This rewrites the input contract for the module. Per an end-to-end audit, no customer can have completed onboarding on the AWS-WIF version (Google STS rejects every Verify), so there is no rollback population. Any sandbox installs need to
terraform destroyand re-apply with the new variables.Test plan
terraform fmt -check -recursivecleanterraform validateclean for the root module + every example dirshellcheckclean forinstall.sh+uninstall.shnullify_oidc_issuer_uri = \"https://gcp.dev.nullify.ai\", real tenant id), paste outputs into staging UI, click Verify → expect green per projectPOST /context/cloud-integration/gcp/scan/startand confirmaws s3 lsshows per-servicelatest.jsonfor compute, iam, vpcterraform destroycleanly removes all resourcesCoordination
gcp-v1.0.0after merge — the monorepo PR's UI trust-instructions block links to the cloud-connector repo at this tag.