Service Accounts and Workload Identity on GCP
A pipeline cannot sign in with a password, so on Google Cloud it runs as a service account. This guide explains the kinds of service account, the five ways a workload proves it is one, why key files are the last resort, and how Workload Identity Federation lets GitHub Actions and GKE pods authenticate with no key.
GloryTecks Team
Training & Placement Faculty
Last updated: 2 October 2026
A pipeline that loads files into BigQuery at two in the morning cannot type a password or answer a phone prompt. It still has to be somebody, because Google Cloud checks every request against an identity. For a workload, that identity is a service account. How safe the arrangement is depends less on the account than on how the workload proves it is that account. This guide covers GCP service accounts and the five ways to authenticate as one, from the method to prefer to the one to avoid.
What a service account is
A service account is an account used by a workload, not by a person. It is identified by an email address, has no password and cannot sign in through a browser (Google Cloud: service accounts overview). One that you create in a project has an address of the form service-account-name@project-id.iam.gserviceaccount.com.
It has a dual nature, and most of the security advice follows from it:
- It is a principal. You grant it roles on resources, as you would a user: read access to a bucket, write access to a dataset.
- It is a resource. It has its own allow policy, which states who may attach it, impersonate it or create keys for it.
So every service account raises two questions. What can it do? And who can become it? A narrowly scoped account that every developer in the company can impersonate is not narrow. Roles and policies themselves are covered in GCP IAM explained.
Three kinds you will meet
| Kind | Created by | What to do with it |
|---|---|---|
| User-managed | You, one per workload | Use these |
| Default | Google Cloud, when you enable or use certain services such as Compute Engine | Replace with your own |
| Service agent | Google Cloud, so its services can act on your behalf | Leave alone |
Default service accounts deserve attention. Depending on organization policy, a default account might automatically be granted the Editor role on the project. Google strongly recommends disabling that automatic grant through an organization policy constraint, and enforces the constraint by default for organizations created after 3 May 2024 (Google Cloud: types of service accounts). In an older project, check. A virtual machine running as a default account with Editor can change nearly everything in the project, and so can anyone able to run code on that machine.
Service agents appear in allow policies and audit logs with Google-owned addresses. They are what let, for example, a logging sink write to a storage bucket. They are not created in your project and you cannot use them directly.
Five ways to prove identity
| Method | Where the workload runs | Secret you must store |
|---|---|---|
| Attached service account | On Google Cloud: a VM, a serverless service, a managed job | None |
| Workload Identity Federation for GKE | Pods on Google Kubernetes Engine | None |
| Impersonation | A developer's machine, or one account acting as another | None |
| Workload Identity Federation | Outside Google Cloud, where another identity provider vouches for the workload | None on the Google side |
| Service account key | Anywhere | A private key file |
The first four produce short-lived tokens. Only the last involves a long-lived secret, which is why it sits at the bottom.
Client libraries find credentials through Application Default Credentials, which looks in three places in order: the GOOGLE_APPLICATION_CREDENTIALS environment variable, a file created by gcloud auth application-default login, and the attached service account supplied by the metadata server (Google Cloud: Application Default Credentials). Code written this way runs unchanged on a laptop and in production.
Attached service accounts
For code that runs on Google Cloud, attach a user-managed account to the resource. The code then authenticates as that account with no configuration at all.
Attaching requires the Service Account User role on the account. That role is the gate: whoever can attach an account to a resource can run code as it. The commands below create an account, state what it can do, and state who can use it (Google Cloud: create service accounts).
# One account for one workload
gcloud iam service-accounts create nightly-loader \
--description="Loads landing files into the analytics dataset" \
--display-name="Nightly loader"
# What it can do: read one bucket, run jobs in one project
gcloud storage buckets add-iam-policy-binding gs://example-landing \
--member="serviceAccount:nightly-loader@analytics-prod.iam.gserviceaccount.com" \
--role="roles/storage.objectViewer"
gcloud projects add-iam-policy-binding analytics-prod \
--member="serviceAccount:nightly-loader@analytics-prod.iam.gserviceaccount.com" \
--role="roles/bigquery.jobUser"
# Who can use it: one group may attach it to a resource
gcloud iam service-accounts add-iam-policy-binding \
nightly-loader@analytics-prod.iam.gserviceaccount.com \
--member="group:data-platform@example.com" \
--role="roles/iam.serviceAccountUser"Note where the last grant is made: on the account itself, not on the project. The same role granted on the project would let the group act as every service account in it.
Impersonation
Impersonation involves two identities: an authenticated principal, and the service account it borrows. A principal holding the Service Account Token Creator role on an account can obtain a short-lived token for it. Google describes this as similar to AssumeRole on AWS (Google Cloud: service account impersonation), which is covered in AWS IAM explained.
gcloud storage ls gs://example-landing \
--impersonate-service-account=nightly-loader@analytics-prod.iam.gserviceaccount.comThree things make it the right tool for people:
- No key file reaches a laptop. The credential lasts a few hours or less.
- The trail names the person. Most audit logs record both the caller and the impersonated account. With a key, or on a shared machine, the logs show only the service account.
- It tests permissions honestly. Running a pipeline locally as its own account shows whether the account's roles are sufficient, before deployment.
Why keys are the last resort
A service account key is the private half of an RSA key pair. Holding it is similar to knowing a user's password, with one difference that makes it worse: a stolen password often fails at a second verification step, while a stolen key simply works, because service accounts have no additional sign-in checks (Google Cloud: managing service account keys).
Google lists the threats: credential leakage, privilege escalation, information disclosure and non-repudiation. The last means that actions taken with a key cannot be traced to a person. Keys leak through ordinary places: source repositories, public buckets, email, file shares, backups and temporary directories.
A key created in IAM has no expiry time by default and stays valid until deleted. Google's guidance is to avoid user-managed keys wherever another method exists. Where a tool truly accepts nothing else, restrict who can create keys, rotate them, and keep the account's roles minimal.
Workload Identity Federation
A workload outside Google Cloud usually has an identity already: an AWS role, a Microsoft Entra ID application, a GitHub Actions workflow. Workload Identity Federation lets Google Cloud trust that identity directly. The workload presents the token it already has, Google's Security Token Service checks it and returns a short-lived federated token, and no Google key exists to leak (Google Cloud: Workload Identity Federation).
Four pieces make it work:
- A workload identity pool: a container for external identities. Google recommends one per external environment.
- A provider: the trust relationship with one identity provider, named by its issuer.
- An attribute mapping: which claims in the external token become Google attributes.
google.subjectis required; custom attributes are optional. - An attribute condition: an expression that must be true, or the credential is rejected.
For GitHub Actions the setup is three commands (Google Cloud: federation with deployment pipelines):
gcloud iam workload-identity-pools create ci-pool \
--location="global" \
--display-name="CI pipelines"
gcloud iam workload-identity-pools providers create-oidc github \
--location="global" \
--workload-identity-pool="ci-pool" \
--issuer-uri="https://token.actions.githubusercontent.com/" \
--attribute-mapping="google.subject=assertion.sub,attribute.repository_id=assertion.repository_id,attribute.repository_owner_id=assertion.repository_owner_id" \
--attribute-condition="assertion.repository_owner_id=='1001'"
gcloud storage buckets add-iam-policy-binding gs://example-landing \
--role="roles/storage.objectViewer" \
--member="principalSet://iam.googleapis.com/projects/123456789012/locations/global/workloadIdentityPools/ci-pool/attribute.repository_id/5001"The condition is not optional in practice. GitHub uses one issuer for every organization on the platform, so a provider without a condition would accept a valid token from any repository anywhere. Google's documentation also advises conditions on the numeric id claims, not on names, because a deleted organization's name can be registered again by someone else.
What the condition and the binding each do
The script below models the flow for four tokens. It is a model written for this article, not the Security Token Service, and the claims are sample values. The subject follows the format GitHub documents for a branch, repo:OWNER/REPOSITORY:ref:refs/heads/BRANCH (GitHub: OpenID Connect reference).
# A model of Workload Identity Federation for a deployment pipeline: check the issuer and the
# attribute condition, map the token's claims, build the principal identifiers, then look for a
# role binding. It is not Google's Security Token Service; the claims are sample values.
POOL = "projects/123456789012/locations/global/workloadIdentityPools/ci-pool"
ISSUER = "https://token.actions.githubusercontent.com"
MAPPING = { # target attribute <- claim in the pipeline's token
"google.subject": "sub",
"attribute.repository_id": "repository_id",
"attribute.repository_owner_id": "repository_owner_id",
}
ALLOWED_OWNER_ID = "1001" # condition: assertion.repository_owner_id == '1001'
BUCKET_BINDINGS = { # principal -> role granted on one bucket
f"principalSet://iam.googleapis.com/{POOL}/attribute.repository_id/5001": "roles/storage.objectViewer",
}
def exchange(claims):
"""Return the mapped attributes, or None with the reason the credential is rejected."""
if claims["iss"] != ISSUER:
return None, "rejected: the issuer is not this provider's"
if claims["repository_owner_id"] != ALLOWED_OWNER_ID:
return None, "rejected: the attribute condition is false"
return {target: claims[source] for target, source in MAPPING.items()}, "accepted"
def roles_on_bucket(mapped):
principals = [f"principal://iam.googleapis.com/{POOL}/subject/{mapped['google.subject']}"]
principals += [f"principalSet://iam.googleapis.com/{POOL}/{name}/{value}"
for name, value in mapped.items() if name.startswith("attribute.")]
return [BUCKET_BINDINGS[p] for p in principals if p in BUCKET_BINDINGS]
TOKENS = [
("pipelines repository", {"iss": ISSUER, "sub": "repo:example-org/data-pipelines:ref:refs/heads/main",
"repository_id": "5001", "repository_owner_id": "1001"}),
("another repository, same owner", {"iss": ISSUER, "sub": "repo:example-org/website:ref:refs/heads/main",
"repository_id": "5002", "repository_owner_id": "1001"}),
("look-alike owner name", {"iss": ISSUER, "sub": "repo:examp1e-org/data-pipelines:ref:refs/heads/main",
"repository_id": "9001", "repository_owner_id": "2002"}),
("token from another issuer", {"iss": "https://gitlab.com",
"sub": "project_path:example-org/data-pipelines:ref_type:branch:ref:main"}),
]
for label, claims in TOKENS:
mapped, verdict = exchange(claims)
if mapped is None:
print(f"{label:31} {verdict}")
continue
roles = roles_on_bucket(mapped)
print(f"{label:31} {verdict}; " + (f"holds {roles[0]} on the bucket" if roles else "no role on the bucket, so no access"))pipelines repository accepted; holds roles/storage.objectViewer on the bucket
another repository, same owner accepted; no role on the bucket, so no access
look-alike owner name rejected: the attribute condition is false
token from another issuer rejected: the issuer is not this provider's- The pipelines repository is accepted and authorised. Its owner satisfies the condition, and its repository id matches the binding on the bucket.
- Another repository of the same owner is accepted and gets nothing. The pool recognises it; no role binding names it. Authentication is not authorisation, and the two are configured in different places.
- The look-alike owner is rejected. The name differs by one character, which a hurried reviewer might miss. The condition compares the numeric id, so the resemblance does not matter.
- A token from another issuer never reaches the condition.
Granting roles straight to the federated principal, as here, is what Google recommends. Where an API does not accept federated tokens, the external identity is instead allowed to impersonate a service account.
Workload Identity Federation for GKE
Pods on Google Kubernetes Engine get the same treatment with less setup. When the feature is enabled, GKE creates a fixed pool named PROJECT_ID.svc.id.goog, registers the cluster as an identity provider and runs a metadata server on every node. When a pod asks for credentials, that server exchanges the pod's Kubernetes ServiceAccount token for a short-lived federated token (Google Cloud: Workload Identity Federation for GKE).
gcloud container clusters update analytics-cluster \
--location=asia-south1 \
--workload-pool=analytics-prod.svc.id.goog
gcloud storage buckets add-iam-policy-binding gs://example-landing \
--role="roles/storage.objectViewer" \
--member="principal://iam.googleapis.com/projects/123456789012/locations/global/workloadIdentityPools/analytics-prod.svc.id.goog/subject/ns/pipelines/sa/loader"Two points catch teams out. Enabling the feature grants nothing by itself; each Kubernetes ServiceAccount still needs roles. And the principal is identified by namespace and ServiceAccount name, not by cluster. Two clusters in one project that both have a loader account in a pipelines namespace are the same identity to IAM. Google's advice is to put clusters you do not equally trust in separate projects.
Choosing a method
Google's own guidance reduces to four questions (Google Cloud: using service accounts securely):
- Is this a developer's own environment? Use your user credentials, or impersonate a service account when the work needs one.
- Does the code run on Google Cloud? On GKE, use Workload Identity Federation for GKE. Elsewhere, attach a service account.
- Does it run elsewhere with an identity provider that supports federation? Configure Workload Identity Federation.
- None of these? Only then create a key.
The same idea appears on other clouds under other names; managed identities in Azure Data Factory are the Azure counterpart of an attached account.
Habits that hold up
- One account per workload. Shared accounts accumulate permissions and can never be safely deleted.
- No basic roles. Remove Editor from default accounts and grant specific roles on specific resources, such as one Cloud Storage bucket or one BigQuery dataset.
- Grant access to an account on the account, never at project or folder level.
- Do not let anyone impersonate an account more privileged than they are. That is privilege escalation by configuration.
- Block key creation by organization policy, and make exceptions deliberately.
- Limit shell access to machines that have a privileged account attached.
- Disable an unused account before deleting it, so a mistake can be undone.
What is the difference between a service account and a user account on Google Cloud?
Are service account keys safe to use?
Do GitHub Actions need a service account key to deploy to Google Cloud?
Does enabling Workload Identity Federation for GKE give pods access to Google Cloud?
Google Cloud, pipelines and governance are part of the Data Engineering course.
Sources
- Service accounts overview (Google Cloud Documentation) — Google Cloud
- Types of service accounts (Google Cloud Documentation) — Google Cloud
- Create service accounts (Google Cloud Documentation) — Google Cloud
- How Application Default Credentials works (Google Cloud Documentation) — Google Cloud
- Service account impersonation (Google Cloud Documentation) — Google Cloud
- Best practices for managing service account keys (Google Cloud Documentation) — Google Cloud
- Workload Identity Federation (Google Cloud Documentation) — Google Cloud
- Configure Workload Identity Federation with deployment pipelines (Google Cloud Documentation) — Google Cloud
- OpenID Connect reference (GitHub Docs) — GitHub
- About Workload Identity Federation for GKE (Google Cloud Documentation) — Google Cloud
- Best practices for using service accounts securely (Google Cloud Documentation) — Google Cloud
GloryTecks Team
Training & Placement Faculty
The GloryTecks faculty team mentors learners across Data Science, AI and Data Engineering, with a focus on real-time projects and placement readiness in Hyderabad.
Want to master GCP?
Join GloryTecks’ expert-led Data Engineering training in Hyderabad with real-time projects and 100% placement support.
Related articles
GCP Roadmap 2026 for Data Engineers
On Google Cloud the warehouse is the centre of the data stack, so this roadmap starts inside BigQuery, in the free sandbox, and moves outward in seven rings: storage and access, loading, table design, transformation, streaming and operations. Each ring has a build, and the renamed services are listed.
BigQuery Explained: Google's Serverless Data Warehouse
What “serverless” means in BigQuery, why its bill depends on the bytes a query reads, and the three habits that keep that number small: select only needed columns, partition and cluster tables, and check a query’s cost before running it.
Dataflow Tutorial: Stream and Batch Processing on GCP
Dataflow runs Apache Beam pipelines as a managed service, with one programming model for batch and streaming. This tutorial explains the model, runs a real pipeline locally on 77,033 taxi trips with a batch branch and a windowed branch, and covers windows, watermarks, late data, templates and how to run on Dataflow.