Why GCP Service Account Keys Are Dangerous and What to Use Instead
Service account keys are JSON files that give an application the ability to authenticate as a GCP service account. They do not expire, have no network restrictions, and can be copied without any trace. Once a key file exists outside a secure boundary (a repository, a CI log, a Docker image), you cannot know how many copies exist or who has seen it.
Google’s own guidance recommends avoiding service account keys for most use cases. Applications running on Google Cloud can use attached service account identities instead. External systems and CI/CD pipelines can use Workload Identity Federation. Human and automation access to other accounts can use service account impersonation. This page explains what makes keys uniquely risky and how to eliminate them.
Simple explanation
When you authenticate to GCP as a user, you log in interactively and your session produces short-lived tokens that expire within an hour. If someone steals your session token, it becomes useless very quickly. A service account key works differently: it is a permanent credential stored in a file that any system can use to request tokens indefinitely. There is no expiry, no login prompt, and no way to detect that the file has been copied.
Hotel keycard vs master key
Think of it like the difference between a hotel keycard and a master key that opens every room permanently. The keycard expires when you check out. The master key works until someone physically changes the locks. A service account key is the master key. Once you make a digital copy, you cannot tell how many copies now exist or who has them.
Short-lived credentials from the metadata server, Workload Identity Federation, or impersonation are more like the keycard: they expire on their own, limit exposure if stolen, and do not require a file stored anywhere.
What a service account key actually is
A service account key is a JSON file containing a private RSA key. When an application needs to call a Google Cloud API, it uses the private key to sign a JWT, which it exchanges for a short-lived access token. The access token is what actually makes the API call. The key is what generates new tokens on demand, indefinitely.
The file looks something like this (simplified):
{
"type": "service_account",
"project_id": "my-app-prod",
"private_key_id": "a1b2c3d4",
"private_key": "-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----\n",
"client_email": "deployer@my-app-prod.iam.gserviceaccount.com"
}This is fundamentally different from how a user authenticates (interactively, via OAuth with short-lived tokens) or how a Compute Engine VM authenticates (via the metadata server, which issues tokens scoped to that specific VM). The key file is a self-contained credential. Anyone who has the file can use it from any machine, in any location, without needing access to GCP infrastructure.
For a deeper look at what the file contains and how the signing process works, see Service Account Keys Explained. For background on what service accounts are, see Service Accounts and Identity vs Service Accounts.
Why service account keys are dangerous
Most credential incidents in cloud environments are not sophisticated attacks. They are leaks. What makes service account keys especially hazardous is a combination of properties that no other GCP credential type shares.
Keys are long-lived, copyable, and work from anywhere. Each property alone would be manageable. Together, they create a credential that can persist undetected for years after it has been leaked. This is what makes keys a fundamentally different risk compared to short-lived tokens.
They are long-lived
Keys do not expire. A key created during a project that finished two years ago is still valid today unless someone explicitly deleted it. Most are not deleted. In a typical organisation without active key governance, service accounts accumulate keys over time: many unused, some forgotten, all still working.
They can be copied silently
A key is a file. It takes one second to copy. There is no alert when it is read, downloaded, or duplicated. If a developer downloads a key to test something locally, that copy exists on their laptop indefinitely. If a CI job writes the key to a log, that log may be stored for months. There is no audit event for “this key was read”, only for “this key was created” or “this key was deleted”. What happens in between is invisible.
They work from anywhere
A key works from any IP address, in any country, on any machine. Credentials obtained from the metadata server are scoped to the specific resource they are attached to: a VM’s metadata credentials only work from that VM. A key file has no such restriction. An attacker who obtains a key can use it from a different cloud provider, a home network, or a compromised third-party system.
They are hard to rotate under pressure
When a key leaks, the correct response is to delete it immediately. But deleting a key breaks every system that depends on it. Before you can delete safely, you need to know which systems use it, information that may not be documented. During an active incident with an attacker, the time it takes to investigate dependencies and coordinate revocation gives the attacker a meaningful window to operate.
They spread into more systems than intended
A key created for one purpose rarely stays in one place. It gets copied into a staging environment “just to test”. It gets added to a second CI pipeline for convenience. A team member takes a copy for local development. Within a year, a key originally meant for one system is referenced in three environments, two pipelines, and four developer laptops, making rotation or revocation extremely difficult to coordinate.
How leaks actually happen
Key leaks are almost never the result of sophisticated attacks. They happen through ordinary developer workflows. These are the most common paths:
Removing a key file from the latest commit does not remove it from Git history. Anyone with repository access (including forks) can retrieve a key that was committed and then deleted, using standard Git commands. If a key has touched a repository at any point, treat it as compromised.
Committed to source control. A key is added to a
.envfile or config alongside application code. Even if removed in a later commit, the key remains in Git history and is retrievable by anyone with repository access, including forks.CI/CD logs. A pipeline step that prints environment variables (for debugging, during a failure, or via a verbose flag) can expose the entire key in plain text. Many CI systems retain logs for months or years, and logs are often accessible to a broader audience than the secrets store itself.
Docker image layers. A Dockerfile that copies a key file into the image bakes it into that layer permanently. Even if a later
RUN rm key.jsonremoves it, the key is still present in the previous layer and recoverable usingdocker historyor image-scanning tools.Chat and tickets. A key sent via Slack or email to “test something quickly” lives in chat history indefinitely. Keys shared in support tickets, internal wikis, or comment threads are particularly difficult to track down and remove.
Developer laptops. A key downloaded for local development sits on a developer’s laptop, often unencrypted, potentially backed up to a personal cloud drive, and easily accessible if the device is lost or compromised.
Local scripts and forgotten backups. Keys saved into local shell scripts, test fixtures, or backup archives frequently outlive the original use case by years. They are rarely inventoried or removed during offboarding.
Shared internal storage. Keys saved to shared network drives, internal buckets, or build artefact stores can become accessible to far more people than originally intended, especially when access controls on those stores are not reviewed regularly.
How it looks from an attacker’s point of view
A service account key is one of the most valuable items an attacker can find in a public repository or leaked file archive. Unlike a user password, it requires no phishing and no interactive authentication. The attacker presents the key directly to Google’s token endpoint, receives a valid access token, and begins making API calls as the service account.
Finding a service account key in a public repository is the equivalent of finding a signed blank cheque. The attacker does not need to break into a bank or forge a signature. They already have everything they need to withdraw funds, up to the limit of whatever the account can reach.
The service account inherits whatever IAM roles were granted to it. If the
service account has roles/editor or any broad data-access role,
the attacker can read, modify, or exfiltrate data within those permissions.
The impact depends entirely on what the service account can access, which is
why the principle of least privilege
matters. A key on a service account with narrow, specific permissions limits
blast radius significantly. A key on an overly permissive service account can
expose an entire project.
Because the key works from any IP address, the attacker’s activity appears in audit logs as normal API calls from the service account. There is no login alert, no unusual IP flag, and no automatic session expiry. Without active log monitoring, the compromise may go undetected for weeks or months.
Google scans public GitHub repositories and other sources for exposed GCP service account keys and sends alerts through Security Command Center. This catches public leaks, but it does not detect keys leaked into private repositories, internal systems, or developer machines.
When to avoid service account keys
In most cases, service account keys should not be used at all. Specifically, avoid them for:
Applications running on Google Cloud. Any workload on Compute Engine, Cloud Run, GKE, Cloud Functions, or App Engine can use an attached service account and receive short-lived credentials from the metadata server automatically. No key file is needed.
CI/CD pipelines. GitHub Actions, GitLab CI, CircleCI, and most modern CI systems support OIDC-based identity. Use Workload Identity Federation to authenticate pipelines without storing any credential. The Secrets in CI/CD Pipelines guide covers this in detail.
Automation that can use impersonation. If a human or service needs to act as another service account for specific operations, use service account impersonation rather than distributing a key.
Production workloads. Production environments should be the first to eliminate keys. Apply the
iam.disableServiceAccountKeyCreationOrganisation Policy at the folder level to prevent key creation in production not just as policy guidance, but as a technical control.Multi-team environments. Where multiple teams share a project or service account, keys are especially risky because there is no practical way to rotate them without coordination across all teams. Keyless methods avoid this problem entirely.
When service account keys are still used
Keys are not entirely obsolete. There are legitimate situations where they remain in use:
Legacy tools and integrations. Some third-party tools and older enterprise software have no support for OIDC or Workload Identity Federation and require a service account key to authenticate to GCP. Replacing these integrations takes time.
External systems without identity providers. A script running on an on-premises server that has no OIDC-compatible identity provider cannot use federation directly. A key may be the only practical option while migration is in progress.
Short migration windows. When migrating from a key-based setup to a keyless alternative, a key may remain active temporarily. This is acceptable as a short-term measure with a clear removal date.
Break-glass access. Some organisations retain a tightly controlled key for emergency access scenarios, stored in a hardware security module or secrets management system with strict access controls and regular rotation.
When keys cannot be avoided, treat them as exceptions: use the narrowest possible IAM permissions, set a key expiry where supported, store them in Secret Manager rather than in files or environment variables, enforce rotation, and document the business reason for the exception.
Service account keys vs safer alternatives
Google Cloud offers three keyless alternatives, each suited to a different context. Here is how they compare:
| Service Account Key | Attached Identity (Metadata Server) | Workload Identity Federation | Service Account Impersonation | |
|---|---|---|---|---|
| Credential type | Static JSON file | Short-lived token | Short-lived token | Short-lived token |
| Stored on disk | Yes | No | No | No |
| Works outside GCP | Yes | No (VM/resource-scoped) | Yes | Yes (via gcloud or SDK) |
| Automatic expiry | No | Yes (~1 hour) | Yes (~1 hour) | Yes (up to 1 hour) |
| Rotation burden | Manual, disruptive | None | None | None |
| Best used for | Legacy/external (exception only) | GCP-hosted workloads | External systems and CI/CD | Human access and cross-account automation |
What to do instead
For workloads running in Google Cloud
Attach a service account to the resource at creation time. A Compute Engine VM, Cloud Run service, GKE pod, or Cloud Function that runs as a service account automatically receives short-lived credentials from the metadata server. No file is stored, and no rotation is needed. The Google Cloud client libraries handle token retrieval transparently using Application Default Credentials.
Google Cloud client libraries use Application Default Credentials (ADC) to find credentials automatically. On a VM or Cloud Run service with an attached service account, ADC reads from the metadata server without any configuration. The same code that uses a key file locally will use the attached identity in production, with no code changes required.
For CI/CD pipelines outside Google Cloud
Use Workload Identity Federation.
Your CI platform (GitHub Actions, GitLab, CircleCI, and others) already issues
OIDC tokens identifying the pipeline. Set up a Workload Identity Pool in GCP,
configure it to trust your CI provider, and grant the target service account
the workloadIdentityUser role for that pool. The pipeline
exchanges its own token for a short-lived GCP credential, with no key file involved.
The Secrets in CI/CD Pipelines
guide covers a complete setup.
For human or automation access to other service accounts
Use service account impersonation.
Grant the caller roles/iam.serviceAccountTokenCreator on the
target service account. The caller can then generate short-lived tokens that
act as the target account, with a maximum lifetime of one hour. Every
impersonation event is recorded in Cloud Audit Logs,
showing both the caller and the impersonated account. This makes impersonation
significantly more auditable than key-based access.
For secrets and credentials
If an application genuinely needs to access a credential at runtime, store it
in Secret Manager and
grant the service account the roles/secretmanager.secretAccessor
role. The application fetches the secret via API at runtime rather than
embedding it in configuration files or environment variables. This is not a
substitute for keyless authentication, but it is the correct pattern for any
runtime secret the application needs.
How to detect risky key usage
Cloud Audit Logs records
every key creation and deletion event. In an environment that has adopted
keyless authentication, the event google.iam.admin.v1.CreateServiceAccountKey
should be rare to non-existent. Any unexpected key creation in a production
project is a significant security signal worth immediate investigation.
Run this query to find recent key creation events in a project:
# List all service account key creation events in the past 7 days
gcloud logging read \
'protoPayload.methodName="google.iam.admin.v1.CreateServiceAccountKey"' \
--project=my-app-prod \
--freshness=7d \
--format='table(timestamp,protoPayload.authenticationInfo.principalEmail,protoPayload.request.name)'Run this to list all user-managed keys currently active on a specific service account:
# Show all user-managed keys for a service account, including creation dates
gcloud iam service-accounts keys list \
--iam-account=ci-deployer@my-app-prod.iam.gserviceaccount.com \
--project=my-app-prod \
--filter="keyType=USER_MANAGED" \
--format='table(name,validAfterTime,validBeforeTime)'Set up a log-based alert that fires whenever
google.iam.admin.v1.CreateServiceAccountKey is called in any
production project. Route the alert to your security channel or incident
queue. Creating a key on an existing service account is one of the most
common lateral movement techniques after an initial account compromise: the
attacker gains a persistent credential while the original access vector is
under investigation.
To prevent key creation entirely rather than just detecting it, apply the
iam.disableServiceAccountKeyCreation constraint using
Organisation Policies.
Set it at the folder level for all production workloads and document a review
process for any project-level exceptions.
If a key is leaked: the response sequence
Treat a suspected key leak as a live incident. Do not wait to confirm the leak before acting. Speed of revocation matters more than service continuity.
Delete the key immediately. This revokes the credential at the source. Any system still using the key will stop working. Accept the disruption. It is recoverable. An attacker with an active key is not.
Check audit logs for access using the service account. Look for API calls made by the service account identity since the key was created or since the suspected leak time. Pay attention to unusual resource access, unexpected regions, or high-volume data operations.
Assess the blast radius. Review the IAM roles granted to the service account using gcloud IAM commands. This tells you the maximum scope of possible damage based on what the account could access.
Rotate downstream secrets. If the service account had access to Secret Manager, database credentials, or other sensitive material, treat those as potentially exposed and rotate them.
Migrate to a keyless alternative. Use Workload Identity Federation or service account impersonation instead of issuing a replacement key. Generating a new key and redeploying it solves the immediate disruption but leaves the underlying vulnerability in place.
Creating a fresh replacement key without redesigning the authentication approach restores availability, but recreates the same risk that caused the incident. Use the disruption as the forcing function to migrate to keyless authentication properly.
Common mistakes
Assuming a private repository is safe for key storage. Private repositories still leak through team member laptops, repository access breaches, CI integrations, forks, and automated scanning tools. Visibility settings do not protect credentials stored in source control.
Reusing one key across environments. A single key shared across development, staging, and production means a leak in any environment exposes all three. Shared keys also cannot be rotated for one system without breaking the others. Use separate service accounts and separate keys per environment.
Not tracking key age or usage. Keys older than 90 days without a documented active use case should be deleted. Run
gcloud iam service-accounts keys listregularly to see creation dates. A key that has not been used recently is a dormant risk waiting to be discovered.No alerting on key creation events. Without a log-based alert on
CreateServiceAccountKey, rogue key creation goes undetected. This is one of the most common lateral movement techniques after an initial GCP compromise.Storing keys in Secret Manager but still distributing them widely. Secret Manager is the right place to store a key that genuinely cannot be replaced. But it does not reduce the risk if the key is then read and distributed to many systems. Storing a secret securely is not the same as eliminating the secret.
Keeping keys forever because rotation is too hard. If rotating a key would be disruptive, that is a signal the key has spread into too many systems. The difficulty of rotation is a risk indicator, not a reason to defer indefinitely.
Not enforcing Organisation Policies to block key creation. Relying on team awareness and process alone is not sufficient. Apply
iam.disableServiceAccountKeyCreationas a technical control in production environments. Policy without enforcement is guidance, not security.Not documenting exceptions. If a key is genuinely needed, document why, who approved it, when it should be removed, and what the rotation schedule is. Undocumented keys become permanent.
Summary
- Service account keys are static, long-lived JSON files with no expiry and no network restrictions. They work from anywhere until explicitly deleted.
- Common leak paths include Git history, CI logs, Docker image layers, chat tools, developer laptops, and shared internal storage.
- For GCP-hosted workloads, use attached service accounts and metadata server credentials. No key file required.
- For external systems and CI/CD pipelines, use Workload Identity Federation to exchange short-lived OIDC tokens for GCP credentials.
- For cross-account access, use service account impersonation. Tokens expire in one hour and leave a clear audit trail.
- Set up a log-based alert on
CreateServiceAccountKeyevents and apply theiam.disableServiceAccountKeyCreationOrganisation Policy in production. - If a key leaks: delete immediately, audit access logs, assess blast radius, rotate downstream secrets, then migrate to keyless auth.
- Creating a replacement key after an incident restores availability but does not fix the underlying risk.
Frequently asked questions
Why are service account keys dangerous in GCP?
Service account keys are long-lived static credentials stored as JSON files. They have no automatic expiry, no network restrictions, and can be copied silently. A leaked key gives anyone full access to the service account permissions from any location (including outside GCP), with no way to detect that a copy was taken.
Are service account keys ever safe to use?
In limited cases, yes. When an external system genuinely cannot use Workload Identity Federation, or during a short migration window, keys may be necessary. Treat them as exceptions: restrict IAM permissions tightly, enforce a short maximum lifetime, rotate regularly, and document the reason. They should never be the default approach for anything running inside Google Cloud.
What should I use instead of a service account key?
For workloads running on Google Cloud (Compute Engine, Cloud Run, GKE, Cloud Functions), attach a service account to the resource and let the metadata server issue short-lived tokens automatically. For external systems and CI/CD pipelines, use Workload Identity Federation. For user or automation access to other service accounts, use service account impersonation.
How do I stop teams from creating service account keys?
Apply the Organisation Policy constraint iam.disableServiceAccountKeyCreation at the folder level for all production environments. This blocks key creation at the API level regardless of IAM permissions. Combine this with a log-based alert on CreateServiceAccountKey events so any attempt in any project is immediately visible.
What is the difference between a service account key and service account impersonation?
A service account key is a static file that represents the service account permanently and works from anywhere. Impersonation lets an authorised identity generate short-lived tokens that act as the target service account for up to one hour. Impersonation tokens expire automatically, leave a clear audit trail showing who impersonated whom, and never produce a file that can be leaked.