Google Cloud Platform (GCP)
Set up with an AI agent
Using Claude Code, Cursor, Codex CLI, or another coding agent? The subimage-setup:connect-gcp skill walks the agent through every choice on this page. Paste this into your agent:
Use curl to download, read and follow: https://skills.subimage.io/plugins/subimage-setup/skills/connect-gcp/SKILL.mdOr install the marketplace once with claude plugin marketplace add subimagesec/skills. Full instructions on Agent Skills.
Purpose
Inventories all projects, folders, and organization-level settings inside your Google Cloud organization.
Typical asset classes collected:
- Compute Engine instances, images, and disks
- Cloud SQL instances and Cloud Storage buckets
- IAM policies (users, service accounts, roles)
- Organization / folder hierarchy and Org Policies
- API keys used to authenticate to public Google APIs
Secret fields below accept either an AWS Secrets Manager ARN or a value pasted directly into SubImage's managed vault. See Secrets for details.
Required Fields
Choose one authentication method.
| Field | Secret? | Description |
|---|---|---|
gcp_wif_project_number |
No | Project number for the project that owns the Workload Identity Pool |
gcp_wif_pool_id |
No | Workload Identity Pool ID |
gcp_wif_provider_id |
No | Workload Identity Provider ID inside the pool |
gcp_service_account_key |
Yes | Service account JSON key. Use only if you cannot use Workload Identity Federation |
Required IAM roles (org level)
| Role | Purpose | Required |
|---|---|---|
roles/iam.securityReviewer |
Read IAM policies, relationships, and Workload Identity Federation pools/providers | Yes |
roles/resourcemanager.organizationViewer |
Discover the organization, projects, and folders | Yes |
roles/resourcemanager.folderViewer |
Enumerate folder hierarchy | Yes |
roles/cloudasset.viewer |
Sync effective IAM policy bindings across the hierarchy | Optional |
roles/run.viewer |
Sync Cloud Run services, jobs, and executions | Optional |
roles/notebooks.viewer |
Sync Vertex AI Workbench (Notebooks API) resources | Optional |
roles/serviceusage.apiKeysViewer |
Sync GCP API Keys from apikeys.googleapis.com |
Optional |
roles/cloudsql.viewer |
List Cloud SQL instances, databases, and users | Optional |
roles/bigquery.dataViewer |
List BigQuery datasets and tables across projects | Optional |
roles/bigquery.connectionUser |
Access BigQuery connection resources | Optional |
roles/artifactregistry.reader |
Pull container images from Artifact Registry for vulnerability and SBOM scanning | Required if scanning GAR images |
Setup Steps
- Pick or create a host project that will own the Workload Identity Pool and absorb API billing.
- Recommended: configure Workload Identity Federation so SubImage can use short-lived credentials without a JSON key.
- Grant the three required roles at the org root to the SubImage WIF principal. Add optional roles for broader coverage. If you plan to scan container images stored in Google Artifact Registry, grant
roles/artifactregistry.readeron the projects or repositories that contain the images. If you grant a custom role instead ofroles/iam.securityReviewer, ensure it includesiam.workloadIdentityPools.listandiam.workloadIdentityPoolProviders.list(or grantroles/iam.workloadIdentityPoolVieweralongside it); without these, WIF pools and providers are silently skipped. - Enable the required APIs on the host project: see API enablement.
- If you cannot use WIF, create a service account, grant the same roles to that service account, generate a JSON key, and enter it in
gcp_service_account_key(or its AWS Secret ARN). - Optionally configure:
gcp_requested_syncs: select a subset of GCP resource groups for faster, scoped syncs. Leave it empty to sync everything supported by this Cartography version.gcp_enable_cai_iam_fallback: leave this enabled if you want SubImage to backfill service accounts and custom roles from Cloud Asset Inventory when a target project's IAM API is disabled.
- Save the module, then open the Run Sync page to start a GCP sync immediately or wait for the next scheduled run.
Selective Sync
SubImage can scope GCP syncs to a subset of supported resource groups, similar to the AWS module. This is useful when you want shorter syncs or only care about a specific slice of GCP inventory.
Examples:
compute,storage,iamfor a core infra inventory passbigquery,bigquery_connectionfor data-platform coveragegke,policy_bindings,permission_relationshipsfor Kubernetes plus IAM relationship analysis
Caveats:
- If you leave
gcp_requested_syncsempty, SubImage syncs all supported GCP resource groups. policy_bindingsdepends on IAM role data, so keepingiamselected is recommended.permission_relationshipsdepends on bothiamandpolicy_bindings.bigquery_connectiondepends onbigquery.
API Enablement
Sync calls are billed against the host project. Enable the APIs there:
# Core APIs
gcloud services enable cloudresourcemanager.googleapis.com --project=YOUR_HOST_PROJECT
gcloud services enable serviceusage.googleapis.com --project=YOUR_HOST_PROJECT
gcloud services enable iam.googleapis.com --project=YOUR_HOST_PROJECT
# Required when using Workload Identity Federation
gcloud services enable sts.googleapis.com --project=YOUR_HOST_PROJECT
# Optional APIs (enable based on resources you want synced)
gcloud services enable compute.googleapis.com --project=YOUR_HOST_PROJECT
gcloud services enable storage.googleapis.com --project=YOUR_HOST_PROJECT
gcloud services enable container.googleapis.com --project=YOUR_HOST_PROJECT
gcloud services enable dns.googleapis.com --project=YOUR_HOST_PROJECT
gcloud services enable cloudkms.googleapis.com --project=YOUR_HOST_PROJECT
gcloud services enable bigtableadmin.googleapis.com --project=YOUR_HOST_PROJECT
gcloud services enable sqladmin.googleapis.com --project=YOUR_HOST_PROJECT
gcloud services enable cloudasset.googleapis.com --project=YOUR_HOST_PROJECT
gcloud services enable cloudfunctions.googleapis.com --project=YOUR_HOST_PROJECT
gcloud services enable run.googleapis.com --project=YOUR_HOST_PROJECT
gcloud services enable secretmanager.googleapis.com --project=YOUR_HOST_PROJECT
gcloud services enable artifactregistry.googleapis.com --project=YOUR_HOST_PROJECT
gcloud services enable aiplatform.googleapis.com --project=YOUR_HOST_PROJECT
gcloud services enable notebooks.googleapis.com --project=YOUR_HOST_PROJECT
gcloud services enable bigquery.googleapis.com --project=YOUR_HOST_PROJECT
gcloud services enable bigqueryconnection.googleapis.com --project=YOUR_HOST_PROJECT
gcloud services enable apikeys.googleapis.com --project=YOUR_HOST_PROJECTWhen an optional API is disabled, SubImage logs a warning and skips those resources; the rest of the sync continues.
Cloud Asset Inventory (CAI)
SubImage uses the Cloud Asset Inventory API for enhanced IAM coverage:
- IAM Fallback: when the IAM API is disabled on a target project, SubImage falls back to CAI to retrieve service accounts and custom roles. Requires
cloudasset.googleapis.comenabled on the host project. - Policy Bindings: syncs effective IAM policies (including inherited ones from parent orgs and folders) for all resources. Requires
roles/cloudasset.viewerat the organization level. - Disabling
gcp_enable_cai_iam_fallbackonly turns off the IAM fallback. To avoid CAI-backed policy bindings too, deselectpolicy_bindingsfromgcp_requested_syncs.
Workload Identity Federation
Workload Identity Federation lets the SubImage tenant AWS role exchange its AWS identity for short-lived Google credentials. This avoids storing a long-lived service account key.
When you view these docs inside your authenticated tenant, SubImage auto-fills your tenant AWS account ID and tenant ID in the snippets below:
| Value | Auto-filled value |
|---|---|
| SubImage tenant AWS account ID | |
| SubImage tenant ID | |
| SubImage AWS role name | |
| Suggested Workload Identity Pool ID | subimage-wip |
| Suggested AWS provider ID | subimage-aws-provider |
The role ARN format is:
arn:aws:iam::<ACCOUNT_ID>:role/<TENANT_ID>-subimage-readonlySubImage stores only the GCP project number, pool ID, and provider ID. It does not need a Google credential configuration file and does not impersonate a Google service account for WIF. Bind the GCP IAM roles directly to the WIF principal shown below; otherwise token exchange can succeed while GCP APIs still return PERMISSION_DENIED.
The principal member you grant in Google Cloud should look like this after replacing the host project number:
principalSet://iam.googleapis.com/projects/<HOST_PROJECT_NUMBER>/locations/global/workloadIdentityPools/subimage-wip/attribute.aws_role/arn:aws:sts::<ACCOUNT_ID>:assumed-role/<TENANT_ID>-subimage-readonlyOption A: Terraform
variable "subimage_org_id" { type = string }
variable "subimage_host_project" { type = string }
variable "subimage_tenant_account_id" {
type = string
default = "<ACCOUNT_ID>"
}
variable "subimage_tenant_id" {
type = string
default = "<TENANT_ID>"
}
data "google_project" "host" {
project_id = var.subimage_host_project
}
locals {
subimage_aws_role_name = "${var.subimage_tenant_id}-subimage-readonly"
subimage_assumed_role_arn = "arn:aws:sts::${var.subimage_tenant_account_id}:assumed-role/${local.subimage_aws_role_name}"
subimage_wif_member = "principalSet://iam.googleapis.com/projects/${data.google_project.host.number}/locations/global/workloadIdentityPools/${google_iam_workload_identity_pool.subimage.workload_identity_pool_id}/attribute.aws_role/${local.subimage_assumed_role_arn}"
}
resource "google_iam_workload_identity_pool" "subimage" {
project = var.subimage_host_project
workload_identity_pool_id = "subimage-wip"
display_name = "SubImage AWS"
}
resource "google_iam_workload_identity_pool_provider" "subimage_aws" {
project = var.subimage_host_project
workload_identity_pool_id = google_iam_workload_identity_pool.subimage.workload_identity_pool_id
workload_identity_pool_provider_id = "subimage-aws-provider"
display_name = "SubImage AWS"
aws {
account_id = var.subimage_tenant_account_id
}
attribute_mapping = {
"google.subject" = "assertion.arn"
"attribute.account" = "assertion.account"
"attribute.aws_role" = "assertion.arn.contains('assumed-role') ? assertion.arn.extract('{account_arn}assumed-role/') + 'assumed-role/' + assertion.arn.extract('assumed-role/{role_name}/') : assertion.arn"
}
attribute_condition = "assertion.arn.startsWith('${local.subimage_assumed_role_arn}/')"
}
locals {
required_roles = [
"roles/iam.securityReviewer",
"roles/resourcemanager.organizationViewer",
"roles/resourcemanager.folderViewer",
]
optional_roles = [
# Add if scanning GAR images across many projects. For least privilege,
# grant roles/artifactregistry.reader at the repository or project scope instead.
# "roles/artifactregistry.reader",
]
all_roles = concat(local.required_roles, local.optional_roles)
}
resource "google_organization_iam_member" "subimage" {
for_each = toset(local.all_roles)
org_id = var.subimage_org_id
role = each.key
member = local.subimage_wif_member
}
output "subimage_gcp_wif_project_number" {
value = data.google_project.host.number
}
output "subimage_gcp_wif_pool_id" {
value = google_iam_workload_identity_pool.subimage.workload_identity_pool_id
}
output "subimage_gcp_wif_provider_id" {
value = google_iam_workload_identity_pool_provider.subimage_aws.workload_identity_pool_provider_id
}Option B: gcloud
ORG_ID=<ORG_ID>
HOST_PROJECT=<HOST_PROJECT>
TENANT_ACCOUNT_ID=<ACCOUNT_ID>
TENANT_ID=<TENANT_ID>
POOL_ID=subimage-wip
PROVIDER_ID=subimage-aws-provider
SUBIMAGE_AWS_ROLE_NAME="${TENANT_ID}-subimage-readonly"
SUBIMAGE_ASSUMED_ROLE_ARN="arn:aws:sts::${TENANT_ACCOUNT_ID}:assumed-role/${SUBIMAGE_AWS_ROLE_NAME}"
HOST_PROJECT_NUMBER="$(gcloud projects describe "$HOST_PROJECT" --format='value(projectNumber)')"
SUBIMAGE_WIF_MEMBER="principalSet://iam.googleapis.com/projects/${HOST_PROJECT_NUMBER}/locations/global/workloadIdentityPools/${POOL_ID}/attribute.aws_role/${SUBIMAGE_ASSUMED_ROLE_ARN}"
gcloud iam workload-identity-pools create "$POOL_ID" \
--project="$HOST_PROJECT" \
--location=global \
--display-name="SubImage AWS"
gcloud iam workload-identity-pools providers create-aws "$PROVIDER_ID" \
--project="$HOST_PROJECT" \
--location=global \
--workload-identity-pool="$POOL_ID" \
--account-id="$TENANT_ACCOUNT_ID" \
--attribute-mapping="google.subject=assertion.arn,attribute.account=assertion.account,attribute.aws_role=assertion.arn.contains('assumed-role') ? assertion.arn.extract('{account_arn}assumed-role/') + 'assumed-role/' + assertion.arn.extract('assumed-role/{role_name}/') : assertion.arn" \
--attribute-condition="assertion.arn.startsWith('${SUBIMAGE_ASSUMED_ROLE_ARN}/')"
for ROLE in \
roles/iam.securityReviewer \
roles/resourcemanager.organizationViewer \
roles/resourcemanager.folderViewer; do
gcloud organizations add-iam-policy-binding "$ORG_ID" \
--member="$SUBIMAGE_WIF_MEMBER" \
--role="$ROLE"
done
# Required if SubImage should scan images in Google Artifact Registry.
# Prefer repository or project scope for least privilege when practical.
gcloud artifacts repositories add-iam-policy-binding "<REPOSITORY>" \
--project="<GAR_PROJECT>" \
--location="<LOCATION>" \
--member="$SUBIMAGE_WIF_MEMBER" \
--role="roles/artifactregistry.reader"Then enter these values in Modules → GCP:
| SubImage field | Value |
|---|---|
gcp_wif_project_number |
$HOST_PROJECT_NUMBER |
gcp_wif_pool_id |
$POOL_ID |
gcp_wif_provider_id |
$PROVIDER_ID |