Skip to main content

GCP Cloud Account

Connect a Google Cloud project so DriftWise can scan live infrastructure and detect drift against your Terraform state.

Prerequisites

1. Enable the API and create the scanner service account

DriftWise discovers GCP resources via Cloud Asset Inventory. Enable the API and create the scanner service account — both credential types (SA key and Workload Identity) use this same SA:

export GCP_PROJECT=your-project-id
export SA_EMAIL="driftwise-scanner@${GCP_PROJECT}.iam.gserviceaccount.com"

gcloud services enable cloudasset.googleapis.com --project=$GCP_PROJECT

gcloud iam service-accounts create driftwise-scanner \
--project=$GCP_PROJECT \
--display-name="DriftWise Scanner"

See Cloud Discovery for the full discovery model, including how resources are categorized.

2. Grant IAM Permissions

DriftWise needs one role on the scanner service account:

  • Cloud Asset Viewer (roles/cloudasset.viewer) — grants cloudasset.assets.searchAllResources, the only permission the scanner needs. Cloud Asset Inventory projects data from across services, so DriftWise does not need per-service reader roles (compute.viewer, storage.objectViewer, etc.) for discovery. The credential validation probe also uses this role — no additional role is required.
gcloud projects add-iam-policy-binding $GCP_PROJECT \
--member="serviceAccount:$SA_EMAIL" \
--role="roles/cloudasset.viewer"
Scope

You can grant cloudasset.viewer at project, folder, or organization level. A broader scope lets one DriftWise account discover resources across multiple projects; a narrower scope keeps visibility scoped to a single project.

3. Choose a Credential Type

Option A: Service Account Key (simple, for dev/test)

Download a JSON key for the scanner SA created in step 1:

gcloud iam service-accounts keys create driftwise-sa-key.json \
--iam-account=$SA_EMAIL
warning

Service account keys are long-lived credentials. Use Workload Identity for production.

Self-service setup — copy from the UI

Open Cloud Scan → + Add Account → GCP in DriftWise. The "Setup Instructions" panel renders the exact gcloud add-iam-policy-binding command with your org UUID and DriftWise's WIF principal already filled in — copy from there instead of editing this template by hand. The block below is reference; the UI is canonical.

Avoid managing service-account keys by authorizing DriftWise's per-org WIF principal to impersonate the scanner SA created in step 1. You grant impersonation permission directly — no OIDC provider or trust policy to configure on your side, and the grant is scoped to your specific DriftWise org via the principal's subject/org-<UUID> segment.

# Replace <YOUR-ORG-UUID> + <DRIFTWISE-PROJECT-NUMBER> with values from the
# UI's Setup Instructions panel.
gcloud iam service-accounts add-iam-policy-binding $SA_EMAIL \
--project=$GCP_PROJECT \
--role="roles/iam.serviceAccountTokenCreator" \
--member="principal://iam.googleapis.com/projects/<DRIFTWISE-PROJECT-NUMBER>/locations/global/workloadIdentityPools/driftwise-federation/subject/org-<YOUR-ORG-UUID>"
Cross-project impersonation

DriftWise runs in its own Google Cloud project, not in yours. The binding above grants DriftWise's per-org WIF principal permission to mint short-lived access tokens for your scanner SA via iamcredentials.googleapis.com. Your SA still holds the scan permissions — DriftWise simply borrows its identity for the duration of a scan, and only when the JWT identifying your org is present.

4. Add the account in DriftWise

Via the UI

Echo the scanner SA email so you can paste it into the form:

echo "driftwise-scanner@${GCP_PROJECT}.iam.gserviceaccount.com"

Then:

  1. Open Cloud Scan in the left sidebar.
  2. Click + Add Account and select Google Cloud Platform.
  3. Fill in the form:
    • Display Name — any label you'll recognize (e.g. Production GCP).
    • Project ID — the GCP project ID you scanned (e.g. my-project-123). Must match the project in your IAM grants above.
    • Default Region — optional; used as default when starting scans.
    • Credential TypeWorkload Identity (recommended) or Service Account Key.
    • Workload Identity only:
      • Target Service Account Email — the scanner SA you created in step 3: driftwise-scanner@<YOUR_PROJECT>.iam.gserviceaccount.com. Paste the output of the echo command above. This is the SA in your project that DriftWise impersonates — not DriftWise's own backend SA.
    • Service Account Key only:
      • Service Account JSON — the full text of the downloaded driftwise-sa-key.json file.
  4. Click Register Account. The backend validates against GCP before saving — a bad SA email or missing impersonation grant fails here, not at first scan.

Via the API

