Google Cloudintermediate

GCP Service Account Key Rotation: Best Practices and Automation

Complete guide to rotating Google Cloud service account keys securely. Covers manual rotation, automated rotation with Cloud Functions, Workload Identity Federation, and eliminating keys entirely.

11 min readUpdated 2026-01-13

Service account keys are one of the most common security vulnerabilities in Google Cloud environments. Unlike user credentials that can be protected with MFA, service account keys are static credentials that provide access until revoked. Compromised keys have been responsible for numerous cloud breaches.

This guide covers key rotation best practices, automation strategies, and modern alternatives that eliminate keys entirely. This expands on Tip 7 from our 30 Cloud Security Tips for 2026 guide on rotating access keys regularly.

Understanding Service Account Key Risks

Service account keys present several security challenges:

  • No MFA protection - Keys work without additional authentication
  • Difficult to detect misuse - Keys can be used from anywhere
  • Long-lived by default - No automatic expiration
  • Easy to leak - Accidentally committed to git, shared in Slack, etc.

According to Google's security research, service account keys are the #1 source of credential leaks in GCP environments.

Option 1: Eliminate Keys with Attached Service Accounts

For workloads running on GCP, use attached service accounts instead of keys:

Compute Engine VMs

# Create VM with attached service account
gcloud compute instances create my-vm \
  --zone=us-central1-a \
  --service-account=my-service-account@PROJECT_ID.iam.gserviceaccount.com \
  --scopes=cloud-platform \
  --project=PROJECT_ID

Cloud Functions

# Deploy function with specific service account
gcloud functions deploy my-function \
  --runtime=python311 \
  --trigger-http \
  --service-account=my-service-account@PROJECT_ID.iam.gserviceaccount.com \
  --project=PROJECT_ID

Cloud Run

# Deploy service with specific service account
gcloud run deploy my-service \
  --image=gcr.io/PROJECT_ID/my-image \
  --service-account=my-service-account@PROJECT_ID.iam.gserviceaccount.com \
  --region=us-central1 \
  --project=PROJECT_ID

GKE with Workload Identity

# Enable Workload Identity on cluster
gcloud container clusters update my-cluster \
  --zone=us-central1-a \
  --workload-pool=PROJECT_ID.svc.id.goog \
  --project=PROJECT_ID

# Create Kubernetes service account
kubectl create serviceaccount my-k8s-sa --namespace=default

# Allow K8s SA to impersonate GCP SA
gcloud iam service-accounts add-iam-policy-binding \
  my-gcp-sa@PROJECT_ID.iam.gserviceaccount.com \
  --role=roles/iam.workloadIdentityUser \
  --member="serviceAccount:PROJECT_ID.svc.id.goog[default/my-k8s-sa]" \
  --project=PROJECT_ID

# Annotate K8s SA
kubectl annotate serviceaccount my-k8s-sa \
  iam.gke.io/gcp-service-account=my-gcp-sa@PROJECT_ID.iam.gserviceaccount.com

Option 2: Workload Identity Federation for External Workloads

For workloads running outside GCP (AWS, Azure, GitHub Actions, on-premises), use Workload Identity Federation:

Step 1: Create Workload Identity Pool

# Create identity pool
gcloud iam workload-identity-pools create github-pool \
  --location=global \
  --display-name="GitHub Actions Pool" \
  --project=PROJECT_ID

# Create provider for GitHub Actions
gcloud iam workload-identity-pools providers create-oidc github-provider \
  --location=global \
  --workload-identity-pool=github-pool \
  --display-name="GitHub Provider" \
  --issuer-uri="https://token.actions.githubusercontent.com" \
  --attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository" \
  --project=PROJECT_ID

Step 2: Allow External Identity to Impersonate Service Account

# Grant the external identity access to impersonate a service account
gcloud iam service-accounts add-iam-policy-binding \
  deploy-sa@PROJECT_ID.iam.gserviceaccount.com \
  --role=roles/iam.workloadIdentityUser \
  --member="principalSet://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/github-pool/attribute.repository/my-org/my-repo" \
  --project=PROJECT_ID

Step 3: Configure GitHub Actions

# .github/workflows/deploy.yml
name: Deploy to GCP
on:
  push:
    branches: [main]

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - id: auth
        uses: google-github-actions/auth@v2
        with:
          workload_identity_provider: projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/github-pool/providers/github-provider
          service_account: deploy-sa@PROJECT_ID.iam.gserviceaccount.com

      - name: Set up Cloud SDK
        uses: google-github-actions/setup-gcloud@v2

      - name: Deploy
        run: gcloud run deploy my-service --image gcr.io/PROJECT_ID/my-image

Option 3: Manual Key Rotation

If you must use service account keys, rotate them regularly:

Step 1: Create New Key

# Create new key
gcloud iam service-accounts keys create new-key.json \
  --iam-account=my-sa@PROJECT_ID.iam.gserviceaccount.com \
  --project=PROJECT_ID

Step 2: Update Application

Update the application or service using the key. For Secret Manager:

# Upload new key to Secret Manager
gcloud secrets versions add my-service-account-key \
  --data-file=new-key.json \
  --project=PROJECT_ID

Step 3: Verify New Key Works

Test the application with the new key before removing the old one.

Step 4: Delete Old Key

# List all keys
gcloud iam service-accounts keys list \
  --iam-account=my-sa@PROJECT_ID.iam.gserviceaccount.com \
  --project=PROJECT_ID

