setup

Google Cloud Platform (GCP)

tip

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.md

Or 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
tip

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

  1. Pick or create a host project that will own the Workload Identity Pool and absorb API billing.
  2. Recommended: configure Workload Identity Federation so SubImage can use short-lived credentials without a JSON key.
  3. 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.reader on the projects or repositories that contain the images. If you grant a custom role instead of roles/iam.securityReviewer, ensure it includes iam.workloadIdentityPools.list and iam.workloadIdentityPoolProviders.list (or grant roles/iam.workloadIdentityPoolViewer alongside it); without these, WIF pools and providers are silently skipped.
  4. Enable the required APIs on the host project: see API enablement.
  5. 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).
  6. 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.
  7. 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,iam for a core infra inventory pass
  • bigquery,bigquery_connection for data-platform coverage
  • gke,policy_bindings,permission_relationships for Kubernetes plus IAM relationship analysis

Caveats:

  • If you leave gcp_requested_syncs empty, SubImage syncs all supported GCP resource groups.
  • policy_bindings depends on IAM role data, so keeping iam selected is recommended.
  • permission_relationships depends on both iam and policy_bindings.
  • bigquery_connection depends on bigquery.

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_PROJECT

When 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.com enabled on the host project.
  • Policy Bindings: syncs effective IAM policies (including inherited ones from parent orgs and folders) for all resources. Requires roles/cloudasset.viewer at the organization level.
  • Disabling gcp_enable_cai_iam_fallback only turns off the IAM fallback. To avoid CAI-backed policy bindings too, deselect policy_bindings from gcp_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 <ACCOUNT_ID>
SubImage tenant ID <TENANT_ID>
SubImage AWS role name <TENANT_ID>-subimage-readonly
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-readonly

SubImage 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-readonly

Option 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