Google Cloud¶
Connecting from the web app?
Follow the prerequisites and credential creation instructions below, then return to Cloud Security → Settings → Providers → Add provider. Enter the provider IDs under Configuration and save the credential using New secret under Permissions. Run Test Provider, fix required failures, and save. The LimaCharlie CLI examples below are an alternative. First-time setup and verification explains the full journey.
Collects the Google Cloud estate across every project in scope — compute, serverless (Cloud Run and Cloud Functions), Kubernetes (GKE clusters and the workloads running inside them), storage, networking, IAM, KMS, databases, secrets, Pub/Sub — plus CIEM (who can reach what), Vertex AI inventory, and agentless workload vulnerabilities from VM Manager.
Auth model: a service-account key (JSON) granted read-only roles at the organization, folder, or project you want enumerated. The collector discovers every active project underneath that node by itself.
Prerequisites¶
For a first connection, choose one project containing resources you recognize. Find its project ID in the Google Cloud project selector; the display name and numeric project number are different values. In the LimaCharlie wizard, enter that ID in Project and leave the broader scope field empty.
A service account is an identity for the collector, separate from your personal Google login. Its key proves its identity; IAM roles decide what it may read.
- A GCP project to own the service account (any project you control — it does not have to be one being scanned).
- Permission to grant IAM roles at the scope you intend to connect (Organization Admin / Folder Admin / Project IAM Admin).
- Permission to enable APIs in the service account's project, or an administrator who can complete the following step.
Enable the APIs¶
Enable these APIs in the service account's own project. Both tabs use the same list; you only need to follow one method.
- Select the service account's project in Google Cloud.
- Open APIs & Services → Library, search for a service below, and select Enable. Repeat for the full list; leave already enabled APIs on.
- Check Enabled APIs & services in that project to confirm the list.
You need permission to enable services. If Enable is unavailable, ask your Google Cloud administrator. Google's API enablement guide explains this console workflow.
Open Cloud Shell in Google Cloud, or use an installed and authenticated Google Cloud CLI. Set the project that owns the service account:
gcloud services enable \
cloudresourcemanager.googleapis.com \
serviceusage.googleapis.com \
iam.googleapis.com \
compute.googleapis.com \
storage.googleapis.com \
secretmanager.googleapis.com \
cloudkms.googleapis.com \
bigquery.googleapis.com \
sqladmin.googleapis.com \
pubsub.googleapis.com \
apikeys.googleapis.com \
osconfig.googleapis.com \
containeranalysis.googleapis.com \
run.googleapis.com \
cloudfunctions.googleapis.com \
container.googleapis.com \
aiplatform.googleapis.com \
notebooks.googleapis.com \
recommender.googleapis.com \
policyanalyzer.googleapis.com \
cloudidentity.googleapis.com \
orgpolicy.googleapis.com \
logging.googleapis.com \
monitoring.googleapis.com \
dns.googleapis.com \
essentialcontacts.googleapis.com \
accessapproval.googleapis.com \
accesscontextmanager.googleapis.com \
appengine.googleapis.com \
dataproc.googleapis.com \
--project "$SA_PROJECT"
| API Library name | Service ID |
|---|---|
| Cloud Resource Manager API | cloudresourcemanager.googleapis.com |
| Service Usage API | serviceusage.googleapis.com |
| Identity and Access Management (IAM) API | iam.googleapis.com |
| Compute Engine API | compute.googleapis.com |
| Cloud Storage JSON API | storage.googleapis.com |
| Secret Manager API | secretmanager.googleapis.com |
| Cloud Key Management Service (KMS) API | cloudkms.googleapis.com |
| BigQuery API | bigquery.googleapis.com |
| Cloud SQL Admin API | sqladmin.googleapis.com |
| Cloud Pub/Sub API | pubsub.googleapis.com |
| API Keys API | apikeys.googleapis.com |
| OS Config API | osconfig.googleapis.com |
| Container Analysis API | containeranalysis.googleapis.com |
| Cloud Run Admin API | run.googleapis.com |
| Cloud Functions API | cloudfunctions.googleapis.com |
| Kubernetes Engine API | container.googleapis.com |
| Vertex AI API | aiplatform.googleapis.com |
| Notebooks API | notebooks.googleapis.com |
| Recommender API | recommender.googleapis.com |
| Policy Analyzer API | policyanalyzer.googleapis.com |
| Cloud Identity API | cloudidentity.googleapis.com |
| Organization Policy API | orgpolicy.googleapis.com |
| Cloud Logging API | logging.googleapis.com |
| Cloud Monitoring API | monitoring.googleapis.com |
| Cloud DNS API | dns.googleapis.com |
| Essential Contacts API | essentialcontacts.googleapis.com |
| Access Approval API | accessapproval.googleapis.com |
| Access Context Manager API | accesscontextmanager.googleapis.com |
| App Engine Admin API | appengine.googleapis.com |
| Cloud Dataproc API | dataproc.googleapis.com |
Some APIs are checked in the service account's project, not the scanned one
For some Google Cloud APIs, Google checks whether the API is enabled in the
project that owns the calling service account, not only in the project or
organization being read. If one of these is off in the service account's
project, every read of that API fails, for every project in scope, with a
SERVICE_DISABLED error that names the service account's project.
LimaCharlie cannot tell from that error what the scanned projects contain,
so it does not treat them as empty. The benchmark controls that depend on
the API read as not assessed, and the related findings are missing.
We have confirmed this behaviour for these APIs:
| API | What depends on it |
|---|---|
apikeys.googleapis.com |
API key age and restrictions |
essentialcontacts.googleapis.com |
Organization essential contacts |
accessapproval.googleapis.com |
Access Approval enrollment |
orgpolicy.googleapis.com |
Organization policy constraints |
accesscontextmanager.googleapis.com |
VPC Service Controls perimeters |
policyanalyzer.googleapis.com |
Dormant-identity and last-authentication findings |
They are all in the list above. If you trimmed that list, make sure at least these are on in the service account's project:
gcloud services enable \
apikeys.googleapis.com \
essentialcontacts.googleapis.com \
accessapproval.googleapis.com \
orgpolicy.googleapis.com \
accesscontextmanager.googleapis.com \
policyanalyzer.googleapis.com \
--project "$SA_PROJECT"
Enabling them only in the scanned projects does not fix this.
provider test catches this for Organization Policy, Access Context
Manager (organization scope) and Policy Analyzer: the check fails and its
message names the project to enable the API in. The API Keys, Essential
Contacts and Access Approval checks only ask IAM about the grant, so they
stay green even when the API is off in the service account's project.
APIs must also be on in the projects being scanned
A scanned project with a service API disabled is skipped for that
service — treated as covered, empty, never as a permission failure.
That is usually what you want. provider test reports a disabled API as a
passing check with an explanatory note, precisely so you do not mistake it
for a missing grant.
The preflight checks grants, not every API
The preflight checks that the roles are in place for every surface in the check table below. For the configuration surfaces (KMS, BigQuery, Cloud SQL, API keys, logging, DNS and the others added for the CIS benchmark) it asks IAM about the grant, so it does not tell you whether their APIs are enabled. Pub/Sub and Workbench notebooks are exercised only during the sweep. If an API is disabled, that inventory type comes back empty while the provider test stays green, so enable the full list above rather than trimming it to what the test covers.
Required roles¶
Grant at the scope node (organization, folder, or project). Roles are inherited down the hierarchy, so an org-level grant covers every project.
| Role | Why | Preflight check |
|---|---|---|
roles/browser |
Resolve the organization or folder and enumerate its folders and projects | auth, projects |
roles/compute.viewer |
Compute, network, subnetwork, load-balancer, SSL-policy and project-metadata reads, plus which services are enabled | compute, network_config, service_usage |
roles/iam.securityReviewer |
*.getIamPolicy across services for the CIEM graph (who can access what), plus the list reads for storage, service accounts and keys, KMS, Cloud SQL, API keys, Pub/Sub, secrets, log sinks, log-based metrics, alert policies, Cloud DNS, Dataproc and essential contacts |
storage, iam, sa_keys, audit_config, kms, cloud_sql, api_keys, log_sinks, log_metrics_alerts, dns, dataproc, essential_contacts |
These are the same roles the web app's Permissions required list and its
generated gcloud script grant. Each is a read-only predefined role.
Using roles/viewer instead?
roles/viewer with roles/iam.securityReviewer also works, but it grants
much more than the collector reads, and it still lacks some permissions
the collector uses:
resourcemanager.organizations.getandresourcemanager.folders.get, which the preflight'sauthcheck needs for an organization or folder scope. Addroles/browser.bigquery.tables.get, which the table encryption check needs. Addroles/bigquery.metadataViewer.storage.buckets.get, which the log sink destination bucket check needs. Addroles/storage.bucketViewer.
roles/viewer already includes the Access Approval, App Engine and
Organization Policy reads listed under optional roles.
If you use Code Security to scan container
images, add roles/artifactregistry.reader to the least-privilege set (see
Container image scanning).
roles/viewer already includes it.
Optional roles¶
Each adds one inventory or analysis surface. Skipping one leaves that surface unobserved; the sweep still succeeds.
| Role | Unlocks | Preflight check |
|---|---|---|
roles/bigquery.metadataViewer |
BigQuery dataset inventory, dataset sharing, and dataset and table encryption keys (bigquery.datasets.get, bigquery.tables.get). Metadata only: it cannot read table data |
bigquery_metadata |
roles/accessapproval.viewer |
Whether Access Approval is enrolled, at the project, folder and organization (accessapproval.settings.get) |
access_approval |
roles/appengine.appViewer |
App Engine application and version configuration, used to check HTTPS enforcement (appengine.applications.get, appengine.versions.get) |
app_engine |
roles/orgpolicy.policyViewer |
Organization policy constraints such as public access prevention and service-account key creation (orgpolicy.policy.get) |
constraint_org_policies |
roles/storage.bucketViewer |
The retention policy and lock of the Cloud Storage buckets your log sinks export to (storage.buckets.get). Bucket metadata only: it cannot list or read objects. Without it, only the sink bucket retention lock check reads as not assessed; the check that a sink exports every log entry still runs |
log_sink_destinations |
roles/secretmanager.viewer |
Secret metadata inventory (names/rotation posture — never secret values) | secret_manager |
roles/osconfig.vulnerabilityReportViewer |
Agentless workload vulnerabilities from VM Manager | osconfig_vuln |
roles/osconfig.inventoryViewer |
The OS-inventory join that attaches package name + installed/fixed version to each CVE | (not probed — exercised during the sweep) |
roles/containeranalysis.occurrences.viewer |
Container image vulnerability and signed Cloud Build provenance occurrences from Artifact Analysis, for images in Artifact Registry and Container Registry | artifact_analysis |
roles/artifactregistry.reader |
Code Security pulling your private container images to scan them. Already included in roles/viewer; add it if you use the required least-privilege roles |
(not probed — exercised when an image is scanned) |
roles/recommender.iamViewer |
Unused-privilege findings (activity-based CIEM) | activity_ciem |
roles/policyanalyzer.activityAnalysisViewer |
Dormant-identity / last-authentication findings | activity_ciem |
roles/aiplatform.viewer |
Vertex AI endpoint and model inventory | vertex_ai |
roles/run.viewer |
Cloud Run service inventory and its public-access verdict (run.services.list + run.services.getIamPolicy) |
serverless |
roles/cloudfunctions.viewer |
Cloud Functions inventory (1st and 2nd gen) plus their invoker policies (cloudfunctions.functions.list + cloudfunctions.functions.getIamPolicy) |
serverless |
roles/cloudidentity.groups.readonly |
Google-group membership expansion, so group: IAM bindings resolve to real people |
cloud_identity |
roles/container.viewer |
Only needed to reach a cluster through its DNS-based control-plane endpoint (container.clusters.connect). In-cluster collection itself already works on the required roles — see the Kubernetes notes below |
(not probed — exercised during the sweep) |
What container image scanning gives you, and what it does not
With roles/containeranalysis.occurrences.viewer granted and Artifact
Analysis enabled in the project, each vulnerable image is reported against
its digest, so one image is one finding subject however many tags point
at it — the same shape we use for Amazon ECR and Azure Container Registry, so
an image is one thing to triage across clouds.
Three limits are worth knowing up front:
- Artifact Analysis is not a registry catalog. Use the Code Security image browser for image inventory and links to workloads running a digest.
- An enabled verdict is about the API, not every repository. We read whether Artifact Analysis answers for the project. That does not prove on-push scanning is configured for every repository.
- Very large registries are skipped whole, not truncated. A project whose registries hold more scan results than our per-project ingestion budget reports none of them rather than an arbitrary subset — a truncated set would look like a shrinking estate. A busy CI project that keeps every historical build image is the case that hits this. The results are not lost on Google's side; we simply do not ingest them yet.
Serverless already works on the required baseline
The required roles/iam.securityReviewer already grants both the list
and the getIamPolicy reads for Cloud Run and Cloud Functions (as does
roles/viewer), so you do not need to add anything for serverless coverage.
The two roles above exist for custom grants that leave out the Security
Reviewer role, where they are what turns serverless coverage on.
Both halves of each grant matter, and the second is the one that gets
missed: without getIamPolicy we can list a service but cannot tell whether
it is invocable by anyone on the internet. In that case we report no
verdict rather than guess — so serverless exposure findings are absent,
not empty, which is a different thing from "you have none".
Services published only through a load balancer
A Cloud Run service or function whose ingress is internal and Cloud Load
Balancing cannot be reached at its own run.app URL, but it can be
published to the internet through an external load balancer in front of it.
We do not yet collect load balancers, so such a service is inventoried but
is not reported as internet-facing. Services reachable directly at their
own URL are assessed normally.
2nd-gen functions are authorized as Cloud Run services
A 2nd-gen Cloud Function runs on Cloud Run, and its invoker permission
lives on the underlying Cloud Run service (roles/run.invoker) rather than
on the function (roles/cloudfunctions.invoker is the 1st-gen role). So
assessing a 2nd-gen function's public access reads the Cloud Run side,
not the Cloud Functions side.
If you are assembling a custom grant rather than using the required
roles, grant both roles/run.viewer and roles/cloudfunctions.viewer and
let provider test confirm it: the serverless check exercises both APIs and
reports each separately, so it will tell you if one half is missing rather
than leaving you to reason about role contents.
Kubernetes needs no in-cluster setup
Reading what runs inside a GKE cluster needs nothing installed in the cluster: no kubeconfig, no ClusterRoleBinding, no agent, no manifest to apply. GKE's API server authorizes a Google OAuth token against Cloud IAM in addition to Kubernetes RBAC, so the same service-account key you already connected reaches each cluster's Kubernetes API directly.
The required roles/iam.securityReviewer grant already covers it — container.pods.list,
container.namespaces.list, container.deployments.list,
container.statefulSets.list, container.daemonSets.list,
container.cronJobs.list, container.jobs.list,
container.replicaSets.list. Since roles/iam.securityReviewer is
required, no extra role is needed for the Kubernetes object reads.
Google lists them under the
Security Reviewer role.
roles/container.viewer is worth adding for exactly one case: it is the
read-only role that carries container.clusters.connect, which is
required to reach a cluster through its DNS-based control-plane
endpoint. roles/viewer also carries it. If you are using that endpoint
(see below) and have not granted roles/viewer, grant it. See Google's
DNS endpoint access guidance.
Collection uses read-only list requests for namespaces, controllers
and pods. It retains workload configuration (replica counts, service
accounts, security settings, host-path mounts), namespace and workload
labels and annotations (bounded in size), and the images pods are running.
It does not read environment variables, command lines, Secrets,
ConfigMaps, pod logs or exec, and the listed roles do not grant
Kubernetes write access.
A cluster we cannot reach on the network is reported as partial, not empty
The connection talks to each cluster's control-plane endpoint. Two common configurations block that, and in both cases the cluster keeps its cloud-side posture assessment but nothing inside it is collected:
- a private control plane with no externally reachable endpoint;
- control-plane authorized networks whose allowlist does not include us.
When that happens the project's Kubernetes workload inventory is marked partial — whatever was collected before is preserved rather than deleted, and the connection's notes name the cluster and the reason. It is never reported as a cluster that runs nothing. The same is true of a cluster whose API simply did not answer this pass: the view goes stale, it is never emptied.
Two ways to open it, if you want in-cluster coverage on such a cluster:
- enable the cluster's DNS-based control-plane endpoint and allow
external traffic on it. It is served by Google's front end, so it works
for a private control plane and is not subject to authorized networks —
this is the option we recommend, and it needs no addresses from us. It
does need
roles/container.vieweron the connection's service account, because that endpoint additionally checkscontainer.clusters.connect. VPC Service Controls can still restrict access; allow the connection identity through any applicable service perimeter; - or allowlist our egress in the cluster's authorized networks. Ask support for the current addresses for your region rather than inferring them: they are per-datacenter and they change.
What in-cluster collection gives you, and what it does not
Collected: namespaces (including their Pod Security Admission labels) and workloads — Deployments, StatefulSets, DaemonSets, CronJobs and standalone Jobs — with their replica counts, service accounts, pod-security settings, and the image digest their pods are actually running. That last one is what populates Running On in the image browser, so a vulnerable image can be traced to the Kubernetes workloads running it.
Not collected: Services, RBAC objects (Roles, ClusterRoles and their bindings), NetworkPolicies and admission webhooks. Kubernetes RBAC therefore does not appear in identity or attack-path analysis.
Three deliberate modelling choices worth knowing:
- a Job created by a CronJob and a ReplicaSet created by a Deployment are not listed as workloads of their own; their pods are attributed to the CronJob or Deployment that owns them, which is the object you actually operate;
- a pod that none of those five kinds owns (a bare pod, or one managed by a custom controller) is listed as a workload of unspecified kind, named after the pod or after the top-level object that owns it;
- a workload scaled to zero is inventoried with no image link. That is "nothing is running", not "no image".
osconfig_vuln does not prove the inventory join
The osconfig_vuln check probes the vulnerability-report read only, so it
passes with roles/osconfig.vulnerabilityReportViewer alone. If
roles/osconfig.inventoryViewer is missing, the sweep still records the
CVEs but skips the OS-inventory join and moves on — the symptom is
vulnerability rows with no package name and no fixed version, not a
failing check.
Workbench notebooks are a separate API
Vertex AI Workbench notebook inventory comes from the Notebooks API
(notebooks.googleapis.com), not from the Vertex AI API, and it is not
covered by the vertex_ai check. Enable that API too if you want notebooks
inventoried.
Recommender needs only the list permission
Google's own walkthrough for reviewing role recommendations in the
console also asks for roles/iam.roleViewer and a resource IAM-admin role.
Those cover applying recommendations interactively; this connector only
lists them, and the required roles/iam.securityReviewer already covers
the role-metadata reads.
Cloud Identity groups are granted elsewhere
roles/cloudidentity.groups.readonly (or the Groups Reader role) is
granted at the Cloud Identity account/customer level in the Google
Admin console, not on a GCP project. Without it, IAM bindings to groups
remain visible but their membership is not expanded, so
"which humans can reach this bucket" stops at the group.
Container image scanning by Code Security¶
Code Security scans the container images your
repositories and workloads reference by digest. To scan a private image in
Artifact Registry (*-docker.pkg.dev) or gcr.io, it pulls the image using
this connection's service account. That service account needs
roles/artifactregistry.reader on the project that hosts the image. That
is often not the project where the image runs. gcr.io is served by Artifact
Registry, so the same role covers it.
- With
roles/viewerat the organization or folder, you already have it. -
With the required least-privilege roles, grant it at the same node (variables as in Create the service account):
-
Images kept in a separate project, such as a shared build or artifact project, need the grant there too if that project is outside the connected scope.
- A project still on legacy Container Registry serves
gcr.ioimages from Cloud Storage. There the service account needsroles/storage.objectVieweron the project'sartifacts.<project>.appspot.combucket instead.
To verify Google Cloud Build provenance for those images, also grant this
connection's service account roles/containeranalysis.occurrences.viewer on
the project that stores the image and its Artifact Analysis occurrences.
It is a read-only role. A grant on the project where the workload runs is not
enough when the image lives in a different project. The role appears in the
optional permissions list above and in the web app's generated GCP grant list.
Without it, Code Security can still scan an image when registry access is
available, but its Cloud Build lineage cannot be verified.
For a project-level grant, use the service account and image project you selected during setup:
gcloud projects add-iam-policy-binding "IMAGE_PROJECT_ID" \
--member "serviceAccount:COLLECTOR_SERVICE_ACCOUNT_EMAIL" \
--role roles/containeranalysis.occurrences.viewer
Enable containeranalysis.googleapis.com in the project that owns the service
account, as described under Enable the APIs, and in the
image project if it is disabled there.
Without registry pull access, the image is not scanned and its status reads
registry_permission_denied, naming the registry and missing pull role. The code
security status shows image_registry_permission. A refused image is retried
less and less often, down to once a day. It keeps being retried, so granting
the image pull role fixes it without any other change. To retry right away, use
Sync now on the source-control connection (for example GitHub) whose
scans reference the image.
Create the service account¶
Choose the same scope in the provider configuration and in the role grants. A project grant covers one project; a folder or organization grant covers its projects. The web-console steps support each scope. The retained CLI example below uses an organization and names the command substitutions for narrower scopes.
- Select the project that will own the credential. Open IAM & Admin → Service
Accounts → Create service account and create
lc-cloudsec. - Copy the service account's email address. Select the project, folder, or organization you want to scan, then open IAM & Admin → IAM → Grant access. Use that email as the principal
and grant Browser (
roles/browser), Compute Viewer (roles/compute.viewer) and Security Reviewer (roles/iam.securityReviewer), as listed above. Add optional roles only for the data you want to collect. - Return to the service account's project, open the account, then Keys → Add key → Create new key → JSON. Download the key. If key creation is blocked by organization policy, ask your Google Cloud administrator to resolve it.
- In the wizard's Permissions step, select New secret, give it a name,
and paste the complete downloaded JSON. Do not add another
secretwrapper. - Run Test Provider. Fix required failures and check which optional data is unavailable. Save and verify a known resource in Inventory.
See Google's service account key instructions for the console workflow. Complete the API prerequisites above as well as the role grants; a key alone does not enable collection.
Open Cloud Shell, or use an installed and authenticated gcloud CLI.
The following example grants access to an organization. Replace ORG_ID and
SA_PROJECT with your values; do not use this wider scope for a one-project
pilot unless you intend to grant organization-wide access.
SA_PROJECT=my-security-project
ORG_ID=123456789 # organization ID, not project ID
gcloud iam service-accounts create lc-cloudsec \
--display-name "LimaCharlie Cloud Security" \
--project "$SA_PROJECT"
SA="lc-cloudsec@${SA_PROJECT}.iam.gserviceaccount.com"
# Required
for ROLE in roles/browser roles/compute.viewer roles/iam.securityReviewer; do
gcloud organizations add-iam-policy-binding "$ORG_ID" \
--member "serviceAccount:${SA}" --role "$ROLE"
done
# Optional surfaces. If you use Code Security to scan private container
# images, add roles/artifactregistry.reader here.
for ROLE in roles/bigquery.metadataViewer \
roles/accessapproval.viewer \
roles/appengine.appViewer \
roles/orgpolicy.policyViewer \
roles/storage.bucketViewer \
roles/secretmanager.viewer \
roles/osconfig.vulnerabilityReportViewer \
roles/osconfig.inventoryViewer \
roles/recommender.iamViewer \
roles/policyanalyzer.activityAnalysisViewer \
roles/aiplatform.viewer \
roles/containeranalysis.occurrences.viewer \
roles/run.viewer \
roles/cloudfunctions.viewer; do
gcloud organizations add-iam-policy-binding "$ORG_ID" \
--member "serviceAccount:${SA}" --role "$ROLE"
done
gcloud iam service-accounts keys create sa-key.json \
--iam-account "$SA" --project "$SA_PROJECT"
For a folder scope use gcloud resource-manager folders add-iam-policy-binding
<FOLDER_ID>; for a single project use gcloud projects add-iam-policy-binding
<PROJECT_ID>.
Create the credentials secret¶
The secret value is the service-account key JSON itself — no wrapper.
In LimaCharlie, open Organization Settings → Secrets Manager → Add.
Name the secret gcp-collector-sa and paste the complete downloaded key JSON
into its value. Save it enabled. Do not paste the example JSON from a guide or
add an outer secret property.
Create the provider record¶
provider.yaml:
provider_type: gcp
gcp_scope: organizations/123456789 # or folders/456 or projects/my-project
credentials: hive://secret/gcp-collector-sa
internal_domains: [example.com]
refresh: 6h
| Field | Meaning |
|---|---|
gcp_scope |
The node to enumerate: organizations/{id}, folders/{id}, or projects/{id}. Every active project underneath is swept. |
gcp_project |
Alternative to gcp_scope for a single project. Supply one or the other. |
In the web app: Add provider → GCP, then set the scope, Credentials, and the Sync cadence (pick a preset, or Custom interval for a duration of your own).
Verify¶
The report opens with the config and credential checks common to every
provider, documented once in Provider Setup;
the GCP-specific checks follow.
| Check | Required | Meaning if it fails |
|---|---|---|
auth |
✅ | The key could not mint a token, or the scope node is unreachable. Nothing else can be probed. |
projects (folder/org scope only) |
✅ | resourcemanager.projects.list denied — no project can be discovered under the scope. A single-project scope has nothing to enumerate, so this check is not reported at all. |
compute |
✅ | compute.instances.list denied — no compute inventory. |
storage |
✅ | storage.buckets.list denied — no storage inventory. |
iam |
✅ | getIamPolicy and/or serviceAccounts.list denied — the CIEM access graph cannot be built. |
secret_manager |
— | Secret-store inventory unavailable. |
activity_ciem |
— | Unused-privilege and dormant-identity findings unavailable. |
osconfig_vuln |
— | Workload vulnerability findings unavailable. |
vertex_ai |
— | Vertex AI endpoint inventory unavailable. |
serverless |
— | Cloud Run / Cloud Functions inventory unavailable, and with it the "invocable by anyone on the internet" verdict for that tier. Reported per API, so a partial grant names the half that is missing. |
cloud_identity |
— | Group membership is not expanded; group: bindings do not resolve to people. |
sa_keys |
— | Service-account key presence and age cannot be assessed. |
audit_config |
— | Audit logging configuration cannot be read at every level (project, and the folder or organization of a hierarchy scope). |
essential_contacts (organization scope only) |
— | Organization essential contacts cannot be assessed. Skipped for project and folder scopes, where the controls read as not assessed. |
log_sinks |
— | Log sinks cannot be read at the project or the scope's folder or organization. |
log_sink_destinations |
— | The Cloud Storage buckets that log sinks export to cannot be read, so their retention lock cannot be assessed. Add roles/storage.bucketViewer. |
log_metrics_alerts |
— | Log-based metrics and alert policies cannot be read, so the change-alerting controls cannot be assessed. |
dns |
— | DNSSEC and Cloud DNS logging cannot be assessed. |
network_config |
— | Default and legacy networks, VPC flow logs, load-balancer logging, SSL policies and project-wide OS Login cannot be assessed. |
service_usage |
— | Enabled services (for example the Cloud Asset API) cannot be read. |
access_approval |
— | Access Approval enrollment cannot be assessed. |
app_engine |
— | App Engine HTTPS enforcement cannot be assessed. |
cloud_sql |
— | Cloud SQL flags, SSL mode, backups and public IP cannot be assessed. |
kms |
— | KMS key rotation and public key access cannot be assessed. |
api_keys |
— | API key age and restrictions cannot be assessed. |
bigquery_metadata |
— | BigQuery datasets are not inventoried, and dataset and table encryption cannot be assessed. |
dataproc |
— | Dataproc cluster encryption cannot be assessed. |
The checks from sa_keys onward cover the configuration that the CIS Google
Cloud Foundation Benchmark checks rely on. Each asks IAM whether the service
account holds the permissions, using testIamPermissions: one call on the
probed project, plus one on the folder or organization for a hierarchy scope.
That works even when the project has no such resource (no App Engine app, for
example). When a permission is missing, the check names it, the level it is
missing on, and the role to add.
Org-scope tests probe one representative project
For a folder/organization scope the preflight picks the first active project under the node and probes there — IAM grants are inherited, so one project answers "is this granted". If that project happens to have a service API disabled, the check passes with a note saying the grant was neither proven nor disproven.
Troubleshooting¶
provider test result |
Cause | Fix |
|---|---|---|
auth fails: PERMISSION_DENIED on the scope |
The service account has no binding at that org/folder/project, or (for an org/folder scope) it holds roles/viewer without roles/browser |
Grant roles/browser at the scope node (not just on the SA's own project) |
auth fails: "Cloud Resource Manager API has not been used in project <number>" on an organization or folder scope |
An org/folder has no project of its own, so the API-enablement check is billed to the caller's project — <number> is the service account's project, not a project being scanned. The console only offers to enable APIs per project, which makes this look unresolvable at the org level |
gcloud services enable cloudresourcemanager.googleapis.com --project "$SA_PROJECT" (see Prerequisites) |
projects fails |
Missing resourcemanager.projects.list |
roles/viewer or roles/browser at the scope node |
iam fails on getIamPolicy |
roles/viewer alone does not cover every getIamPolicy |
Add roles/iam.securityReviewer |
A configuration check (log_sinks, bigquery_metadata, access_approval, …) fails with "lacks …" |
The named permission is not granted at the named level | Grant the role the message names at the scope node |
| A check passes with "API not enabled on the probed project" | Benign — the sweep skips API-disabled projects | Enable the named API if you want that surface; otherwise ignore |
| A check fails with "… has not been used in project … (enable this API in the project this message names)" | The API is off in a project other than the one probed. For the Organization Policy, Access Context Manager and Policy Analyzer checks, that is usually the service account's project (see Enable the APIs) | Enable the named API in the project the message names: gcloud services enable <api> --project <that project> |
serverless fails on run only |
The grant reaches Cloud Functions but not Cloud Run | Add roles/run.viewer. 2nd-gen functions are authorized as Cloud Run services, so without that read they list with no public-access verdict |
| Cloud Run services appear but none is ever flagged public | getIamPolicy is denied on Cloud Run, so the invoker verdict is unobserved rather than negative |
Add roles/iam.securityReviewer (or roles/run.viewer) and re-run the sweep |
| Inventory is missing whole projects | Those projects are not ACTIVE, or the grant is on a narrower node |
Confirm project state, and that the binding is at the scope you configured |