cd ../blogs
Cloud Security

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.

10 min read
PI
Prathamesh Inde
GH
OIDC TOKEN
WIF
TEMP CRED
GCP

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:

  1. 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.
  2. Secret Sprawl: Keys get copied into local .env files, shared in Slack channels for debugging, and accidentally committed to repositories.
  3. 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

BEFORE (Risky)
GitHub Actions
↓ holds static JSON key ↓
Service Account Key
GCP Resources
AFTER (Zero-Trust)
GitHub Actions
↓ OIDC Token ↓
Workload Identity Pool
↓ Short-lived Credential ↓
GCP Resources

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).

  1. 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).
  2. The Exchange: The Action sends this token to Google Cloud's Security Token Service (STS).
  3. Verification: GCP verifies the cryptographic signature of the token against GitHub's public OIDC endpoints.
  4. 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-repo repository?") match the allowed conditions.
  5. 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!

yaml
jobs:
  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: write permission: 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 workloadIdentityUser binding 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_lifetime parameter 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.

OIDCWorkload Identity FederationCI/CDGCPIAMSecurityZero Trust