Call POST /orgs/:id/accounts with provider: "gcp" and one of two credential types:

  • credential_type: "gcp_sa_json"credential_ref is a JSON-encoded object with project_id and service_account_json. The service_account_json value is the raw text of the downloaded key file — not base64. Because credential_ref is itself a JSON string, every " in the key file must be escaped to \" and every newline in the private key must stay as the literal two-character sequence \n.
  • credential_type: "gcp_workload_id"credential_ref is a JSON-encoded object with project_id, target_service_account (the SA email; no key material), and _credential_type: "gcp_workload_id" (see note below).
The _credential_type field inside credential_ref

Today the server parses credential_ref independently of the top-level credential_type field, so OIDC / Workload Identity credentials must also include _credential_type inside the inner JSON to trigger the federated code path. Without it, the request is treated as a service account key with an empty key and falls through to Application Default Credentials — the target_service_account is silently ignored. This duplication will be removed once the backend merges the fields at request time.

A shell one-liner to build the Service Account Key body correctly:

SA_JSON=$(jq -c '.' driftwise-sa-key.json)
CREDENTIAL_REF=$(jq -cn --arg sa "$SA_JSON" \
'{project_id:"your-project-id", service_account_json:$sa}')

Credentials are validated against GCP before save. The provider-reported project ID must match external_account_id. See the accounts tag of the API reference for the full request and response shapes.

Supported Resources

DriftWise discovers every resource Cloud Asset Inventory returns for the project (or folder / organization, depending on where you granted cloudasset.viewer). There is no per-type allowlist. Resources are normalized into eight broad categories (compute, storage, database, network, iam, serverless, messaging, other) regardless of provider. See Cloud Discovery for the full list with examples.

The underlying GCP asset type (for example compute.googleapis.com/Instance) is preserved alongside the category for exact-match drift detection.

Because Cloud Asset Inventory returns full resource configuration in one call, GCP scans have no separate enrichment phase — every row is stored with enrichment_status = n/a.

Reading Terraform state from GCS

When you link a GCP cloud account to a GCS state source, DriftWise uses the linked account's principal to read the state object. The principal needs read access on the state bucket.

  • For service-account key credentials (gcp_sa_json), grant the role on the service account named in the key.
  • For workload identity federation (gcp_workload_id), grant the role on the target_service_account — not the federating principal.

Required role:

  • roles/storage.objectViewer on the state bucket (or at object scope)

Grant at bucket scope rather than project-wide. DriftWise reads one object per refresh and does not enumerate buckets. The grant is read-only; DriftWise never writes or locks state.

If you prefer strict separation, register a second cloud account whose target SA has only the bucket-read role, then link that account to the state source instead of the scanner account. The cloud_account_id field on the state source chooses which credentials are used for the fetch — no new feature required.

CMEK-encrypted buckets additionally need the target SA to have roles/cloudkms.cryptoKeyDecrypter on the CMEK key. GCS's server-side managed keys (the default) require no extra grants.

Troubleshooting

Scan fails with cloudasset.googleapis.com has not been used

The Cloud Asset API is not enabled on the project. Enable it:

gcloud services enable cloudasset.googleapis.com --project=$GCP_PROJECT

Allow a minute for the API enablement to propagate, then re-run the scan.

Scan fails with Permission 'cloudasset.assets.searchAllResources' denied

The service account is missing the cloudasset.viewer role (or a role that grants the permission). Grant it:

gcloud projects add-iam-policy-binding $GCP_PROJECT \
--member="serviceAccount:$SA_EMAIL" \
--role="roles/cloudasset.viewer"
info

After granting IAM roles, allow up to 60 seconds for propagation before re-running the scan.

Scan completes with 0 resources and 0 errors

The credentials are valid and Cloud Asset Inventory responded, but no resources matched. Common causes:

  • Empty project — verify manually: gcloud asset search-all-resources --scope=projects/$GCP_PROJECT
  • Propagation delay — Cloud Asset Inventory takes up to several minutes to reflect newly-created resources. Re-run the scan shortly after.
  • Region filter too narrow — if you passed filter_regions in the scan request body, verify those regions actually contain resources. Matching is prefix-based: a zone like us-central1-a matches a filter_regions entry of us-central1.

Scan stuck in "pending"

The scan worker hasn't claimed it. Check the worker logs:

kubectl logs -f deploy/scan-worker

If the worker is running but scans stay pending, check for stuck scans:

Diagnostic query — internal schema, column names may change
SELECT id, status, retry_count, started_at FROM scan_runs
WHERE status = 'running' AND started_at < NOW() - INTERVAL '10 minutes';