A Google Cloud service account key is a private key file that never expires by default and works from anywhere, so every key in a laptop, CI variable or repository is a standing credential for that service account. Google provides the pieces to remove them: key listings, Activity Analyzer for last authentication, workload identity federation for external workloads, and organisation policy constraints that stop new keys being created. This runbook takes you from a list of keys to keyless access: inventory, find which keys are used and by whom, move each consumer to federation, disable, then delete, and lock the door with organisation policy. It is for cloud platform and security engineers responsible for Google Cloud projects or organisations. For the background on key risk, see the Google Cloud Service Account Keys Security Guide.
Key takeaways
- Only user-managed keys are your problem. Listings can include Google-created keys used by the Service Account Credentials API; filter on key type
USER_MANAGED. - Activity Analyzer tells you which keys are used.
gcloud policy-intelligence query-activity --activity-type=serviceAccountKeyLastAuthenticationreturnslastAuthenticatedTimeper key, omitted for keys never used. - Federation replaces the key for external workloads. A workload identity pool and provider let GitHub Actions, GitLab, Azure DevOps, HCP Terraform and other clouds exchange their own tokens for Google credentials.
- Disable, then delete.
gcloud iam service-accounts keys disableis reversible withenable; it does not revoke short-lived credentials already minted from the key. - Enforce with organisation policy.
iam.disableServiceAccountKeyCreationandiam.disableServiceAccountKeyUploadare enforced by default only for organisations created on or after 3 May 2024.
When you need this
- Older organisations: organisations created before 3 May 2024 do not have key creation blocked by default and often hold years of user-managed keys.
- CI/CD migration: pipelines that authenticate with a JSON key stored as a CI secret are the most common and easiest keys to remove with federation.
- Exposure or offboarding: a key found in a repository, a departing engineer who created keys, or a supplier that held one. If a key is known to be leaked, disable it at once and follow the Cloud Access Key Leak Playbook; this runbook is for planned replacement.
- Audit requests: evidence that long-lived service credentials are inventoried and minimised (NIST SP 800-53 IA-5, CM-8).
Before you start
| Task | Role (least privilege named by Google) | Notes |
|---|---|---|
| List keys | roles/iam.serviceAccountViewer | IAM API must be enabled |
| Query last authentication | roles/policyanalyzer.activityAnalysisViewer | Policy Analyzer API must be enabled (needs serviceusage.services.enable) |
| Disable, enable, delete keys | roles/iam.serviceAccountKeyAdmin | On the project or the service account |
| Create pools and providers | Workload Identity Pool Admin or equivalent | Use the project number, not the project ID, in principal identifiers |
| Set organisation policy | Organization Policy Administrator | At organisation, folder or project level |
Google advises against basic roles (Owner, Editor, Viewer) in production; run discovery with a dedicated viewer identity. Decide the target for each consumer type in advance using the table in step 3.
Step-by-step
Steps 1 and 2 are read-only. Steps 3 and 4 add federation alongside the key. Step 5 is reversible. Step 6 is destructive. Step 7 changes policy for the whole scope.
- Inventory user-managed keys. For each service account, list keys with their creation, expiry and disabled state:
The output showsgcloud iam service-accounts list --project=PROJECT_ID --format="value(email)" gcloud iam service-accounts keys list \ --iam-account=SA_NAME@PROJECT_ID.iam.gserviceaccount.comKEY_ID,CREATED_AT,EXPIRES_AT,DISABLED,DISABLE_REASONandEXTENDED_STATUS. A disable reason ofEXPOSEDmeans Google itself disabled a key it detected as exposed. The REST API'skeyTypesparameter filters onUSER_MANAGEDorSYSTEM_MANAGED; ignore system-managed keys. Loop over every project in scope and keep the result as a dated CSV. - Find which keys are used. Query Activity Analyzer for every key in the project, or up to 10 specific keys joined with OR:
In the console the same query is under IAM & Admin > Policy Analyzer > Analyze recent activity. Keys with nogcloud policy-intelligence query-activity \ --activity-type=serviceAccountKeyLastAuthentication \ --project=PROJECT_ID --limit=1000lastAuthenticatedTimehave never been used within theobservationPeriod; results may miss very recent events and timestamps are daily. Disabled keys are excluded from results. Use Cloud Audit Logs on the services the account calls to find the source of each used key (caller IP, user agent, the system that holds the file), then confirm with the owner. - Choose the replacement for each consumer.
Consumer Replacement Note Workload on Google Cloud (Compute Engine, Cloud Run, GKE) Attached service account; Workload Identity Federation for GKE No key or federation pool needed GitHub Actions, GitLab SaaS, HCP Terraform, Azure DevOps Workload identity federation with an OIDC provider Attribute condition limiting your organisation or group is mandatory AWS or Azure workloads Workload identity federation with other clouds Uses the other cloud's native identity On-premises or third-party SaaS with no federation support Keep a key, with short expiry and an owner Track as a documented exception - Set up federation alongside the key. For a deployment pipeline, create a pool and an OIDC provider, then grant access:
GitHub, GitLab SaaS and HCP Terraform share one issuer across all customers, so without the attribute condition any of their customers could present a valid token. Google recommends the numericgcloud iam workload-identity-pools create POOL_ID --location="global" \ --display-name="CI pipelines" gcloud iam workload-identity-pools providers create-oidc PROVIDER_ID \ --location="global" --workload-identity-pool="POOL_ID" \ --issuer-uri="https://token.actions.githubusercontent.com/" \ --attribute-mapping="google.subject=assertion.sub,attribute.repository_owner_id=assertion.repository_owner_id,attribute.repository_id=assertion.repository_id" \ --attribute-condition="assertion.repository_owner_id=='OWNER_ID'"repository_idandrepository_owner_idclaims over names, which can be re-registered after deletion. Then grant access either directly to the resource with aprincipal://orprincipalSet://member, or by letting the federated identity impersonate the existing service account:
Impersonation is the lowest-risk first move because the service account's existing roles stay unchanged. In GitHub Actions, usegcloud iam service-accounts add-iam-policy-binding SA_EMAIL \ --role=roles/iam.workloadIdentityUser \ --member="principalSet://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/attribute.repository_id/REPO_ID"google-github-actions/authwithworkload_identity_providerandpermissions: id-token: write, then remove the JSON key secret from the pipeline. See the GitHub Actions OIDC Federation Hardening Guide. - Disable the old key (reversible). Once the consumer runs on federation:
Google notes that disabling does not revoke short-lived credentials already issued from the key; revoking those requires disabling or deleting the service account, which affects every workload using it. Leave the key disabled through a full business cycle.gcloud iam service-accounts keys disable KEY_ID \ --iam-account=SA_NAME@PROJECT_ID.iam.gserviceaccount.com --project=PROJECT_ID - Delete the key (destructive). Google recommends disabling before deleting.
Treat deletion as permanent and keep the inventory record of the key ID, owner and consumer.gcloud iam service-accounts keys delete KEY_ID \ --iam-account=SA_NAME@PROJECT_ID.iam.gserviceaccount.com - Block new keys with organisation policy.
Managed equivalents exist (gcloud resource-manager org-policies enable-enforce \ --organization ORGANIZATION_ID iam.disableServiceAccountKeyCreation gcloud resource-manager org-policies enable-enforce \ --organization ORGANIZATION_ID iam.disableServiceAccountKeyUploadiam.managed.disableServiceAccountKeyCreation, which also blocks Cloud Storage HMAC keys, andiam.managed.disableServiceAccountKeyUpload). For documented exceptions, setconstraints/iam.serviceAccountKeyExpiryHours(allowed values from1hto2160h) on the excepted project so new keys expire, and override the creation constraint only at that project.
Verify it worked
- Consumer on federation: Cloud Audit Logs for the pipeline's calls show the federated principal or the impersonated service account, with no key ID in the authentication info.
- Key quiet: re-run
query-activityfor the key before disabling; after disabling, the consumer keeps working. - Inventory shrinks: the keys list for the service account shows the key disabled, then gone.
- No orphaned secrets: the JSON key secret is removed from the CI system, secret store and any developer machines named by the owner, so a later re-enable cannot be quietly reused. Ask owners to confirm deletion of local copies in the change ticket.
- Policy holds: an attempt to create a key in a covered project fails with an organisation policy error.
Rollback and common errors
| Situation | Fix |
|---|---|
| A consumer fails after key disable | gcloud iam service-accounts keys enable KEY_ID ..., find the consumer, move it, disable again |
| Federated token rejected | Check the attribute condition, the mapping for every attribute used in the condition, and the audience; see the Multi-Cloud Workload Identity Troubleshooting Guide |
PERMISSION_DENIED on key delete | The service account does not exist or the caller lacks Service Account Key Admin |
| Principal identifier rejected | Use the project number, not the project ID, in principal:// and principalSet:// |
| Pool or provider ID refused | The gcp- prefix is reserved |
| Team needs a key after enforcement | Grant a project-level exception with key expiry set, and record an owner and end date |
| Provider too broad | A condition on repository owner alone admits every repository in the organisation; narrow to repository ID or branch claims |
Hardening afterwards
- One provider per trust boundary: separate pools or providers for production and non-production pipelines, so a test repository cannot reach production.
- Least privilege on the service account: federation does not reduce what the service account can do. Review its roles; see the Google Cloud IAM and Service Accounts Guide.
- Watch for new keys: alert on key creation events in Cloud Audit Logs for any project with an exception.
- Remove dormant service accounts: query
serviceAccountLastAuthenticationfor accounts with no authentication and disable them. - Keep the inventory current: feed the key and exception list into your NHI inventory. See NHI Inventory Reconciliation.
Metrics
- User-managed keys in the organisation (target: falling to the documented exception list).
- Keys never used or unused for 90 days (target: zero).
- Pipelines using federation instead of a JSON key (target: all).
- Projects with key creation allowed (target: only named exceptions, each with key expiry set).
Small estate or large organisation
A handful of projects can be cleaned in a sprint using the commands above. A large organisation should enforce the creation constraint first at a folder holding new projects, migrate CI/CD systems centrally (one pool per platform with per-repository conditions), and work the remaining keys by owner. Enforcing at the organisation root before the migration will block teams that legitimately rotate keys today, so roll out folder by folder with exceptions agreed.
Framework mapping
| Framework | Reference | Relevance |
|---|---|---|
| NIST SP 800-53 Rev 5 | IA-5 Authenticator Management; CM-8 System Component Inventory | Inventory and minimise long-lived authenticators |
| NIST CSF 2.0 | PR.AA-01 | Service credentials managed by the organisation |
| MITRE ATT&CK | T1552.001 Credentials In Files; T1078.004 Cloud Accounts | Key files are stolen from disks and repositories and used as cloud accounts |
Practitioner checklist
- List user-managed keys in every project and record owner and consumer.
- Query Activity Analyzer for key last authentication.
- Pick a replacement per consumer: attached service account, federation or documented exception.
- Create providers with an attribute condition on numeric owner or repository IDs.
- Run the consumer on federation before touching the key.
- Disable, wait a business cycle, then delete.
- Enforce the key creation and upload constraints, with expiry on exceptions.
Standards and references
- Google Cloud: List and get service account keys
- Google Cloud: View recent usage for service accounts and keys
- Google Cloud: Workload identity federation with deployment pipelines
- Google Cloud: Disable and enable service account keys
- Google Cloud SDK: gcloud iam service-accounts keys delete
- Google Cloud: Restricting service account usage with organisation policy
Related NHI Mgmt Group resources: Google Cloud Service Account Keys Security Guide · Workload Identity Federation Guide · GitHub Actions OIDC Federation Hardening Guide · Google Cloud IAM and Service Accounts Guide