Hardening GCP Service Accounts: Blue Team Defense Guide

Hardening GCP Service Accounts: Blue Team Defense Guide

Overview & Defensive Context#

In this morning's architectural analysis, we explored how adversaries abuse Service Account Impersonation in Google Cloud Platform (GCP) to execute keyless privilege escalation. By targeting the IAM Service Account Credentials API (iamcredentials.googleapis.com), a low-privilege user or compromised workload possessing roles/iam.serviceAccountTokenCreator can invoke generateAccessToken to mint short-lived OAuth2 access tokens for higher-privilege service accounts. Furthermore, through multi-hop delegation chains (delegates), attackers can pivot across project perimeters to assume Project Editor or Owner privileges without ever generating or handling static JSON keys on disk.

Defending enterprise Google Cloud estates against service account impersonation abuse exposes significant structural weaknesses in conventional cloud security postures:

  • The Invisibility of Keyless Exploitation: Standard cloud detection engineering heavily emphasizes tracking static service account keys (.json private key files) via Git commit scanners (e.g., TruffleHog) and Cloud Audit Logs for CreateServiceAccountKey. Because impersonation operates entirely keyless via Google's centralized OAuth2 infrastructure, token minting bypasses file-based secret scanners entirely, leaving zero cryptographic artifacts on endpoints or developer workstations.
  • The Danger of Project-Level Role Bindings: A widespread cloud configuration error is granting roles/iam.serviceAccountTokenCreator or roles/iam.serviceAccountUser at the Project, Folder, or Organization level. In GCP's resource hierarchy, permissions granted at a parent node inherit downward. A project-level serviceAccountTokenCreator binding grants the caller the capability to impersonate every service account in the project, including the default Compute Engine service account (PROJECT_NUMBER-compute@developer.gserviceaccount.com), which historically possessed broad Editor rights by default.
  • Attribution Masking in Cloud Audit Logs: When an attacker executes administrative actions using an impersonated token, the acting principal recorded in standard SIEM ingestion pipelines is the target service account (protoPayload.authenticationInfo.principalEmail), not the attacker. Unless security analysts explicitly configure their SIEM to extract and correlate the nested protoPayload.authenticationInfo.serviceAccountDelegationInfo array, malicious activity is routinely misattributed to legitimate automation workloads.
  • Stateless Token Invalidation Challenges: Google Cloud OAuth2 access tokens are cryptographically signed, bearer-style credentials valid for up to 3,600 seconds (1 hour). Once minted, an access token cannot be individually revoked via a single API call; incident responders must execute aggressive service account disabling or project-level role stripping to neutralize active attacker sessions.

[!WARNING] In Google Cloud, a service account is both an identity and an access-controlled resource. Treating service accounts solely as machine identities while ignoring resource-level IAM policies creates unmonitored privilege escalation bridges across your cloud organization.


Architecture Hardening#

Securing GCP service accounts against unauthorized impersonation requires an enterprise defense-in-depth framework: Resource-Level Least-Privilege Scoping, Organization Policy Constraints, Context-Aware IAM Conditions, and VPC Service Controls.

Defense Architecture Flowchart#

flowchart TD
    classDef client fill:#1e293b,stroke:#ef4444,stroke-width:2px,color:#f8fafc
    classDef gate fill:#0f172a,stroke:#3b82f6,stroke-width:2px,color:#93c5fd
    classDef iam fill:#064e3b,stroke:#10b981,stroke-width:2px,color:#6ee7b7
    classDef audit fill:#4c1d95,stroke:#8b5cf6,stroke-width:2px,color:#ddd6fe
    classDef drop fill:#450a0a,stroke:#dc2626,stroke-width:2px,color:#fca5a5
    classDef pass fill:#065f46,stroke:#34d399,stroke-width:2px,color:#a7f3d0

    CallerRequest[Caller Invocates GenerateAccessToken]:::client --> VPCSC{Tier 1: VPC Service Controls Gate}:::gate

    VPCSC -- Request Originates Outside Approved Service Perimeter --> Drop1[Action: Deny by VPC-SC Perimeter Policy]:::drop
    VPCSC -- Request Inside Authorized Corporate Boundary --> OrgPolicy{Tier 2: Organization Policy Evaluation}:::gate

    OrgPolicy -- Violates Token Lifetime or Key Constraints --> Drop2[Action: Deny by Organization Policy]:::drop
    OrgPolicy -- Passes Org Constraints --> IAMCheck{Tier 3: Resource-Level IAM Evaluation}:::iam

    IAMCheck -- Project-Wide TokenCreator Binding Present --> Drop3[Action: Prohibited by Guardrail Policy]:::drop
    IAMCheck -- Scoped Resource Policy on Specific Service Account --> CondCheck{Tier 4: IAM Condition Verification}:::iam

    CondCheck -- Fails Time / Source Tag / Bastion Condition --> Drop4[Action: HTTP 403 Forbidden Access Denied]:::drop
    CondCheck -- Satisfies Context-Aware IAM Condition --> MintToken[Generate Ephemeral OAuth2 Bearer Token]:::pass

    MintToken --> AuditPipeline[Tier 5: Cloud Audit Log Telemetry Pipeline]:::audit
    AuditPipeline --> SIEMAlert[SIEM Ingestion: Parse serviceAccountDelegationInfo]:::pass

