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_IDCloud 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_IDCloud 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_IDGKE 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.comOption 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_IDStep 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_IDStep 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-imageOption 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_IDStep 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_IDStep 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_IDStep 5: Securely Delete Local Key File
# Securely delete the key file
shred -u new-key.json # Linux
rm -P new-key.json # macOSOption 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}", 200Deploy 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_IDSchedule 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_IDStep 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_IDCreate 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
Related Resources
- 30 Cloud Security Tips for 2026 - Comprehensive cloud security guide
- GCP Secret Manager Tutorial - Secure credential storage
- GCP Super Admin Best Practices - Admin security
- Workload Identity Federation Documentation
- Service Account Security Best Practices
Need help implementing keyless authentication or automating key rotation? Contact InventiveHQ for expert guidance on identity and access management in Google Cloud.