Eliminating Service Account Keys: Implementing Workload Identity Federation in Google Cloud

Alexander Hose on August 16, 20267 min. read

Abstract

Automated Continuous Integration and Continuous Deployment (CI/CD) pipelines interacting with Google Cloud Platform (GCP) historically relied on long-lived Service Account private keys (.json credential files) stored in external Version Control System (VCS) secrets. Empirical cloud security forensics demonstrate that static Service Account keys are among the highest-risk credential types: they possess no default expiration, are vulnerable to build-log exposure, and cannot be centrally invalidated without breaking active pipelines.

This article covers the architectural and cryptographic implementation of Workload Identity Federation (WIF) in Google Cloud. We analyze the OpenID Connect (OIDC) token exchange protocol executed by Google Cloud Security Token Service (STS), evaluate the distinction between direct principalSet resource bindings and Service Account impersonation, dissect the critical role of CEL-based attribute_condition filters, and reference the production policy codified in GCP-IAM-001: Enforce Workload Identity Federation for VCS CI/CD Connections to GCP.

bash
+---------------------------------------------------------------------------------------------------+
|                        GCP CI/CD AUTHENTICATION ARCHITECTURE: STATIC VS. FEDERATED                |
+---------------------------------------------------------------------------------------------------+
|                                                                                                   |
|  1. LEGACY STATIC KEY PATTERN (HIGH RISK)                                                         |
|  [VCS Secrets Store] ---(Long-Lived JSON Key)---> [CI Runner] ---(Direct GCP API)---> [GCP APIs]  |
|  * Risk Profile: Static RSA private key, no automated expiry, high exfiltration surface           |
|                                                                                                   |
|  2. WORKLOAD IDENTITY FEDERATION PATTERN (ZERO STANDING PRIVILEGE)                                |
|  [CI Runner] ----(1. Request OIDC Token)-----> [GitHub/GitLab OIDC Provider]                      |
|  [CI Runner] <---(2. Signed OIDC JWT)--------- [GitHub/GitLab OIDC Provider]                      |
|  [CI Runner] ----(3. STS Token Exchange)-----> [Google Cloud STS API]                             |
|                  * Evaluates Workload Identity Pool & Provider                                    |
|                  * Validates Attribute Condition (e.g., assertion.repository_owner)               |
|  [CI Runner] <---(4. Federated Access Token)-- [Google Cloud STS API]                             |
|  [CI Runner] ----(5. Direct / Impersonation)-> [Target GCP Resources & APIs]                      |
|                                                                                                   |
+---------------------------------------------------------------------------------------------------+

1. Threat Model: Static Service Account JSON Key Exposure

In Google Cloud IAM, creating a user-managed Service Account key produces a 72-byte cryptographic RSA private key encoded inside a JSON payload. Once issued, this key remains valid until explicitly deleted via API or Cloud Console (often lasting years). In enterprise environments, relying on static keys introduces four critical failure modes:

1.1 Log Aggregation & Environment Reflection

Modern build systems execute complex dependency pipelines where debugging tools, test frameworks, or misconfigured container entrypoints dump process environments (printenv / env). Once dumped to standard output, plaintext Service Account keys are indexed by centralized observability pipelines, making them accessible to broad engineering tiers.

1.2 Multi-Tenant Fork-Based Ingestion

Workflows triggered on pull_request events from external forks can execute untrusted code in the build context. If repository secrets are exposed to untrusted pull requests without runner isolation, threat actors can exfiltrate the JSON key directly to external Command & Control (C2) servers.

1.3 Upstream Supply Chain Compromise

Build steps execute package managers (npm, pip, cargo, go) that pull external dependencies. A malicious package executing in pre- or post-install scripts has full access to the runner's process memory, enabling silent exfiltration of all configured secrets.

1.4 Post-Decommission Zombie Credentials

When repositories or application components are decommissioned, their associated Service Account keys frequently remain active in GCP IAM. Without automated expiration, abandoned keys create persistent, unmonitored footholds in enterprise landing zones.


2. Technical Protocol: Workload Identity Federation Flow

Google Cloud Workload Identity Federation replaces static keys by establishing asymmetric cryptographic trust between an external identity provider (such as GitHub Actions or GitLab CI) and Google Cloud STS.

bash
+---------------+              +--------------------+              +--------------------+              +-------------------+
|   CI Runner   |              |  VCS OIDC Provider |              |  Google Cloud STS  |              |  IAM / Resources  |
+---------------+              +--------------------+              +--------------------+              +-------------------+
        |                                |                                   |                                   |
        | 1. Request OIDC Token          |                                   |                                   |
        |------------------------------->|                                   |                                   |
        |                                |                                   |                                   |
        | 2. Issue Signed JWT (RS256)    |                                   |                                   |
        |<-------------------------------|                                   |                                   |
        |                                                                    |                                   |
        | 3. Exchange Token: STS Token Exchange API (WIF Pool + Provider)    |                                   |
        |------------------------------------------------------------------->|                                   |
        |                                                                    |                                   |
        |                                | 4. Fetch JWKS & Validate Signature|                                   |
        |                                |<----------------------------------|                                   |
        |                                |---------------------------------->|                                   |
        |                                                                    |                                   |
        |                                                                    | 5. Evaluate CEL Attribute         |
        |                                                                    |    Condition & Map Claims         |
        |                                                                    |                                   |
        | 6. Return Federated Short-Lived Access Token (STS Token)           |                                   |
        |<-------------------------------------------------------------------|                                   |
        |                                                                                                        |
        | 7. (Optional) Impersonate Service Account via roles/iam.workloadIdentityUser                           |
        |------------------------------------------------------------------------------------------------------->|
        |                                                                                                        |
        | 8. Execute Scoped Cloud API Calls (Secret Manager, Compute, GKE, Storage)                              |
        |------------------------------------------------------------------------------------------------------->|