Layer 1: Resource-Level Least Privilege & Eliminating Project-Wide Bindings#

The most critical architectural remediation is strictly prohibiting the assignment of roles/iam.serviceAccountTokenCreator or roles/iam.serviceAccountUser at the Organization, Folder, or Project levels.

Impersonation permissions must be granted exclusively at the individual service account resource level:

BASH
# Anti-Pattern to Remediate: Project-level binding (Grants impersonation over ALL SAs)
# gcloud projects add-iam-policy-binding prod-project #     --member="user:developer@corp.com" #     --role="roles/iam.serviceAccountTokenCreator"

# Hardened Pattern: Resource-level binding (Scythed strictly to one specific SA)
gcloud iam service-accounts add-iam-policy-binding     deployer-sa@prod-project.iam.gserviceaccount.com     --member="user:lead-developer@corp.com"     --role="roles/iam.serviceAccountTokenCreator"

Furthermore, organizations must audit and deprovision default service accounts:

  • Disable the Compute Engine Default Service Account: By default, PROJECT_NUMBER-compute@developer.gserviceaccount.com is granted the broad Editor role. Disable this account or strip its project-level roles, provisioning dedicated, least-privilege service accounts for Compute Engine and GKE workloads.
  • Deprovision Unused SAs: Regularly scan for service accounts with zero API activity over 90 days and disable them.

Layer 2: Enforcing GCP Organization Policies#

Google Cloud Organization Policies establish immutable governance guardrails across the entire resource hierarchy, preventing developers and administrators from creating insecure configurations:

Deploy the following organization policy constraints at the Organization root node:

  1. constraints/iam.disableServiceAccountKeyCreation: Blocks the creation of static, downloadable .json service account keys, forcing teams to adopt Workload Identity or managed impersonation.
BASH
gcloud resource-manager org-policies enable-enforce     iam.disableServiceAccountKeyCreation     --organization=123456789012
  1. constraints/iam.allowServiceAccountCredentialLifetimeExtension: Prevents identities from requesting OAuth2 access tokens with lifetimes exceeding 3,600 seconds (1 hour).

  2. constraints/iam.automaticIamGrantsForDefaultServiceAccounts: Disables the automatic assignment of the legacy Editor role to default service accounts when new APIs are enabled.

BASH
gcloud resource-manager org-policies enable-enforce     iam.automaticIamGrantsForDefaultServiceAccounts     --organization=123456789012
  1. constraints/iam.disableServiceAccountKeyUpload: Blocks users from uploading external public keys to service accounts, closing a common backdoor persistence vector.

Layer 3: Context-Aware IAM Conditions for Impersonation#

Rather than granting permanent, unconstrained impersonation rights, blue teams should enforce IAM Conditions on service account resource bindings. IAM Conditions evaluate contextual attributes—such as time of day, network origin, or resource tags—before authorizing token creation.

Below is an IAM policy binding that restricts token creation strictly to requests originating from an internal administrative bastion subnet during business hours:

