From Long-Lived Credentials to Workload Identity: Building Zero-Trust CI/CD
Why static service account keys are a massive security risk, and how to implement Workload Identity Federation for CI/CD pipelines.
The Problem with Static Credentials
For years, the standard method for allowing a CI/CD pipeline (like GitHub Actions or Jenkins) to deploy resources to a cloud provider was simple: generate a Service Account Key, download the JSON file, and save it as a "secret" in the CI/CD platform.
While this approach works, it introduces catastrophic security risks:
- Long-Lived Keys: A downloaded JSON key is valid indefinitely until manually revoked. If an attacker extracts it from a pipeline or a developer's laptop, they have persistent access to your cloud environment.
- Secret Sprawl: Keys get copied into local
.envfiles, shared in Slack channels for debugging, and accidentally committed to repositories. - Rotation Overhead: Security compliance (like SOC2) often requires rotating secrets every 90 days. Manually updating keys across dozens of GitHub repositories is a massive operational burden.
In modern Platform Engineering, relying on static credentials for CI/CD is an anti-pattern. The solution is OpenID Connect (OIDC) and Workload Identity Federation (WIF).
Enter Workload Identity Federation
Workload Identity Federation allows external identities (like a GitHub Action runner or an on-premise Jenkins server) to authenticate to Google Cloud without a service account key.
Instead of holding a password, the CI/CD system proves its identity using a short-lived, cryptographically signed token (OIDC). Google Cloud verifies this token and grants temporary, short-lived access to a specific Cloud IAM Service Account.
The Before and After Architecture
In the Before architecture, the CI/CD runner possesses a permanent key. If the runner is compromised, the cloud is compromised.
In the After architecture, the CI/CD runner possesses a short-lived OIDC token. It trades this token with GCP for temporary credentials that expire in an hour. No permanent keys ever leave the cloud.
How It Works Under the Hood
Implementing WIF involves establishing a trust relationship between your Cloud Provider (GCP) and your Identity Provider (GitHub).
- The Request: When a GitHub Action runs, it requests an OIDC token from GitHub's authentication server. This token contains claims about the job (e.g., repository name, branch, actor).
- The Exchange: The Action sends this token to Google Cloud's Security Token Service (STS).
- Verification: GCP verifies the cryptographic signature of the token against GitHub's public OIDC endpoints.
- Attribute Mapping: GCP checks the configured Workload Identity Pool to see if the claims in the token (e.g., "Is this coming from the
my-org/my-reporepository?") match the allowed conditions. - Impersonation: If the conditions match, GCP returns a short-lived OAuth 2.0 access token that allows the GitHub Action to impersonate a specific Service Account.
Implementation Example: GitHub Actions to GCP
Here is how you define the trust relationship using Terraform. Notice how we use attribute mapping to restrict access to a specific repository.
hcl# Create the Workload Identity Pool resource "google_iam_workload_identity_pool" "github_pool" { project = var.project_id workload_identity_pool_id = "github-actions-pool" display_name = "GitHub Actions Pool" } # Add GitHub as an OIDC Provider to the Pool resource "google_iam_workload_identity_pool_provider" "github_provider" { project = var.project_id workload_identity_pool_id = google_iam_workload_identity_pool.github_pool.workload_identity_pool_id workload_identity_pool_provider_id = "github-provider" oidc { issuer_uri = "https://token.actions.githubusercontent.com" } # Map the claims from GitHub's token into GCP attributes attribute_mapping = { "google.subject" = "assertion.sub" "attribute.repository" = "assertion.repository" "attribute.ref" = "assertion.ref" } }
The Critical Security Boundary: Attribute Conditions
The code above creates the connection, but we must explicitly authorize which repositories can impersonate our Service Account. This is the most crucial step.
hcl# Allow ONLY the specific repository to impersonate the deployment Service Account resource "google_service_account_iam_member" "workload_identity_user" { service_account_id = google_service_account.deploy_sa.name role = "roles/iam.workloadIdentityUser" # This string ensures only actions running in "my-org/my-infra-repo" are authorized member = "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.github_pool.name}/attribute.repository/my-org/my-infra-repo" }
If you misconfigure the member string and grant access to the entire Identity Pool, any GitHub repository in the world could potentially authenticate as your Service Account. Always use attribute.repository or attribute.sub to enforce strict boundaries.
Configuring the Pipeline
Finally, update your GitHub Actions workflow to use the Google Cloud Auth action. Notice that there are zero secrets required in this workflow block!
yamljobs: deploy: runs-on: ubuntu-latest permissions: contents: 'read' id-token: 'write' # Required to request the OIDC token steps: - uses: actions/checkout@v3 - id: 'auth' name: 'Authenticate to Google Cloud' uses: 'google-github-actions/auth@v1' with: workload_identity_provider: 'projects/123456789/locations/global/workloadIdentityPools/github-actions-pool/providers/github-provider' service_account: 'deploy-sa@my-project.iam.gserviceaccount.com' - name: 'Run Deployment' run: |- gcloud compute instances list
Common Mistakes & Failure Modes
- Missing
id-token: writepermission: GitHub workflows will fail to generate the OIDC token if this permission is missing at the job level. - Overly Permissive Mappings: Failing to restrict the
workloadIdentityUserbinding to a specific repository, thereby opening the service account to other organizations. - Token Lifetime: The default token lifetime is 1 hour. If your deployment pipeline (e.g., a massive Terraform apply or database migration) takes longer than 60 minutes, the token will expire mid-flight. You must configure the
token_lifetimeparameter in the auth action if you need more time.
Conclusion
Transitioning from static service account keys to Workload Identity Federation is one of the highest-impact security improvements a Platform Engineering team can make.
By utilizing OIDC, you eliminate the risks of key leakage, remove the operational burden of key rotation, and implement a true zero-trust architecture where infrastructure access is granted dynamically, verified cryptographically, and revoked automatically.
Continue Reading
Building Production-Ready GKE Platforms with Terraform
How to move from manually created Kubernetes infrastructure toward reusable, version-controlled platform infrastructure.
Read article →Cloud InfrastructurePrivate GKE Networking: The Problems You Discover After Going Production
A deep dive into private GKE architecture, IP range exhaustion, Cloud NAT, and internal connectivity troubleshooting.
Read article →