Killing the Service Account Key: Enforcing Workload Identity Federation for GitHub Actions

November 20, 2021

Let's be real for a second. We all have that one "Secret" in our GitHub repository settings. You know the one. It's usually named GCP_SA_KEY or GCP_CREDENTIALS. It contains a base64-encoded JSON blob that gives a Service Account god-mode access to your production project.

It's the security equivalent of taping your house key under the doormat. Actually, it's worse—it's like photocopying that key and leaving it on a park bench, hoping only your friends pick it up.

If you are still downloading JSON keys and stuffing them into CI/CD secrets, stop. Seriously, stop right now.

Google recently (and finally!) made Workload Identity Federation generally available and easy to integrate with GitHub Actions. It allows us to kill the long-lived JSON key once and for all. Let's talk about why you need to do this today.


The Problem: The JSON Key of Doom

For years, the standard workflow was:

  1. Create a Service Account (SA) in GCP.
  2. Generate a key.json file.
  3. Upload it to GitHub Secrets.
  4. Pray you didn't accidentally commit it to the repo (I've seen it happen to the best of us).
  5. Forget to rotate it for 3 years.

If that key leaks? Game over. The attacker has persistent access until you notice and revoke it.

The Solution: "My Cloud Trusts Your Cloud"

Workload Identity Federation (WIF) flips the script. Instead of a secret key, we use trust.

Here is the concept: Google Cloud trusts GitHub as an identity provider. When your GitHub Action runs, it presents a shiny ID badge (an OIDC token) signed by GitHub. GCP looks at the badge, checks if it trusts that specific repo, and swaps it for a short-lived GCP access token.

Benefits:

  • No Secrets: You literally do not store any credentials in GitHub. None.
  • Short-Lived: The token expires in an hour. If it leaks, it's useless shortly after.
  • Granular: You can lock access down to a specific branch or environment.

How it Works (The Handshake)

We are using the OpenID Connect (OIDC) protocol. It sounds scary, but the flow is actually elegant.

Workload Identity Federation - OIDC Handshake Flow

The Setup: 3 Steps to Freedom

We are moving fast here, but here is the gist of the Terraform/Console work required.

  1. Create the Pool & Provider: You create a Workload Identity Pool in GCP and add a Provider for GitHub.

    • Issuer URI: https://token.actions.githubusercontent.com
    • Attribute Mapping: google.subject=assertion.sub, attribute.repository=assertion.repository
  2. Allow Impersonation (The "Binding"): You don't give the pool direct permissions. You allow the pool to impersonate your existing Service Account.

    • Member: principalSet://iam.googleapis.com/projects/{PROJECT_NUM}/locations/global/workloadIdentityPools/{POOL}/attribute.repository/{ORG/REPO}
    • Role: roles/iam.workloadIdentityUser
  3. Update GitHub Actions: Use the google-github-actions/auth action. Notice: No keys.

    - id: 'auth'
      name: 'Authenticate to Google Cloud'
      uses: 'google-github-actions/auth@v0' # v0 is the current hotness
      with:
        workload_identity_provider: 'projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider'
        service_account: 'my-service-account@my-project.iam.gserviceaccount.com'
    
    - name: 'Use gcloud'
      run: 'gcloud info'

Rad's "Gotchas" Corner ⚠️

I spent an entire afternoon debugging this so you don't have to.

1. The "Subject" Mapping Mistake When you set up the attribute mapping, you must map google.subject to assertion.sub. If you try to get fancy and map it to something else initially, the handshake fails. Keep the primary subject standard.

2. The Repository Case Sensitivity The OIDC token claims from GitHub are strict. If your repo is Rad/Cool-App but you bind the IAM policy to rad/cool-app, it might fail depending on how you set up the attribute condition. Always use lowercase in your IAM bindings just to be safe.

3. Enable the APIs You need to enable the iamcredentials.googleapis.com API on the project. It seems obvious, but the error message you get if it's disabled is incredibly vague.


Key Takeaway

We are responsible for the "Supply Chain" now. If your CI/CD is compromised, your production environment is compromised.

Removing long-lived keys is the single highest-impact security improvement you can make this week. It takes about 30 minutes to set up, and you never have to worry about key rotation again.

So, go ahead. Delete that GCP_SA_KEY secret. It feels really good.

Catch you in the next one!

-Rad