YAML
# service_account_hardened_binding.yaml
bindings:
- role: roles/iam.serviceAccountTokenCreator
  members:
  - "group:sre-team@corp.com"
  condition:
    title: "Enforce_Bastion_Egress_And_Business_Hours"
    description: "Allows token creation exclusively from the authorized SRE Bastion during working hours"
    expression: >
      request.time.getHours('America/New_York') >= 8 &&
      request.time.getHours('America/New_York') <= 18 &&
      request.time.getDayOfWeek('America/New_York') >= 1 &&
      request.time.getDayOfWeek('America/New_York') <= 5

Apply the condition to the service account:

BASH
gcloud iam service-accounts set-iam-policy     deployer-sa@prod-project.iam.gserviceaccount.com     service_account_hardened_binding.yaml

Layer 4: Workload Identity Federation (WIF)#

For external CI/CD pipelines (e.g., GitHub Actions, GitLab CI) and multi-cloud infrastructure (AWS, Azure), eliminate static keys and intermediate proxy accounts by deploying Workload Identity Federation (WIF).

WIF exchanges short-lived OIDC tokens directly with Google Cloud's Security Token Service (STS) without requiring a human or intermediate account to hold token creator privileges:

BASH
# Create an identity pool and provider strictly mapped to GitHub repository attributes
gcloud iam workload-identity-pools providers create-oidc "github-actions-provider"     --workload-identity-pool="cicd-pool"     --issuer-uri="https://token.actions.githubusercontent.com"     --attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository"     --attribute-condition="assertion.repository=='corp/production-deployment'"

Production Detection Queries#

Blue teams must deploy real-time monitoring across Google Cloud Audit Logs to identify unauthorized GenerateAccessToken calls and trace disguised administrative actions.

1. Production Sigma Rule: Suspicious GCP Service Account Impersonation#

The following Sigma rule detects unauthorized service account token generation in Google Cloud Audit Logs:

YAML
title: Suspicious GCP Service Account Token Generation
id: 4e7a2b91-8c3d-4f12-9b5a-1d6e3f8a02c4
status: production
description: |
  Detects invocation of the IAM Service Account Credentials API (GenerateAccessToken or SignJwt)
  by non-automated human identities or from unexpected source networks, indicating potential
  service account impersonation and keyless privilege escalation.
references:
  - https://cloud.google.com/iam/docs/impersonating-service-accounts
  - https://attack.mitre.org/techniques/T1078/004/

tags:
  - attack.privilege_escalation
  - attack.t1078.004
  - attack.defense_evasion
logsource:
  product: gcp
  service: cloudaudit.googleapis.com
detection:
  selection_service:
    protoPayload.serviceName: 'iamcredentials.googleapis.com'
  selection_method:
    protoPayload.methodName:
      - 'google.iam.credentials.v1.GenerateAccessToken'
      - 'google.iam.credentials.v1.SignJwt'
      - 'google.iam.credentials.v1.SignBlob'
  filter_known_ci_runners:
    protoPayload.authenticationInfo.principalEmail|endswith:
      - '.gserviceaccount.com'
    protoPayload.requestMetadata.callerIp:
      - '10.100.50.10' # Authorized Internal CI Bastion
  condition: selection_service and selection_method and not filter_known_ci_runners
fields:
  - protoPayload.authenticationInfo.principalEmail
  - protoPayload.resourceName
  - protoPayload.methodName
  - protoPayload.requestMetadata.callerIp
falsepositives:
  - Authorized SRE emergency administrative sessions during planned maintenance.
level: high

2. Google Cloud Log Metric Filter: Real-Time Impersonation Tracking#

Deploy this Cloud Logging metric filter to trigger real-time Cloud Monitoring alerts whenever token generation is invoked:

BASH
# Create a log-based metric tracking all GenerateAccessToken invocations
gcloud logging metrics create service_account_token_minting     --description="Tracks invocations of the IAM GenerateAccessToken API"     --log-filter='resource.type="service_account" AND protoPayload.methodName="google.iam.credentials.v1.GenerateAccessToken"'

Next, configure an alerting policy that fires whenever the metric exceeds baseline thresholds or triggers outside business hours.

3. Python Audit Script: Auditing Project-Level Impersonation Bindings#

Run this script across all projects in your Google Cloud organization to automatically flag any identity holding project-wide token creation permissions:

PYTHON
# audit_gcp_impersonation.py
# Audits Google Cloud projects for dangerous project-level TokenCreator bindings

