Connecting your GCP project(s)
This is the complete, do-it-once setup to let the platform scan your Google Cloud for misconfigurations and compliance gaps. It is agentless and read-only: you never give us a human password or console login — you create a read-only service account in your own project, grant it Viewer + Security Reviewer, and hand us its key. By IAM, that identity can read every resource's configuration and security posture, change nothing, and read none of your data.
Unlike AWS — where you create a role we assume cross-account and we store no secret at all — Google Cloud needs an actual credential: a service-account key (JSON). You paste it into the console once. On our side it is write-only: stored encrypted in AWS Secrets Manager under a dedicated KMS key, readable only by an isolated credential broker (a Lambda) that, for each scan, mints a short-lived (~1 hour) read-only access token. The durable key never reaches a scanner or worker, is never returned by any API, and is never shown again after you paste it.
:::tip Genuinely one-and-done A GCP service-account key does not carry a forced expiry (unlike Azure's client secret, which you must rotate). Connect once and it keeps working — no calendar reminder, no re-paste. :::
You have two paths:
- Path A — Terraform (
main.tf). One project, or a whole folder/organization. ~5 minutes. - Path B — gcloud CLI — the same three actions by hand.
Both end the same way: paste the key back, validate access, then run your first scan.
:::info What we can and cannot see
The service account holds only Viewer (roles/viewer) and Security
Reviewer (roles/iam.securityReviewer). That is configuration and security
posture — resource settings, IAM policies, metadata. No write access. No
reading your data (no object contents from Cloud Storage, no database rows) — only
how things are configured. Nothing beyond those two roles is granted.
:::
Before you start
From the platform (your console → Cloud → Connect GCP account), click Add account. This time there's nothing to copy out first — GCP is credential- based, so the flow is: you create the read-only service account below, then paste its key JSON back in.
| Value | What it is |
|---|---|
| Service-account key (JSON) | The credential the service account authenticates with. You create it below and paste it into the console once. We store it write-only (encrypted, broker-only); it is never shown again. |
| Project id (optional) | A label / scope hint you can attach as account_id. Optional — scope is actually controlled by where you granted the two roles (one project vs. a folder/org), not by this field. |
Behind the scenes this is the API call POST /org/gcp/accounts (your UI does it
for you); the account is created in state pending until you paste the key and
validate.
Create the read-only service account
The goal of both paths is identical: one service account named
set-cspm-scanner, granted roles/viewer + roles/iam.securityReviewer,
plus a key to hand us.
Path A — Terraform
Our onboarding template is main.tf. It creates the service account, grants the
two read-only roles, and outputs the key JSON. The platform links you a copy, or
copy it here:
main.tf
terraform { required_providers { google = { source = "hashicorp/google", version = "~> 5.0" } } }
variable "project_id" { type = string }
provider "google" { project = var.project_id }
resource "google_service_account" "set_scanner" {
account_id = "set-cspm-scanner"; display_name = "S.E.T system (read-only CSPM)"; project = var.project_id
}
resource "google_project_iam_member" "viewer" {
project = var.project_id; role = "roles/viewer"; member = "serviceAccount:${google_service_account.set_scanner.email}"
}
resource "google_project_iam_member" "security_reviewer" {
project = var.project_id; role = "roles/iam.securityReviewer"; member = "serviceAccount:${google_service_account.set_scanner.email}"
}
resource "google_service_account_key" "set_scanner" { service_account_id = google_service_account.set_scanner.name }
output "service_account_key_json" { value = base64decode(google_service_account_key.set_scanner.private_key); sensitive = true }
Deploy it (single project):
terraform init
terraform apply -var project_id=YOUR_PROJECT_ID
# then read the key JSON back out (it's marked sensitive, so ask for it explicitly):
terraform output -raw service_account_key_json
Copy that JSON — you'll paste it into the console in the next step.
:::note Many projects? Grant the roles higher up.
The template above grants the two roles at the project level. To scan every
project with a single service account, grant them at the folder or
organization level instead — swap the two google_project_iam_member resources
for google_folder_iam_member (with a folder id) or
google_organization_iam_member (with an org_id). One service account, one
key, and the scan covers every project beneath that folder/org.
:::
Path B — gcloud CLI
The same three actions by hand, in the project you want scanned:
# 1. create the read-only service account
gcloud iam service-accounts create set-cspm-scanner \
--display-name="S.E.T system (read-only CSPM)"
# 2. grant the two read-only roles
gcloud projects add-iam-policy-binding PROJECT \
--member="serviceAccount:set-cspm-scanner@PROJECT.iam.gserviceaccount.com" \
--role="roles/viewer"
gcloud projects add-iam-policy-binding PROJECT \
--member="serviceAccount:set-cspm-scanner@PROJECT.iam.gserviceaccount.com" \
--role="roles/iam.securityReviewer"
# 3. create and download the key
gcloud iam service-accounts keys create key.json \
--iam-account=set-cspm-scanner@PROJECT.iam.gserviceaccount.com
key.json is the credential you'll paste back. (For multi-project coverage, use
gcloud resource-manager folders add-iam-policy-binding /
gcloud organizations add-iam-policy-binding with the same two roles instead of the
project bindings.)
Paste the key back
In the platform, open the account you just added and paste the service-account key
JSON into the field, optionally setting a project id as account_id, then
save. That's the whole connection.
Behind the scenes:
POST /org/gcp/accounts
{
"provider": "gcp",
"account_id": "<project id or label>",
"gcp_service_account_key": { ...the key JSON... }
}
Remember the key is write-only from here on — it goes straight to encrypted storage and is never returned or displayed again. Project scoping is optional: the scan covers every project the service account's roles can see, which is decided by where you granted the roles (one project, or a whole folder/org).
Now validate.
Validate access
Before any scan, confirm the key works. In the platform click Validate on the
account (API: POST /org/gcp/accounts/{id}/validate). This triggers a live token
mint through the credential broker — we exercise the real path a scan uses — and
report back:
validated✓ — the broker minted a short-lived token; the account is ready to scan.invalid✗ — with the reason (bad or rotated/deleted key, or the two roles aren't granted). Fix and re-validate.
Check overall readiness any time with the status endpoint
(GET /org/gcp/status → { enabled, validated_accounts, can_run }).
Run your first scan
Once at least one account is validated:
- Platform: Cloud → Run scan.
- API:
POST /org/scans { "module": "gcp" }.
The scan runs one read-only pass and returns when done. It is gated on a validated
account (you'll get a 409 if none is validated yet), and the normal
one-scan-per-14-days cooldown applies (independent of AWS/Azure and of
EASM/DAST).
When it finishes you'll have:
- Findings — the failures, grouped by issue with the affected resources listed and a "5 of 20 remaining" rollup.
- Full posture — every pass and fail (
GET /org/gcp/results), filterable by service, severity, project. - Compliance — pick any framework (
GET /org/gcp/frameworkslists them) and get an overall score plus a control-by-control breakdown (GET /org/gcp/compliance?framework=CIS-GCP-2.0→ e.g. "overall score + per- control breakdown"). One scan reports against every framework — CIS GCP, ISO 27001, SOC 2, NIST, PCI, and more. The framework is a view, never a re-scan.
Fix, then prove it's fixed
Cloud findings use the same lifecycle as the rest of the platform, tracked per resource:
- Mark a finding In Progress / Resolved / Accepted Risk / False Positive.
- Fix it in GCP.
- Rescan. Resolution is confirmed by the scan, not by the click: a finding you marked Resolved closes when the next scan no longer sees it; a fix you didn't triage auto-closes after 2 consecutive clean scans. If a closed issue comes back it reopens (regression). Fix 15 of 20 affected resources and exactly those 15 close — the other 5 stay open.
A finding that keeps showing up across scans is flagged recurring so you can spot what isn't sticking.
Troubleshooting
| Symptom | Cause / fix |
|---|---|
Validate returns invalid (auth failed) | The key was rotated or deleted in GCP. Create a fresh key (gcloud iam service-accounts keys create … or terraform apply) and paste the new JSON back. |
Validate returns invalid: missing roles | The service account isn't granted both roles/viewer and roles/iam.securityReviewer. Re-grant both (project, or folder/org for multi-project) and re-validate. |
| Scan sees fewer projects than expected | The roles were granted at the project level. To cover more, grant them at the folder or organization level so the one service account sees every project beneath it. |
| Wrong project in scope | The account_id/project id is only a label — scope follows where the roles were granted. Adjust the role bindings, not the field. |
| "Can you see our data?" | No. Viewer + Security Reviewer only — configuration and security posture. No write access, no object/database contents. |
What we never expose
The scanning engine is white-labeled end to end. It isn't named in your findings
(engine name and links are scrubbed), and it isn't visible in your GCP audit
logs: activity is attributed to your own service account by its name
(set-cspm-scanner), not to any underlying tool. On findings, the evidence source
reads simply cloud_cspm. You see Binary Networks Scanner and your own service
account — nothing else.