GCP Cloud Account
Connect a Google Cloud project so DriftWise can scan live infrastructure and detect drift against your Terraform state.
Prerequisites
- gcloud CLI installed and authenticated
- A GCP project with permission to manage IAM
- A DriftWise API key or OIDC login
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) — grantscloudasset.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"
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
Service account keys are long-lived credentials. Use Workload Identity for production.
Option B: Workload Identity (recommended for production)
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>"
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:
- Open Cloud Scan in the left sidebar.
- Click + Add Account and select Google Cloud Platform.
- 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 Type —
Workload Identity(recommended) orService 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 theechocommand above. This is the SA in your project that DriftWise impersonates — not DriftWise's own backend SA.
- Target Service Account Email — the scanner SA you created in step 3:
- Service Account Key only:
- Service Account JSON — the full text of the downloaded
driftwise-sa-key.jsonfile.
- Service Account JSON — the full text of the downloaded
- Display Name — any label you'll recognize (e.g.
- 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_refis a JSON-encoded object withproject_idandservice_account_json. Theservice_account_jsonvalue is the raw text of the downloaded key file — not base64. Becausecredential_refis 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_refis a JSON-encoded object withproject_id,target_service_account(the SA email; no key material), and_credential_type: "gcp_workload_id"(see note below).
_credential_type field inside credential_refToday 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 thetarget_service_account— not the federating principal.
Required role:
roles/storage.objectVieweron 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"
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_regionsin the scan request body, verify those regions actually contain resources. Matching is prefix-based: a zone likeus-central1-amatches afilter_regionsentry ofus-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:
SELECT id, status, retry_count, started_at FROM scan_runs
WHERE status = 'running' AND started_at < NOW() - INTERVAL '10 minutes';