# GCP Workload Identity Federation


Identity Federation lets an attached VM mint short-lived exe.dev OIDC tokens.
Google Cloud trusts those tokens through a Workload Identity Pool provider, then
exchanges them for access to a service account. Use this instead of storing
Google Cloud service account keys on the VM.

Create the exe.dev integration in the web UI. Run the Google Cloud commands
from a machine with `gcloud`.

## Setup

### Set values

One-time Google Cloud setup values. Choose these once for the Google Cloud
project:

```
export PROJECT_ID=example-gcp-project
export POOL_ID=exe-dev
export PROVIDER_ID=exe-dev

export PROJECT_NUMBER="$(gcloud projects describe "$PROJECT_ID" --format='value(projectNumber)')"
export GCP_POOL_RESOURCE="projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${POOL_ID}"
export GCP_PROVIDER_RESOURCE="${GCP_POOL_RESOURCE}/providers/${PROVIDER_ID}"
export GCP_PROVIDER_AUDIENCE="https://iam.googleapis.com/${GCP_PROVIDER_RESOURCE}"
```

Per-service-account and per-exe.dev-integration values. Choose these for each
workload:

```
export INTEGRATION_NAME=gcpwif
export SERVICE_ACCOUNT_NAME=exe-dev-demo

export SERVICE_ACCOUNT_EMAIL="${SERVICE_ACCOUNT_NAME}@${PROJECT_ID}.iam.gserviceaccount.com"
```

### Add the exe.dev integration

Open the [Integrations page](/integrations), choose `Identity Federation`, and
select `GCP`.

- `Name`: `gcpwif`
- `Project ID`: the `PROJECT_ID` value from above
- `Project number`: the `PROJECT_NUMBER` value from above
- `Pool ID`: the `POOL_ID` value from above
- `Provider ID`: the `PROVIDER_ID` value from above
- `Service account`: the `SERVICE_ACCOUNT_EMAIL` value from above
- `Attach to`: the VM or tag that should use this service account

Copy the `Unique generated subject`, then click `Run`. The Google Cloud IAM
binding below must use that exact `sub`.

<img src="/docs/integrations/gcp-wif/identity-federation-add-gcp.png" alt="Identity Federation modal configured for a GCP Workload Identity provider" width="100%"/>

Set the copied subject in your shell before running the Google Cloud commands:

```
export EXE_WIF_SUBJECT=sub-copy-from-the-integration
```

### Configure Google Cloud

```
gcloud services enable \
  iam.googleapis.com \
  sts.googleapis.com \
  iamcredentials.googleapis.com \
  cloudresourcemanager.googleapis.com \
  --project "$PROJECT_ID"

gcloud iam workload-identity-pools create "$POOL_ID" \
  --project "$PROJECT_ID" \
  --location global \
  --display-name "exe.dev"

gcloud iam workload-identity-pools providers create-oidc "$PROVIDER_ID" \
  --project "$PROJECT_ID" \
  --location global \
  --workload-identity-pool "$POOL_ID" \
  --display-name "exe.dev" \
  --issuer-uri "https://exe.dev" \
  --allowed-audiences "$GCP_PROVIDER_AUDIENCE" \
  --attribute-mapping "google.subject=assertion.sub"

gcloud iam service-accounts create "$SERVICE_ACCOUNT_NAME" \
  --project "$PROJECT_ID" \
  --display-name "exe.dev demo workload"

gcloud iam service-accounts add-iam-policy-binding "$SERVICE_ACCOUNT_EMAIL" \
  --project "$PROJECT_ID" \
  --role "roles/iam.workloadIdentityUser" \
  --member "principal://iam.googleapis.com/${GCP_POOL_RESOURCE}/subject/${EXE_WIF_SUBJECT}"
```

Grant the service account only the roles your workload needs. For example:

```
gcloud projects add-iam-policy-binding "$PROJECT_ID" \
  --member "serviceAccount:${SERVICE_ACCOUNT_EMAIL}" \
  --role "roles/storage.objectViewer"
```

## Use it from the VM

From a VM that has the integration attached, use `/metadata` to read the Google
Cloud values you entered in the integration:

Choose the integration name:

```
export INTEGRATION_NAME=gcpwif

export EXE_WIF_URL="https://${INTEGRATION_NAME}.int.exe.xyz"
export EXE_WIF_METADATA_FILE="/tmp/exe-${INTEGRATION_NAME}-gcp-wif-metadata.json"
export GOOGLE_APPLICATION_CREDENTIALS="$HOME/.config/gcloud/exe-${INTEGRATION_NAME}-gcp-wif.json"

mkdir -p "$(dirname "$GOOGLE_APPLICATION_CREDENTIALS")"
curl -fsS "$EXE_WIF_URL/metadata" > "$EXE_WIF_METADATA_FILE"
```

For a team integration, use `https://${INTEGRATION_NAME}.team.exe.xyz` for
`EXE_WIF_URL` instead.

