Essay · Infrastructure as code

The same keyless CI idea, this time on Google Cloud

Build Workload Identity Federation with Terraform and let GitHub Actions impersonate a service account safely.

The earlier AWS setup established the useful pattern: CI should present a short-lived identity, not carry a permanent key. Google Cloud implements the same idea through Workload Identity Federation.

A service-account JSON key in GitHub Secrets works, but it creates three chores: prevent leakage, rotate the key, and eventually discover that somebody forgot to rotate the key. Federation removes the private key entirely.

Pool, provider, service account #

The configuration has three parts:

  1. A Workload Identity Pool is the namespace that accepts external identities.
  2. A Pool Provider defines GitHub as the issuer and maps claims from its token.
  3. A service account binding allows matching identities to impersonate one service account.
resource "google_iam_workload_identity_pool" "github" {
  workload_identity_pool_id = "github-actions"
  display_name              = "GitHub Actions Pool"
}
 
resource "google_iam_workload_identity_pool_provider" "github" {
  workload_identity_pool_id          = google_iam_workload_identity_pool.github.workload_identity_pool_id
  workload_identity_pool_provider_id = "github-provider"
 
  oidc {
    issuer_uri = "https://token.actions.githubusercontent.com"
  }
 
  attribute_mapping = {
    "google.subject"       = "assertion.sub"
    "attribute.repository" = "assertion.repository"
    "attribute.actor"      = "assertion.actor"
    "attribute.aud"        = "assertion.aud"
  }
 
  attribute_condition = "attribute.repository == 'your-org/your-repo'"
}

The condition is not decorative. It prevents an otherwise valid GitHub identity from a different repository using this provider.

Bind only the intended repository #

Create the service account and grant the repository’s principal set permission to impersonate it:

resource "google_service_account" "github_actions" {
  account_id   = "terraform-apply"
  display_name = "GitHub Actions Terraform Apply"
}
 
resource "google_service_account_iam_member" "github" {
  service_account_id = google_service_account.github_actions.name
  role               = "roles/iam.workloadIdentityUser"
  member = "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.github.name}/attribute.repository/your-org/your-repo"
}

The service account still needs narrowly scoped permissions for its actual work. Workload Identity Federation answers who may become the account; IAM roles answer what that account may do. Mixing those questions produces policies nobody wants to debug during an incident.

Authenticate from the workflow #

permissions:
  contents: read
  id-token: write
 
steps:
  - uses: actions/checkout@v4
  - name: Authenticate to Google Cloud via OIDC
    uses: google-github-actions/auth@v2
    with:
      project_id: your-gcp-project-id
      workload_identity_provider: projects/123/locations/global/workloadIdentityPools/github-actions/providers/github-provider
      service_account: [email protected]

AWS calls the destination an IAM role; Google Cloud uses a pool, provider, and service-account impersonation. The resource names differ, but the architecture is now familiar: verify GitHub’s signed claims, restrict the repository, mint short-lived credentials, and keep long-lived keys out of CI.

References #

Related articles