Protocol Execution Phases:

  1. OIDC Token Minting: When a workflow job triggers with id-token: write permissions, the GitHub Actions runner requests a cryptographically signed JSON Web Token (JWT) from https://token.actions.githubusercontent.com.
  2. STS Endpoint Resolution: The runner submits the token to the Google Cloud Security Token Service (sts.googleapis.com) specifying the Workload Identity Pool and Provider identifier.
  3. Cryptographic Validation: Google STS validates the issuer (iss), audience (aud), and cryptographic signature against GitHub's public JSON Web Key Set (/.well-known/jwks.json).
  4. Attribute Mapping & Condition Verification: STS extracts the configured claims from the JWT and evaluates the Common Expression Language (CEL) attribute_condition.
  5. Credential Generation: Upon validation, STS returns a temporary Google OAuth2 access token valid for up to 3,600 seconds.

3. The Security Boundary: Attribute Conditions

A widespread architectural misconfiguration in Workload Identity Federation is omitting the Attribute Condition (attribute_condition).

When a Workload Identity Pool Provider is created without an attribute condition, any GitHub Actions workflow in the world can present a valid GitHub OIDC token and exchange it for a federated Google credential if they know your Project Number and Pool ID.

The Decoded GitHub OIDC Claims Payload:

json
{
  "iss": "https://token.actions.githubusercontent.com",
  "sub": "repo:AlexanderHose/gcp-deployment:ref:refs/heads/main",
  "aud": "https://github.com/AlexanderHose",
  "repository": "AlexanderHose/gcp-deployment",
  "repository_owner": "AlexanderHose",
  "repository_owner_id": "12799633",
  "actor": "AlexanderHose",
  "ref": "refs/heads/main",
  "ref_type": "branch",
  "event_name": "push",
  "job_workflow_ref": "AlexanderHose/gcp-deployment/.github/workflows/deploy.yml@refs/heads/main"
}

Enforcing Strict Attribute Conditions

In your Workload Identity Pool Provider configuration, you must define an attribute_condition that restricts token exchange to your specific organization or repository:

hcl
resource "google_iam_workload_identity_pool_provider" "github_provider" {
  workload_identity_pool_id          = "github-pool"
  workload_identity_pool_provider_id = "github-provider"

  attribute_mapping = {
    "google.subject"             = "assertion.sub"
    "attribute.actor"            = "assertion.actor"
    "attribute.repository"       = "assertion.repository"
    "attribute.repository_owner" = "assertion.repository_owner"
  }

  # MANDATORY SECURITY BOUNDARY:
  # Reject any token where repository_owner does not match your enterprise organization
  attribute_condition = "assertion.repository_owner == 'AlexanderHose'"

  oidc {
    issuer_uri = "https://token.actions.githubusercontent.com"
  }
}

4. Production GitHub Actions Implementation

Below is the hardened production GitHub Actions workflow configuration utilizing google-github-actions/auth@v3:

yaml
name: Deploy Infrastructure to Google Cloud

on:
  push:
    branches: [ "main" ]

permissions:
  id-token: write  # Mandatory for minting OIDC tokens
  contents: read   # Mandatory for repository checkout

jobs:
  deploy:
    name: Terraform Apply
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Code
        uses: actions/checkout@v7

      - name: Authenticate to Google Cloud
        id: auth
        uses: google-github-actions/auth@v3
        with:
          project_id: 'security-alexanderhose'
          workload_identity_provider: 'projects/243190957191/locations/global/workloadIdentityPools/github-pool/providers/github-provider'

      - name: Setup Google Cloud SDK
        uses: google-github-actions/setup-gcloud@v2

      - name: Verify Identity
        run: |
          gcloud auth list
          echo "Authenticated as: $(gcloud config get-value account)"

5. Organizational Governance: Enforcing Key Disablement

To ensure that teams cannot bypass Workload Identity Federation by generating static Service Account keys, security architects must enforce the Google Cloud Organization Policy constraint:

yaml
# Enforce across Organization or Folder landing zone roots
name: organizations/ORG_ID/policies/iam.disableServiceAccountKeyCreation
spec:
  rules:
  - enforce: true

Applying this constraint globally blocks all calls to CreateServiceAccountKey, preventing engineers and automation scripts from generating user-managed private keys and cementing Workload Identity Federation as the sole compliant authentication path.


Conclusion & Implementation Reference

Workload Identity Federation transforms CI/CD authentication from a high-risk static secret model into an auditable, ephemeral zero-trust architecture. By enforcing cryptographically validated OIDC tokens, scoping access via CEL attribute_condition expressions, and applying preventative Organization Policies, organizations eliminate private key exfiltration vectors across their entire Google Cloud footprint.

For the formal control specification, automated audit CLI scripts, and detection logic, refer to:

šŸ‘‰ GCP-IAM-001: Enforce Workload Identity Federation for VCS CI/CD Connections to GCP

šŸ‘‰ AWS-IAM-005: Enforce OIDC for VCS CI/CD Connections to AWS

We value your privacy

We use analytics cookies to understand how visitors interact with our site and to improve the user experience. You can choose to accept or decline these cookies.