# Delete old key (by key ID)
gcloud iam service-accounts keys delete OLD_KEY_ID \
  --iam-account=my-sa@PROJECT_ID.iam.gserviceaccount.com \
  --project=PROJECT_ID

Step 5: Securely Delete Local Key File

# Securely delete the key file
shred -u new-key.json  # Linux
rm -P new-key.json     # macOS

Option 4: Automated Key Rotation with Cloud Functions

Automate rotation using Cloud Functions and Cloud Scheduler:

Cloud Function Code (Python)

# main.py
import functions_framework
from google.cloud import iam_admin_v1
from google.cloud import secretmanager
import json

@functions_framework.http
def rotate_key(request):
    """Rotate service account key and update Secret Manager."""

    project_id = "PROJECT_ID"
    service_account_email = "my-sa@PROJECT_ID.iam.gserviceaccount.com"
    secret_id = "my-service-account-key"

    # Create IAM client
    iam_client = iam_admin_v1.IAMClient()

    # Create new key
    create_request = iam_admin_v1.CreateServiceAccountKeyRequest(
        name=f"projects/{project_id}/serviceAccounts/{service_account_email}",
        key_algorithm="KEY_ALG_RSA_2048"
    )
    new_key = iam_client.create_service_account_key(request=create_request)

    # Store in Secret Manager
    sm_client = secretmanager.SecretManagerServiceClient()
    parent = f"projects/{project_id}/secrets/{secret_id}"

    # Decode the private key data (base64 encoded JSON)
    import base64
    key_data = base64.b64decode(new_key.private_key_data)

    # Add new version
    sm_client.add_secret_version(
        parent=parent,
        payload={"data": key_data}
    )

    # List all keys and delete old ones (keeping only the newest)
    list_request = iam_admin_v1.ListServiceAccountKeysRequest(
        name=f"projects/{project_id}/serviceAccounts/{service_account_email}",
        key_types=["USER_MANAGED"]
    )
    keys = iam_client.list_service_account_keys(request=list_request)

    # Sort by creation time and delete all but newest
    sorted_keys = sorted(keys.keys, key=lambda k: k.valid_after_time.timestamp(), reverse=True)
    for old_key in sorted_keys[1:]:  # Keep the newest, delete the rest
        delete_request = iam_admin_v1.DeleteServiceAccountKeyRequest(
            name=old_key.name
        )
        iam_client.delete_service_account_key(request=delete_request)

    return f"Rotated key for {service_account_email}", 200

Deploy the Function

# Deploy function
gcloud functions deploy rotate-service-account-key \
  --runtime=python311 \
  --trigger-http \
  --entry-point=rotate_key \
  --service-account=key-rotation-sa@PROJECT_ID.iam.gserviceaccount.com \
  --project=PROJECT_ID

Schedule with Cloud Scheduler

# Create scheduler job (every 90 days)
gcloud scheduler jobs create http rotate-key-job \
  --schedule="0 0 1 */3 *" \
  --uri="https://REGION-PROJECT_ID.cloudfunctions.net/rotate-service-account-key" \
  --http-method=POST \
  --oidc-service-account-email=scheduler-sa@PROJECT_ID.iam.gserviceaccount.com \
  --project=PROJECT_ID

Step 5: Monitor Key Usage and Age

Set up monitoring for key age and usage:

Check Key Ages

# List all keys with creation dates
gcloud iam service-accounts keys list \
  --iam-account=my-sa@PROJECT_ID.iam.gserviceaccount.com \
  --format="table(name.basename(), keyType, validAfterTime.date(), validBeforeTime.date())" \
  --project=PROJECT_ID

Create Alerting Policy for Old Keys

Using Cloud Monitoring:

    - Navigate to [Cloud Monitoring](https://console.cloud.google.com/monitoring) - Go to **Alerting > Create Policy** - Use log-based metric for service account key age - Alert when keys exceed 90 days

Terraform for Organization Policy

Prevent new key creation entirely:

resource "google_organization_policy" "disable_sa_key_creation" {
  org_id     = var.organization_id
  constraint = "iam.disableServiceAccountKeyCreation"

  boolean_policy {
    enforced = true
  }
}

resource "google_organization_policy" "disable_sa_key_upload" {
  org_id     = var.organization_id
  constraint = "iam.disableServiceAccountKeyUpload"

  boolean_policy {
    enforced = true
  }
}

Best Practices Summary

  • Eliminate keys when possible - Use attached service accounts or Workload Identity Federation
  • Rotate remaining keys every 90 days - Automate if possible
  • Store keys in Secret Manager - Never in code repos or environment variables
  • Monitor key age - Alert on keys older than 90 days
  • Use organization policies - Prevent new key creation where possible
  • Audit key usage - Review which keys are actually being used
  • Delete unused service accounts - Regular quarterly reviews

Need help implementing keyless authentication or automating key rotation? Contact InventiveHQ for expert guidance on identity and access management in Google Cloud.

Frequently Asked Questions

Find answers to common questions

Google recommends rotating service account keys every 90 days or less. Many compliance frameworks (PCI-DSS, SOC 2) require key rotation at least annually, but shorter rotation periods reduce the window of opportunity for compromised keys. The best practice is to eliminate service account keys entirely using Workload Identity Federation for external workloads or attached service accounts for GCP workloads.

Expert GCP Management

From architecture design to managed operations, we handle your Google Cloud infrastructure.