CloudGoogle Cloud

Scan GCP Projects with Workload Identity Federation

Use Workload Identity Federation to let a service account in one GCP project scan resources in other GCP projects without exporting keys.

Use Workload Identity Federation (WIF) to let a service account in one GCP project scan resources in other GCP projects — without exporting keys or creating service accounts in the target projects.

This guide uses direct resource access (no service account impersonation). You create a Workload Identity Pool in each target project that trusts a specific service account from your scanner project. At runtime, the scanner VM exchanges its Google-issued identity token for a federated access token that carries the granted roles in the target project.

You can also configure this approach using service account impersonation with WIF if your security policy requires it.

Example setup in this guide:

RoleProject IDDescription
Scanner (source)scanner-projectRuns the VM with cnspec installed
Target Atarget-project-aFirst project to be scanned
Target Btarget-project-bSecond project to be scanned

You repeat steps 1 through 5 for each target project. You configure the scanner project only once.

Prerequisites

  • IAM permissions: You need roles/iam.workloadIdentityPoolAdmin on each target project and iam.serviceAccounts.get on the scanner project's service account.
  • APIs enabled on each target project: iam.googleapis.com and cloudresourcemanager.googleapis.com
  • A GCE VM in the scanner project with a dedicated service account attached (not the default compute service account)
  • cnspec installed on the VM

Enable the required APIs on each target project:

gcloud services enable iam.googleapis.com \
  cloudresourcemanager.googleapis.com \
  --project="target-project-a"

gcloud services enable iam.googleapis.com \
  cloudresourcemanager.googleapis.com \
  --project="target-project-b"

Step 1: Gather scanner service account information

From any machine where you are authenticated as a project admin, look up the scanner service account's unique ID. This numeric ID is used in the trust condition instead of the email address because it is immutable — it survives service account deletion and recreation with the same name.

SCANNER_SA_EMAIL="scanner-sa@scanner-project.iam.gserviceaccount.com"

SCANNER_SA_ID=$(gcloud iam service-accounts describe "$SCANNER_SA_EMAIL" \
  --format='value(uniqueId)')

echo "Scanner SA unique ID: $SCANNER_SA_ID"

Step 2: Configure each target project

Repeat the remaining steps for each target project. The examples below show both target projects.

Set variables

LOCATION="global"

Target A:

TARGET_PROJECT="target-project-a"
IDENTITY_POOL_ID="scanner-pool"
PROVIDER_ID="scanner-provider"
TARGET_PROJECT_NUMBER=$(gcloud projects describe "$TARGET_PROJECT" \
  --format='value(projectNumber)')

Target B:

TARGET_PROJECT="target-project-b"
IDENTITY_POOL_ID="scanner-pool"
PROVIDER_ID="scanner-provider"
TARGET_PROJECT_NUMBER=$(gcloud projects describe "$TARGET_PROJECT" \
  --format='value(projectNumber)')

Pool and provider IDs only need to be unique within a project. Using the same names across target projects is fine and simplifies management.

Step 3: Create the Workload Identity Pool and OIDC provider

Create the Workload Identity Pool

gcloud iam workload-identity-pools create "$IDENTITY_POOL_ID" \
  --project="$TARGET_PROJECT" \
  --location="$LOCATION" \
  --display-name="Scanner Pool"

Create the OIDC provider

The provider trusts Google-issued identity tokens and restricts authentication to the scanner service account by its unique ID.

gcloud iam workload-identity-pools providers create-oidc "$PROVIDER_ID" \
  --project="$TARGET_PROJECT" \
  --location="$LOCATION" \
  --workload-identity-pool="$IDENTITY_POOL_ID" \
  --display-name="Scanner GCP Provider" \
  --issuer-uri="https://accounts.google.com" \
  --attribute-mapping='google.subject=assertion.sub,attribute.sa_email=assertion.email' \
  --attribute-condition="assertion.sub == \"${SCANNER_SA_ID}\""

Key parameters:

  • issuer-uri: Set to https://accounts.google.com because the identity token comes from Google's metadata server.
  • attribute-mapping: google.subject=assertion.sub maps the service account's unique ID as the WIF principal identifier. attribute.sa_email=assertion.email is optional but useful for audit logs.
  • attribute-condition: Restricts the provider so that only the scanner service account can authenticate. Without this, any Google-issued token could federate into the pool.

Step 4: Grant IAM roles to the WIF principal