```
export PROJECT_NUMBER="$(jq -r .project_number "$EXE_WIF_METADATA_FILE")"
export POOL_ID="$(jq -r .pool_id "$EXE_WIF_METADATA_FILE")"
export PROVIDER_ID="$(jq -r .provider_id "$EXE_WIF_METADATA_FILE")"
export SERVICE_ACCOUNT_EMAIL="$(jq -r .service_account "$EXE_WIF_METADATA_FILE")"
export GCP_PROVIDER_RESOURCE="projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${POOL_ID}/providers/${PROVIDER_ID}"

gcloud iam workload-identity-pools create-cred-config "$GCP_PROVIDER_RESOURCE" \
  --service-account "$SERVICE_ACCOUNT_EMAIL" \
  --credential-source-url "$EXE_WIF_URL/token" \
  --credential-source-type json \
  --credential-source-field-name token \
  --output-file "$GOOGLE_APPLICATION_CREDENTIALS"

gcloud auth login --cred-file="$GOOGLE_APPLICATION_CREDENTIALS"
```

Smoke-test:

```
gcloud auth print-access-token >/dev/null &&
  echo "GCP Workload Identity Federation is working"
```

For client libraries, keep `GOOGLE_APPLICATION_CREDENTIALS` in the workload
environment. The credential config tells Google auth libraries and `gcloud` to
fetch fresh exe.dev tokens from `GET /token`; no local service account key or
exe.dev token file is needed.

## Use cases

Common things to run from the VM once the federated credentials are active.
Grant the service account only the roles each use case needs.

### Cloud Storage: artifacts and data

Sync build outputs, datasets, or static sites to a bucket:

```
gcloud storage rsync --recursive ./dist "gs://${BUCKET_NAME}/"
```

Grant `roles/storage.objectAdmin` scoped to the bucket.

### Artifact Registry: containers and packages

Push container images, or publish to private npm, pip, or Maven repositories
in the same registry:

```
gcloud auth configure-docker "${REGION}-docker.pkg.dev"
docker push "${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO}/${IMAGE}:${TAG}"
```

Grant `roles/artifactregistry.writer`.

### BigQuery: queries and data jobs

Run queries and load jobs, or run dbt: its BigQuery `oauth` method uses
application default credentials, which the WIF credential file provides.

```
bq query --use_legacy_sql=false "SELECT COUNT(*) FROM \`${PROJECT_ID}.${DATASET}.${TABLE}\`"
```

Grant `roles/bigquery.jobUser` plus dataset-level access.

### Cloud SQL: databases via the Auth Proxy

The Cloud SQL Auth Proxy connects over the instance's public IP with IAM
authorization and TLS; no authorized networks or VPC needed. Connect to
localhost with your normal client or migration tool:

```
cloud-sql-proxy "${PROJECT_ID}:${REGION}:${INSTANCE}" &
psql "host=127.0.0.1 dbname=${DB_NAME} user=${DB_USER}"
```

Grant `roles/cloudsql.client`.

### Vertex AI: model inference

Call Gemini and other models with the federated credentials. Client libraries
pick up `GOOGLE_APPLICATION_CREDENTIALS` automatically:

```
curl -fsS -X POST \
  -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  -H "Content-Type: application/json" \
  -d '{"contents":[{"role":"user","parts":[{"text":"Hello"}]}]}' \
  "https://${REGION}-aiplatform.googleapis.com/v1/projects/${PROJECT_ID}/locations/${REGION}/publishers/google/models/${MODEL}:generateContent"
```

Grant `roles/aiplatform.user`.

### Terraform / IaC: state and deploys

Run `terraform plan` and `terraform apply` with the GCS state backend; the
`google` provider reads the same credential file via application default
credentials:

```
terraform init -backend-config="bucket=${STATE_BUCKET}"
terraform apply
```

Grant access to the state bucket plus the roles for whatever the config
manages.

## Keep it narrow

- Use one exe.dev WIF integration per service account or workload.
- Attach the integration only to the VM or tag that needs it.
- Bind `roles/iam.workloadIdentityUser` to the exact exe.dev subject.
- Give the Google Cloud service account only the roles the workload needs.
- Do not create or store Google Cloud service account keys as a fallback.

For additional exe.dev WIF integrations in the same Google Cloud project, you
usually reuse the same Workload Identity Pool and OIDC provider. Use the same
provider audience, create the new exe.dev integration, copy its `Unique
generated subject`, and grant that subject access to the service account it
should impersonate. Create a new pool or provider only when you want a separate
trust boundary.

Google references:

- [Workload Identity Federation with other providers](https://cloud.google.com/iam/docs/workload-identity-federation-with-other-providers)
- [Create an OIDC workload identity pool provider](https://cloud.google.com/sdk/gcloud/reference/iam/workload-identity-pools/providers/create-oidc)
- [Create credential configurations](https://cloud.google.com/sdk/gcloud/reference/iam/workload-identity-pools/create-cred-config)
