Updated recently
Last updated:
Quick Answer: GCP service accounts authentication leaks almost always come down to one thing: a long-lived JSON key committed to a repository or stored in a CI variable. The fix is structural, not procedural — authenticate workloads with attached service accounts on Google infrastructure, use Workload Identity Federation for external CI systems, reserve impersonation for human access, and enforce the org policy that blocks new key creation.
How GCP Service Accounts Authentication Actually Works
Start from the model, because most bad practices come from a wrong mental picture. A service account is a Google identity — not a username, an identity with an email-like identifier, keys, and IAM bindings that grant it roles. GCP’s resource hierarchy (organization, folders, projects, resources) determines which bindings apply. When a workload calls a GCP API, Google issues a short-lived OAuth token for that identity, and every call is authorised against the bindings in effect at that moment.
The transport of that identity is where things go wrong. Google’s own infrastructure — Compute Engine, Cloud Run, GKE with Workload Identity — attaches a service account to the runtime and handles tokens automatically; no key file ever exists. The moment your workload runs somewhere Google does not control — a laptop, a vendor’s CI, an on-premises job — you need a different mechanism, and the reflex most engineers reach for is downloading a JSON key. That reflex is the root cause of nearly every GCP credential incident you will read about.
Your Authentication Options, Ranked Safest to Riskiest
| Method | Where It Fits | Credential Lifetime | Leak Risk |
|---|---|---|---|
| Attached service account (GCE, Cloud Run) | Workloads on Google infrastructure | None exposed — tokens handled for you | Lowest |
| Workload Identity (GKE) | Workloads on GKE | Short-lived, injected by the node agent | Very low |
| Workload Identity Federation | GitHub Actions, GitLab CI, on-prem, other clouds | Short-lived, exchanged per run | Low |
| Service-account impersonation | Humans and services calling on behalf of another SA | Short-lived tokens | Low, if bindings are tight |
| User-managed JSON keys | Legacy systems that genuinely cannot do anything else | Years by default | High — this is the leak |
The Two Setups You Will Actually Need
For human access and for services calling on behalf of a service account, impersonation is the pattern: the caller’s own identity is granted roles/iam.serviceAccountTokenCreator on the target service account, and it mints short-lived tokens rather than holding a key.
# Grant a user impersonation rights instead of handing them a key file
gcloud iam service-accounts add-iam-policy-binding
[email protected]
--member="user:[email protected]"
--role="roles/iam.serviceAccountTokenCreator"
# Developer then works with:
# gcloud auth login
# gcloud auth print-access-token --impersonate-service-account=trainer-sa@my-project.iam.gserviceaccount.com
For CI systems running outside Google infrastructure, Workload Identity Federation is the correct answer. The CI proves its identity through an OIDC token from its provider, and GCP exchanges that for a short-lived credential. No JSON key exists at any point in the flow:
# WIF: let GitHub Actions authenticate with zero keys
gcloud iam workload-identity-pools create "ci-pool"
--project=my-project
--location=global
--display-name="CI OIDC pool"
gcloud iam workload-identity-pools providers create-oidc "github-provider"
--project=my-project
--location=global
--workload-identity-pool="ci-pool"
--display-name="GitHub"
--issuer-uri="https://token.actions.githubusercontent.com"
--attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository"
The attribute mapping is where teams get sloppy. Map and condition on the repository, and ideally the branch or environment — an unmapped provider that accepts tokens from any repository in your organisation lets a compromised low-trust repo assume a high-privilege identity.
The Org Policy That Prevents the Mistake Before It Happens
Policies and audits only work if they are automatic. Three controls are worth enforcing on any serious GCP estate: iam.disableServiceAccountKeyCreation (no new user-managed keys, org-wide), iam.serviceAccountKeyMaxAge (existing keys expire rather than rot indefinitely), and iam.allowedPolicyMemberDomains for domain-restricted sharing. Combine them with an inventory of what already exists:
# Inventory every user-managed key — this is your actual attack surface
gcloud iam service-accounts keys list
[email protected]
--managed-by=user
--format="table(name, keyType, validAfterTime, validUntilTime)"
Run that across every service account in the organisation and you will usually find keys that have not been used in a year, still valid. Disable them. Then wire Security Command Center findings for public or exposed keys into alerting, and alert on BigQuery and Cloud Storage egress volumes from service accounts specifically — stolen keys rarely stay quiet.
A War Story: The Key in the Public Repository
A fintech-analytics startup in Bangalore trained churn models for e-commerce clients. A junior engineer pushed a training script to a public GitHub repo, and along with it a serviceAccount.json for a service account that had roles/bigquery.jobUser, data editor on the client’s dataset, and storage admin in the same project. The repo went private two days later — after it had already been forked.
The key had been created fourteen months earlier and rotated never. Total data in scope was about 40 GB of client customer records. The startup found out not from their own monitoring but from the client’s security team, who noticed anomalous egress to an unfamiliar bucket. The remediation: keys disabled immediately, IAM bindings rebuilt from scratch around least privilege, BigQuery audit logs reviewed across the full exposure window, a DPDP-flavoured client notification negotiated over three weeks, and two engineers pulled off roadmap work for a month.
What they changed afterwards, in order of impact: the org policy blocking key creation (which alone would have prevented the incident form entirely), Workload Identity Federation for their training CI, impersonation for the two engineers who genuinely needed service-account access, and a monthly keys list --managed-by=user report with an owner attached. The monitoring came last and mattered least — by then the structural fixes had removed the failure mode.
The Practices That Prevent the Next One
- No user-managed keys in production. Attached service accounts, Workload Identity or federation. Exceptions documented with an expiry date.
- One service account per workload, not per team. Blast radius stays small, and audit logs become readable.
- Never grant Editor or Owner to a service account, including “temporarily” during a demo.
- Impersonation over keys for humans. Short-lived tokens, and the grant is revocable in one binding removal.
- Condition federated providers on repository and environment. An unconditioned provider is a shared key with extra steps.
- Monthly key inventory, automated alerting on egress. Assume something exists that you forgot.
If your GCP work sits inside a wider IaC estate, the same discipline applies to infrastructure code — our Terraform vs CDK vs CloudFormation comparison covers where identity bindings belong in pipelines. For the salary side of GCP security specialisation, the cloud security engineer salary data tracks the premium this skill set commands, and the Google Cloud engineer salary guide shows the platform baseline underneath it.
Training Resources
Build these skills in SkilBrill Google Cloud Development Training — API enablement, service-account identity, Workload Identity Federation and IAM hardening taught inside real application projects, not as isolated console exercises.
FAQ
What is a GCP service account?
An identity for a workload or application, with an email-like identifier, optional keys, and IAM role bindings. It is not a user — it has no interactive login, and its credentials are meant to be used by code.
How do I authenticate a service on GCP without a JSON key?
Run it on Google infrastructure with an attached service account or GKE Workload Identity. For external CI or on-premises systems, use Workload Identity Federation, which exchanges an OIDC token for short-lived GCP credentials with no key file involved.
What is Workload Identity Federation?
A keyless authentication mechanism that lets identities from external providers — GitHub Actions, GitLab, AWS, on-prem OIDC — exchange their tokens for short-lived GCP access. Combined with attribute conditions on repository and environment, it removes JSON keys from CI entirely.
How do I find leaked GCP service account keys?
Inventory with gcloud iam service-accounts keys list --managed-by=user across every service account, enable Security Command Center key-exposure findings, and review BigQuery and Cloud Storage audit logs for access from unfamiliar sources. Disable, then rotate, then fix the binding that made the key dangerous.
Are GCP service account keys safe if the repository is private?
No. Private repositories get forked internally, copied into personal accounts and scanned by automated tools within hours. Assume any key committed to version control is compromised, and design so that a leaked key expires quickly and grants almost nothing.
