alt text

In the world of CI/CD, one thing we are all tired of is managing long-lived service account keys. They are hard to rotate.. risky to store, and can lead to serious breaches if leaked! 😿

In this post, I will be showing how to set up GitHub Actions + Google Cloud Workload Identity Federation (WIF) to enable secure, short-lived authentication, with no secrets stored anywhere. πŸš«πŸ”‘

🚨 The Problem with Static Secrets

It is still common to authenticate from GitHub Actions to GCP using service account keys stored as GitHub secrets:

echo "${{ secrets.GCP_KEY }}" > key.json
gcloud auth activate-service-account --key-file=key.json

This approach works, but it has downsides such as:

  • πŸ”‘ Long-lived credentials: valid for months or years
  • πŸ’₯ Risk of leakage
  • πŸ” Manual rotation or forgotten rotation 😬
  • πŸ“¦ Secrets management overhead

βœ… The Better Way: Workload Identity Federation πŸ’Œ

Instead of storing credentials, we can let GitHub Actions request an identity token at runtime, and have GCP validate that token and issue a short-lived access token.

This is made possible using:

πŸ› οΈ How It Works

Let’s walk through the high-level flow!

How GitHub Actions OIDC Works with GCP Source: Google Cloud Blog

  • βœ… GitHub Actions requests an OIDC token signed by GitHub
  • 🌍 This token is sent to GCP’s Security Token Service (STS)
  • πŸ” GCP verifies the token and exchanges it for a short-lived access token
  • πŸš€ Your Action uses this token to call GCP APIs & no secrets involved

🧩 Setting It Up (High-Level)

  1. Create a Workload Identity Pool & Provider in GCP
gcloud iam workload-identity-pools create github-pool \
  --location="global" \
  --display-name="GitHub Actions Pool"

gcloud iam workload-identity-pools providers create-oidc github-provider \
  --location="global" \
  --workload-identity-pool="github-pool" \
  --display-name="My GitHub Provider" \
  --attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository" \
  --issuer-uri="https://token.actions.githubusercontent.com"
  1. Create a Service Account and Allow GitHub to Impersonate It
gcloud iam service-accounts create gha-deploy

gcloud iam service-accounts add-iam-policy-binding gha-deploy@YOUR_PROJECT.iam.gserviceaccount.com \
  --role="roles/iam.workloadIdentityUser" \
  --member="principalSet://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/github-pool/attribute.repository/my-org/my-repo"
  1. Configure GitHub Actions Please see: https://github.com/google-github-actions/auth

In your workflow:

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: 'actions/checkout@v4'

      - name: Authenticate to GCP
        uses: google-github-actions/auth@v2
        with:
          token_format: "access_token"
          workload_identity_provider: "projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/github-pool/providers/github-provider"
          service_account: "gha-deploy@YOUR_PROJECT.iam.gserviceaccount.com"

Now you now have short-lived credentials, scoped precisely to your repository and branch 😎

πŸ” Locking It Down: Use GitHub OIDC Attributes for Fine-Grained Access

GitHub’s OIDC tokens come with a set of useful claims. When you configure your provider in GCP, you can map these claims to attributes, and then use those attributes to control who gets access to impersonate your service account.

πŸ“‹ Common GitHub Claims

ClaimDescription
sube.g. repo:my-org/my-repo:ref:refs/heads/main
repositorymy-org/my-repo
refrefs/heads/main, refs/tags/v1.0.0, etc
workflow.github/workflows/deploy.yml
job_workflow_refmy-org/my-repo/.github/workflows/deploy.yml@refs/heads/main
actorThe GitHub user who triggered the workflow 😁

πŸ—ΊοΈ Example Attribute Mapping

You can map these into usable GCP attributes when creating the provider:

--attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository,attribute.ref=assertion.ref,attribute.workflow=assertion.workflow,attribute.actor=assertion.actor"

πŸ”’ Use Conditions to Restrict Access

Now you can enforce specific conditions when assigning access:

🌟 Restrict to a specific repo

principalSet://.../attribute.repository/my-org/my-repo

🌟 Restrict to a repo and branch

attribute.repository == "my-org/my-repo" &&
attribute.ref == "refs/heads/main"

🌟 Restrict to a specific workflow file

attribute.workflow == ".github/workflows/deploy.yml"

🌟 Restrict to a specific actor (user/bot)

attribute.actor == "deploy-bot"

🧱 Putting It in Practice

gcloud iam service-accounts add-iam-policy-binding [email protected] \
  --role="roles/storage.admin" \
  --member="principalSet://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/github-pool/attribute.repository/my-org/my-repo" \
  --condition="expression=attribute.ref=='refs/heads/main' && attribute.workflow=='.github/workflows/deploy.yml'"

Now only your deploy.yml workflow on the main branch can assume this identity. πŸ”’βœ¨

🧠 Federation β‰  Impersonation

Workload Identity Federation lets GCP trust an external identity (like GitHub) without the external system needing any GCP credentials. Think of it like: β€œGitHub says this workflow is running, and GCP trusts that, so GCP hands back a short-lived access token.” 🀝🏻

It’s similar to assuming a role in AWS, but uses open standards like OIDC and token exchange (STS).

🎯 Benefits Recap

  • βœ… No stored secrets
  • βœ… Short-lived, scoped credentials
  • βœ… Fine-grained access with identity claims
  • βœ… Multi-cloud ready (also available in AWS, Azure)

πŸ™Œ Final Thoughts

This pattern is fast becoming the new standard for CI/CD authentication in cloud-native platforms. It’s simpler, safer, and aligns well with platform engineering principles.

If your team is still managing static service account keys in CI β€” give this a try πŸš€