Bind roles directly to the federated principal. No service account is created or impersonated in the target project.

MEMBER="principal://iam.googleapis.com/projects/$TARGET_PROJECT_NUMBER/locations/$LOCATION/workloadIdentityPools/$IDENTITY_POOL_ID/subject/$SCANNER_SA_ID"

gcloud projects add-iam-policy-binding "$TARGET_PROJECT" \
  --member="$MEMBER" \
  --role="roles/viewer"

gcloud projects add-iam-policy-binding "$TARGET_PROJECT" \
  --member="$MEMBER" \
  --role="roles/browser"

Use principal:// (not principalSet://) when binding to a specific /subject/. principalSet:// is for matching groups of identities (for example, by attribute or wildcard).

Two roles are required:

  • roles/viewer provides the read access that cnspec needs for a project scan.
  • roles/browser grants resourcemanager.projects.get and resourcemanager.projects.list, which the federated principal needs to resolve project metadata. Without this role, WIF tokens receive ACCESS_TOKEN_SCOPE_INSUFFICIENT errors when calling the Cloud Resource Manager API.

You can use more granular roles if your security policy requires least-privilege scoping.

Step 5: Generate the credential configuration file

Each target project needs its own credential configuration file. This file tells the client library to fetch an identity token from the GCE metadata server and exchange it via the Security Token Service (STS) for a federated access token.

POOL_RESOURCE="projects/$TARGET_PROJECT_NUMBER/locations/$LOCATION/workloadIdentityPools/$IDENTITY_POOL_ID/providers/$PROVIDER_ID"
METADATA_AUDIENCE="//iam.googleapis.com/${POOL_RESOURCE}"
METADATA_URL="http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=${METADATA_AUDIENCE}"

cat > "wif-credential-${TARGET_PROJECT}.json" <<EOF
{
  "universe_domain": "googleapis.com",
  "type": "external_account",
  "audience": "${METADATA_AUDIENCE}",
  "subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
  "token_url": "https://sts.googleapis.com/v1/token",
  "credential_source": {
    "url": "${METADATA_URL}",
    "headers": {
      "Metadata-Flavor": "Google"
    },
    "format": {
      "type": "text"
    }
  },
  "token_info_url": "https://sts.googleapis.com/v1/introspect"
}
EOF

The credential configuration file contains no secrets. It only describes how to obtain a token. Token exchange only succeeds from a GCE VM running with the correct service account attached.

Run the scans

Copy the generated credential files to the scanner VM. Before scanning, verify the VM is using the correct service account:

curl -s -H "Metadata-Flavor: Google" \
  http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email
# Expected: scanner-sa@scanner-project.iam.gserviceaccount.com

Scan each target project by pointing GOOGLE_APPLICATION_CREDENTIALS to the corresponding credential file:

GOOGLE_APPLICATION_CREDENTIALS="wif-credential-target-project-a.json" \
  cnspec scan gcp project target-project-a

GOOGLE_APPLICATION_CREDENTIALS="wif-credential-target-project-b.json" \
  cnspec scan gcp project target-project-b

Security considerations

  • No exported keys. Authentication relies on the GCE metadata server. No long-lived credentials are created, stored, or rotated.
  • Scoped trust. The attribute-condition ensures that only the specific scanner service account can federate into each target project's pool. Removing or recreating the service account with the same email does not grant access — the unique ID must match.
  • Least privilege. Each target project independently controls which IAM roles are granted to the federated principal. Revoking access is a single IAM policy binding removal.
  • Audit trail. Federated access appears in Cloud Audit Logs under the WIF principal, making it straightforward to trace scanning activity.

Troubleshooting

SymptomCauseFix
INVALID_ARGUMENT: Invalid principalSet memberUsed principalSet:// with /subject/Change to principal://
PERMISSION_DENIED on scanMissing IAM binding or wrong roleVerify the binding with gcloud projects get-iam-policy
Unable to detect a valid identity tokenVM is not using the correct service accountCheck the metadata endpoint to confirm the SA email
The audience in the identity token is invalidCredential file has the wrong audience stringRegenerate the credential file with the correct project number and pool/provider IDs
ACCESS_TOKEN_SCOPE_INSUFFICIENT on GetProjectWIF principal missing resourcemanager.projects.getGrant roles/browser to the WIF principal on the target project
Request had insufficient authentication scopesVM missing cloud-platform scopeRecreate the VM with --scopes=cloud-platform or use a VM that has it

On this page