import json
import subprocess
import sys

def audit_project_iam(project_id: str):
    print(f"[*] Auditing project: {project_id}...")
    cmd = ["gcloud", "projects", "get-iam-policy", project_id, "--format=json"]
    result = subprocess.run(cmd, capture_output=True, text=True)
    if result.returncode != 0:
        print(f"[-] Failed to fetch policy for {project_id}: {result.stderr}")
        return

    policy = json.loads(result.stdout)
    dangerous_roles = [
        "roles/iam.serviceAccountTokenCreator",
        "roles/iam.serviceAccountUser",
        "roles/iam.serviceAccountAdmin"
    ]

    violations = []
    for binding in policy.get("bindings", []):
        role = binding.get("role")
        if role in dangerous_roles:
            for member in binding.get("members", []):
                violations.append({"role": role, "member": member})

    if violations:
        print(f"[!] CRITICAL VIOLATION in {project_id}: Project-level impersonation detected!")
        for v in violations:
            print(f"    - Role: {v['role']} assigned to {v['member']}")
    else:
        print(f"[+] Project {project_id} is clean: No project-level TokenCreator bindings.")

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Usage: python3 audit_gcp_impersonation.py <PROJECT_ID>")
        sys.exit(1)
    audit_project_iam(sys.argv[1])

Enterprise Mitigation Matrix#

When implementing defensive controls against service account impersonation, security architects must weigh operational velocity against privilege escalation containment:

Remediation Strategy Implementation Effort Blast Radius & Operational Risk Detection & Prevention Efficacy Performance Overhead Architectural Longevity
Workaround: Strip Project-Level Bindings & Enforce Org Policies 1 - 2 Hours
(Remove project-wide TokenCreator roles and enable key creation disablement)
Low
Legitimate users can still impersonate specific service accounts if resource-bound.
High (90%)
Eliminates blanket project-wide impersonation pathways.
Zero
Native IAM policy evaluation.
Immediate Barrier
Removes the most common privilege escalation vector.
Operational Fix: Resource-Level Scoping & IAM Conditions 1 - 3 Days
(Audit all service account DACLs, bind TokenCreator per-resource, add conditions)
Moderate
Requires updating automation scripts and CI/CD pipelines to target specific SAs.
Comprehensive (98%)
Enforces least privilege and restricts token creation to approved contexts.
Zero
Serverless IAM condition engine.
Permanent Hygiene
Maintains precise identity governance over all technical accounts.
Architecture Fix: Workload Identity Federation & VPC-SC 2 - 4 Weeks
(Deploy WIF for CI/CD, establish VPC Service Controls perimeter around IAM API)
High
Requires architectural restructuring of multi-cloud and external deployments.
Absolute (100%)
Eliminates credential delegation entirely; stops out-of-perimeter API access.
Zero
Google Cloud backbone enforcement.
Permanent State
Zero-trust, keyless, and perimeter-contained enterprise cloud estate.

[!IMPORTANT] When transitioning from project-level to resource-level IAM bindings, verify that deployment pipelines (such as Terraform Cloud or GitLab CI) specify the exact service account email rather than relying on project-wide implicit delegation.


Incident Response & Verification Playbook#

If suspicious GenerateAccessToken calls or unauthorized impersonation activity is detected, execute the following four-phase incident response playbook immediately:

flowchart TD
    classDef alert fill:#450a0a,stroke:#dc2626,stroke-width:2px,color:#fca5a5
    classDef check fill:#1e293b,stroke:#f59e0b,stroke-width:2px,color:#fef3c7
    classDef triage fill:#0f172a,stroke:#3b82f6,stroke-width:2px,color:#93c5fd
    classDef clean fill:#064e3b,stroke:#10b981,stroke-width:2px,color:#6ee7b7

    Triage[Phase 1: Log Triage & Delegation Extraction]:::triage --> IdentifyCaller{Extract FirstPartyPrincipal Caller}:::check
    
    IdentifyCaller --> Containment[Phase 2: Immediate Identity Isolation & Token Neutralization]:::alert
    Containment --> DisableSA[Disable Target Service Account & Revoke Roles]:::alert
    
    DisableSA --> ScourLogs[Phase 3: Scour Cloud Audit Logs for Post-Impersonation Actions]:::alert
    ScourLogs --> AuditResources{Were New Keys or IAM Bindings Created?}:::check
    
    AuditResources -- Yes --> RollbackBackdoors[Revert Backdoor Bindings & Delete Rogue Keys]:::alert
    AuditResources -- No --> Hardening[Phase 4: Post-Remediation Verification & Policy Enforce]:::clean
    RollbackBackdoors --> Hardening

