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 Cloud Asset API

DriftWise discovers GCP resources via Cloud Asset Inventory. Enable the API in the target project:

export GCP_PROJECT=your-project-id

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

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

2. Grant IAM Permissions

DriftWise needs two roles on the scanner service account:

  1. Cloud Asset Viewer (roles/cloudasset.viewer) — grants cloudasset.assets.searchAllResources, the only permission the scanner itself uses. Cloud Asset Inventory projects data from across services, so DriftWise does not need per-service reader roles (compute.viewer, storage.objectViewer, etc.) for discovery.
  2. Browser (roles/browser) — grants resourcemanager.projects.get and compute.projects.get, used by DriftWise's one-time credential validation check when you register the account. Without it, POST /accounts fails with "permission denied for project" even though the scanner itself would have worked.
gcloud projects add-iam-policy-binding $GCP_PROJECT \
--member="serviceAccount:<SA_EMAIL>" \
--role="roles/cloudasset.viewer"

gcloud projects add-iam-policy-binding $GCP_PROJECT \
--member="serviceAccount:<SA_EMAIL>" \
--role="roles/browser"
Why two roles

The validation probe calls Compute's projects.get API, which cloudasset.viewer does not grant. We intend to switch the probe onto Cloud Asset Inventory so only cloudasset.viewer is required — until then, the roles/browser grant closes the gap.

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. roles/browser should be granted at project scope.

3. Choose a Credential Type

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

Create a service account and download its JSON key:

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

gcloud projects add-iam-policy-binding $GCP_PROJECT \
--member="serviceAccount:driftwise-scanner@${GCP_PROJECT}.iam.gserviceaccount.com" \
--role="roles/cloudasset.viewer"

gcloud iam service-accounts keys create driftwise-sa-key.json \
--iam-account="driftwise-scanner@${GCP_PROJECT}.iam.gserviceaccount.com"
warning

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

The commands below use the production app.driftwise.ai backend service account. For self-hosted or non-production deployments, fetch the equivalent from GET /api/v2/federation-info — see OIDC Federation.

Avoid managing service-account keys by letting DriftWise's backend service account impersonate a service account in your project. Unlike AWS and Azure, GCP federation does not use iss/sub/aud claims — you grant impersonation directly.

# Create the target service account in your project
gcloud iam service-accounts create driftwise-scanner \
--project=$GCP_PROJECT \
--display-name="DriftWise Scanner"

# Grant Cloud Asset Viewer on the target project
gcloud projects add-iam-policy-binding $GCP_PROJECT \
--member="serviceAccount:driftwise-scanner@${GCP_PROJECT}.iam.gserviceaccount.com" \
--role="roles/cloudasset.viewer"

# Allow DriftWise's backend service account to impersonate your scanner SA
gcloud iam service-accounts add-iam-policy-binding \
driftwise-scanner@${GCP_PROJECT}.iam.gserviceaccount.com \
--project=$GCP_PROJECT \
--role="roles/iam.serviceAccountTokenCreator" \
--member="serviceAccount:[email protected]"
Cross-project impersonation

DriftWise runs in its own Google Cloud project, not in yours. The binding above grants DriftWise's backend SA 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.

4. Add the account in DriftWise

Via the UI

  1. Open Cloud Scan in the left sidebar.
  2. Click + Add Account and select Google Cloud Platform.
  3. Enter your GCP Project ID (e.g., my-project-123).
  4. Select your credential type and fill in the fields.
  5. Click Save.

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';