Phase 1: Live Triage & Delegation Extraction#

  1. Extract Caller and Target Identity: Query Cloud Audit Logs for the specific GenerateAccessToken execution:
BASH
# Fetch detailed audit log for suspected token generation event
gcloud logging read     'resource.type="service_account" AND protoPayload.methodName="google.iam.credentials.v1.GenerateAccessToken"'     --limit=10     --format=json
  • Record protoPayload.authenticationInfo.principalEmail (the initiating caller).
  • Record protoPayload.resourceName (the target service account being impersonated).
  • Record protoPayload.requestMetadata.callerIp (source IP address).
  1. Trace Subsequent Actions via Delegation Info: Search Cloud Audit Logs for any administrative action performed where the caller matches the compromised service account in delegation metadata:
BASH
# Trace all actions executed using tokens delegated by the suspect caller
TARGET_SA="deployer-sa@prod-project.iam.gserviceaccount.com"
CALLER="attacker-user@external.com"

gcloud logging read     "protoPayload.authenticationInfo.principalEmail="${TARGET_SA}" AND protoPayload.authenticationInfo.serviceAccountDelegationInfo.firstPartyPrincipal.principalEmail="${CALLER}""     --format="table(timestamp, protoPayload.serviceName, protoPayload.methodName, protoPayload.resourceName)"

Phase 2: Immediate Containment & Token Neutralization#

Because active OAuth2 access tokens cannot be revoked individually through an API call, execute the following containment protocol:

  1. Disable the Compromised Target Service Account: Disabling the service account immediately causes all Google APIs to reject active tokens issued for that service account:
BASH
gcloud iam service-accounts disable deployer-sa@prod-project.iam.gserviceaccount.com
  1. Strip Token Creation Roles from the Caller: Immediately remove roles/iam.serviceAccountTokenCreator from the originating identity:
BASH
gcloud iam service-accounts remove-iam-policy-binding     deployer-sa@prod-project.iam.gserviceaccount.com     --member="user:attacker-user@external.com"     --role="roles/iam.serviceAccountTokenCreator"
  1. Suspend Originating User / Revoke Refresh Tokens: If the caller was a human user identity, suspend the account in Google Workspace or revoke their OAuth2 refresh tokens in your enterprise IdP.

Phase 3: Eradication & Backdoor Removal#

  1. Audit for Persistence Backdoors: Threat actors frequently use impersonated tokens to create permanent access backdoors:
    • Check for newly uploaded or generated service account keys:
BASH
gcloud iam service-accounts keys list     --iam-account=deployer-sa@prod-project.iam.gserviceaccount.com
  • Review all project-level SetIamPolicy calls to identify new users added to roles/owner or roles/editor.
  1. Revert Unauthorized Changes: Revert any modified cloud infrastructure, bucket ACLs, or IAM policies using known-good Terraform state files.

Phase 4: Post-Remediation Verification & Health Checks#

Before re-enabling the service account:

  • Verify that no project-level serviceAccountTokenCreator or serviceAccountUser bindings exist using audit_gcp_impersonation.py.
  • Confirm that constraints/iam.disableServiceAccountKeyCreation is active and enforced.
  • Verify that IAM Conditions restrict impersonation to authorized management bastions.
  • Test that your SIEM actively triggers an alert when a test token generation call is executed.
  • Re-enable the service account: gcloud iam service-accounts enable <EMAIL>.

Authoritative References#

  1. Google Cloud Documentation: Security Best Practices for Managing Service Accounts — Google Cloud IAM
  2. Google Cloud Architecture Center: Privilege Escalation Analysis & Service Account Security — Google Cloud Architecture
  3. Center for Internet Security (CIS): CIS Google Cloud Platform Foundation Benchmark v3.0 — CIS Security
  4. Cybersecurity and Infrastructure Security Agency (CISA): Cloud Identity & Access Management Technical Guidance — CISA Advisory

Comments