Most Google Kubernetes Engine compromises that reach beyond the cluster do so through identity: a Pod that inherits the node's service account, a role binding to a group that is far wider than anyone intended, or a long-lived key stored as a Kubernetes Secret. GKE layers two authorisation systems, Google Cloud IAM and Kubernetes RBAC, on top of two kinds of workload identity, Kubernetes ServiceAccounts and IAM service accounts, and the joins between them are where privilege leaks. This guide covers how authentication and authorisation actually work in GKE, how to lock down admin roles, how to give workloads short-lived credentials with Workload Identity Federation for GKE, what to log, and a hardening baseline you can enforce with organisation policy. It is written for platform engineers, cloud security architects and Kubernetes administrators running Standard or Autopilot clusters. See the Kubernetes Security Guide for the generic model.
Key takeaways
- Two authorisers, one outcome. GKE checks Kubernetes RBAC and Google Cloud IAM, and either one granting the permission is enough, so a broad IAM role at project level silently overrides careful RBAC design.
- Workload Identity Federation is the default answer. Google states it should be used instead of metadata concealment in all situations; it is always on in Autopilot and must be enabled per cluster and per node pool in Standard.
- The node service account is not a workload identity. Replace the Compute Engine default service account with a dedicated one holding roles/container.defaultNodeServiceAccount, and keep workloads off it.
- Identity sameness is a trust boundary. Every cluster in a project shares one workload identity pool, so the same namespace and ServiceAccount name in any cluster in that project receives the same Google Cloud access.
- Enforce, do not just configure. GKE managed organisation policy constraints can require Workload Identity Federation, Google Groups for RBAC, a non-default node service account and disabled legacy authentication across every project.
How identity and access work in GKE
Every request to a GKE control plane goes through authentication, then authorisation. Understanding the sequence explains most GKE privilege mistakes.
Authentication
- Human users: authenticate with Google credentials through the gcloud CLI and the
gke-gcloud-auth-plugin, after runninggcloud container clusters get-credentials. The credential must carry theuserinfo.emailscope. A user needs at leastcontainer.clusters.get(included in Kubernetes Engine Cluster Viewer and higher) just to authenticate; that permission grants nothing inside the cluster. - In-cluster workloads: use Kubernetes ServiceAccount tokens to call the Kubernetes API, authorised by RBAC. See the Kubernetes Service Account Token Guide.
- Workloads calling Google Cloud APIs: exchange their Kubernetes ServiceAccount token for a short-lived federated access token through the GKE metadata server and Security Token Service (Workload Identity Federation for GKE).
- Legacy methods: client certificates issued by the cluster root CA, and static passwords (basic authentication), which were removed in GKE 1.19 and later. Neither is available in Autopilot. Existing client certificates cannot be removed from a cluster, which is why the flag
--no-issue-client-certificatematters at creation time.
Authorisation: RBAC then IAM
GKE first evaluates Kubernetes RBAC. If no RBAC rule grants the request, it evaluates IAM. If either grants the permission, the request is allowed. RBAC has no deny rules, so the effective permission of any principal is the union of everything RBAC and IAM give it. Google notes that RBAC can almost always be used instead of IAM for in-cluster permissions.
| Layer | Principal types | Scope | Typical use |
|---|---|---|---|
| Google Cloud IAM | Google accounts, Google groups, IAM service accounts, workload identity principals | Organisation, folder, project (applies to every cluster in the project) | Create and upgrade clusters, coarse read access, Google Cloud API access for workloads |
| Kubernetes RBAC | Users (email), Google groups (with Google Groups for RBAC), Kubernetes ServiceAccounts | Cluster (ClusterRoleBinding) or namespace (RoleBinding) | Fine-grained in-cluster permissions per team and namespace |
| Workload Identity Federation for GKE | principal:// and principalSet:// identifiers in the pool PROJECT_ID.svc.id.goog | ServiceAccount, namespace or whole cluster | Pods calling Cloud Storage, BigQuery, Secret Manager and other Google APIs |
Test with kubectl auth can-i: without --as it reflects IAM and RBAC for your own identity, with --as it evaluates RBAC for the impersonated subject. For the RBAC model itself, see the Kubernetes RBAC Guide.
Admin roles and privileged access
GKE privilege comes from two places: predefined IAM roles on the project and cluster-admin style RBAC bindings in each cluster. Both must be inventoried.
| Predefined IAM role | What it allows | Hardening note |
|---|---|---|
| roles/container.admin (Kubernetes Engine Admin) | Full management of clusters and their Kubernetes API objects | Effectively cluster-admin on every cluster in the project. Grant through just-in-time elevation, not standing assignment. |
| roles/container.clusterAdmin (Kubernetes Engine Cluster Admin) | Manage clusters but not Kubernetes API objects | Suits platform teams that create and upgrade clusters. Setting a node service account also needs roles/iam.serviceAccountUser on it. |
| roles/container.developer (Kubernetes Engine Developer) | Work with Kubernetes API objects inside clusters | Project-wide, so it covers all namespaces in all clusters. Prefer namespace RoleBindings. |
| roles/container.viewer (Kubernetes Engine Viewer) | Read-only access to cluster resources, including GKE API objects | Scope it to the teams that need it. |
| roles/container.clusterViewer (Kubernetes Engine Cluster Viewer) | Get and list clusters | The right IAM baseline for users whose in-cluster rights come from RBAC. |
| roles/container.serviceAgent | Used by the GKE service agent to manage cluster resources | Google warns not to grant service agent roles to any principal except service agents. |
- Pattern for least privilege: give humans roles/container.clusterViewer in IAM and all in-cluster rights through RBAC bound to Google groups, so access is per namespace and reviewable in one place.
- Google Groups for RBAC: create a group named exactly
gke-security-groups@yourdomain, nest role groups inside it (never individual users), grant the groups View members permission and enable the feature with--security-group="gke-security-groups@DOMAIN". It requires Google Workspace or Cloud Identity, supports up to 2,000 groups per user under the security group, and caches credentials for about an hour, so removals are not instant. Group names in bindings are case-sensitive. - escalate and bind verbs: creating a Role or RoleBinding normally requires already holding the permissions it grants. GKE does not recommend granting
escalateorbind, because the holder can then grant any permission to anyone. The same applies toimpersonateand to write access on RoleBindings. - Break-glass: keep one documented emergency path to cluster-admin (a group with no standing members, or a time-bound IAM grant) and alert on its use. See the Break-Glass Account Guide.
Integrations, API tokens and service accounts
Non-human identities dominate a GKE estate: node service accounts, Kubernetes ServiceAccounts, IAM service accounts linked to them, CI/CD deployers and controllers. See the Kubernetes NHI Security Guide for the wider inventory.
The node service account
Nodes still use the node pool's IAM service account to pull images and to run the built-in logging and monitoring agents, even when Workload Identity Federation is on. Without Workload Identity Federation, every Pod on the node can obtain that account's token from the metadata server, which is how a single compromised container reaches project resources (ATT&CK T1552.005 Cloud Instance Metadata API).
- Use a dedicated account: create a custom IAM service account per cluster or per node pool class and grant it roles/container.defaultNodeServiceAccount (plus Artifact Registry Reader if it pulls private images). Do not use the Compute Engine default service account, which commonly carries broad project rights.
- Avoid compute permissions: Google advises against giving nodes permissions such as
compute.instances.getor the Compute Instance Admin role, and recommends restricting nodes through IAM permissions rather than access scopes.
Workload Identity Federation for GKE
GKE creates one workload identity pool per project, PROJECT_ID.svc.id.goog, and registers each enabled cluster as a provider. The GKE metadata server runs on every node (a DaemonSet on Linux, a native service on Windows), intercepts requests to metadata.google.internal (169.254.169.254:80) and exchanges the Pod's ServiceAccount token for a federated access token with a default lifetime of one hour.
| Grant model | Principal or member | When to use |
|---|---|---|
| Direct grant to one ServiceAccount (preferred) | principal://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/PROJECT_ID.svc.id.goog/subject/ns/NAMESPACE/sa/KSA | Default for new workloads: no IAM service account to manage, no impersonation step. |
| Grant to ServiceAccount UID | .../kubernetes.serviceaccount.uid/SERVICEACCOUNT_UID | When a deleted and recreated ServiceAccount of the same name must not inherit access. |
| Namespace or cluster principal set | principalSet://.../namespace/NAMESPACE or .../kubernetes.cluster/https://container.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/clusters/CLUSTER | Shared read access only; it widens blast radius to every Pod in scope. |
| Link to an IAM service account | serviceAccount:PROJECT_ID.svc.id.goog[NAMESPACE/KSA] granted roles/iam.workloadIdentityUser, plus annotation iam.gke.io/gcp-service-account | Only where a Google Cloud service does not accept federated tokens. Both the binding and the annotation are required. |
- Identity sameness: workloads with the same namespace and ServiceAccount name in different clusters of the same project are one identity to IAM. An untrusted cluster in the project could create a matching namespace and gain the same access. Mitigate by placing clusters of different trust levels in separate projects, using distinct namespace names, or adding IAM conditions that restrict access to a named cluster. See the Workload Identity Federation Guide.
- hostNetwork Pods: Pods with
hostNetwork: truedo not use Workload Identity Federation; their metadata requests go to the Compute Engine metadata server and therefore to the node service account. Block hostNetwork for application workloads with admission control. See the Kubernetes Admission Control Guide. - No keys in Secrets: exported IAM service account keys stored as Kubernetes Secrets are the alternative Google explicitly does not recommend. Store application secrets in Secret Manager rather than Kubernetes Secrets where possible. See the Google Cloud Service Account Keys Security Guide.
- CI/CD deployers: pipelines outside Google Cloud should authenticate with workload identity federation from their own identity provider rather than downloaded keys, and hold namespace-scoped RBAC, not roles/container.admin.
Metadata concealment versus Workload Identity Federation
Metadata concealment (the deprecated --workload-metadata=SECURE option, still Beta) filtered Pod access to kube-env, which holds kubelet credentials, and to the VM instance identity token. Workload Identity Federation protects the same metadata and also replaces node-level credentials with per-workload ones. The two are incompatible, and Google states Workload Identity Federation should be used instead of metadata concealment in all situations. Treat any remaining SECURE node pools as migration debt.
Logging and detection
GKE sends Kubernetes audit logs and GKE audit logs to Cloud Logging by default. Admin Activity audit logs are always on and cannot be disabled; Data Access audit logs are off until you enable them, and reading them from the _Default bucket needs the Private Logs Viewer role rather than Logs Viewer. Kubernetes API entries use the resource type k8s_cluster and service name k8s.io.
| Signal | Where to find it | Why it matters (ATT&CK) |
|---|---|---|
| ClusterRoleBinding or RoleBinding created or changed, especially to cluster-admin | Admin Activity, io.k8s.authorization.rbac.v1 methods | Persistence and privilege escalation (T1098 Account Manipulation) |
Bindings to system:authenticated, system:unauthenticated or system:anonymous | Admin Activity, filter on subject names | Exposes the API to any Google account or anonymous callers |
pods/exec and pods/attach requests | Kubernetes audit log | T1609 Container Administration Command |
| Secret reads by unexpected principals, and ServiceAccount token creation | Data Access (must be enabled) | T1552.007 Container API |
IAM policy changes adding principal:// or roles/iam.workloadIdentityUser members | Cloud Audit Logs for IAM and Resource Manager | New workload paths into Google Cloud resources |
| Node service account used from outside its nodes, or creating keys | IAM audit logs, service account key creation | T1078.004 Cloud Accounts, T1098.001 Additional Cloud Credentials |
To confirm which Google group granted an access decision, read the authorization.k8s.io/decision and authorization.k8s.io/reason labels on the audit entry. Route audit logs to a sink in a separate security project (Cloud Storage, BigQuery or Pub/Sub) so cluster administrators cannot delete their own trail.
Metrics that show it is working
- Workload identity coverage: percentage of Standard node pools running
GKE_METADATA(target 100%). - Default node accounts: number of node pools using the Compute Engine default service account (target 0).
- Static keys: count of user-managed IAM service account keys used by GKE workloads (target 0, with dated exceptions).
- Standing cluster-admin: principals with roles/container.admin or a cluster-admin ClusterRoleBinding outside break-glass (trend down).
- Broad subject bindings: bindings to
system:authenticatedorsystem:unauthenticatedbeyond the GKE defaults (target 0).
Hardening baseline
The baseline below follows Google's cluster hardening guidance. Most items can be enforced with GKE managed organisation policy constraints, which is the strongest control because it applies to clusters teams have not yet created.
| Control | Setting | Enforce with |
|---|---|---|
| Workload Identity Federation on | --workload-pool=PROJECT_ID.svc.id.goog; node pools --workload-metadata=GKE_METADATA (always on in Autopilot) | constraints/container.managed.enableWorkloadIdentityFederation |
| No default Compute Engine service account on nodes | Custom node account with roles/container.defaultNodeServiceAccount | constraints/container.managed.disallowDefaultComputeServiceAccount |
| Legacy client certificates off | --no-issue-client-certificate at creation | constraints/container.managed.disableLegacyClientCertificateIssuance |
| Legacy ABAC off | Disabled by default, unavailable in Autopilot | constraints/container.managed.disableABAC |
| Google Groups for RBAC | --security-group="gke-security-groups@DOMAIN" | constraints/container.managed.enableGoogleGroupsRBAC |
| Anonymous access limited | --anonymous-authentication-config=LIMITED (GKE 1.32.2-gke.1234000 and later; default for clusters created on 1.35.0-gke.1171000 and later) | Custom organisation policy on resource.anonymousAuthenticationConfig.mode |
| Control plane endpoint restricted | DNS-based endpoint only, or private endpoint, or authorised networks | constraints/container.managed.enableControlPlaneDNSOnlyAccess |
| Private nodes | No external node IPs | constraints/container.managed.enablePrivateNodes |
| Shielded GKE Nodes | Secure boot and integrity monitoring | constraints/container.managed.enableShieldedNodes |
| Audit logging kept on | Do not disable log collection in Standard | constraints/container.managed.enableCloudLogging |
Two points need human judgement. First, the default discovery roles grant read access to system:authenticated, which in GKE means any authenticated Google account, so Google advises treating discovery data as public unless the control plane is network-isolated. Second, Kubernetes Secrets are described as not recommended for sensitive data; prefer Secret Manager, and if Secrets must be used, encrypt them at the application layer with a key you manage. See the Kubernetes Secrets Security Guide and the Google Cloud Organization Policy Guide.
Rollout sequence
- Inventory: list every cluster, node pool, node service account, IAM binding on
container.*roles, ClusterRoleBinding and ServiceAccount with aiam.gke.io/gcp-service-accountannotation. Runkube-benchagainst worker nodes; the control plane is Google-managed and cannot be assessed by customers. - Set organisation policy in dry-run first: apply the managed constraints at a test folder, review violations, then enforce at the organisation.
- Swap node service accounts: create new node pools with the dedicated account and migrate workloads, rather than editing pools in place.
- Enable Workload Identity Federation on new node pools: Google recommends new node pools because updating an existing pool takes effect immediately and running workloads lose access to the node service account. Use the nodeSelector
iam.gke.io/gke-metadata-server-enabled: "true"to move Pods in Standard. Allow egress to 169.254.169.252/32 port 988 (169.254.169.254/32 port 80 with Dataplane V2) if strict network policies are in place. - Move humans to groups: enable Google Groups for RBAC, create namespace RoleBindings to groups, then remove project-wide roles/container.developer grants.
- Test and roll back: verify with
kubectl auth can-i --asand the audit labels. Rollback for Workload Identity Federation is to set node pools back toGCE_METADATA; revoking a single workload is done by removing its IAM allow policy.
Small versus large estates
A small team with one or two clusters gets most of the value from Autopilot (Workload Identity Federation, Shielded Nodes and no legacy authentication by default), a dedicated project per environment, and direct principal:// grants. Large estates should add folder-level organisation policy, a separate project per trust zone to avoid identity sameness, Google Groups for RBAC fed from the identity governance process, and centralised audit sinks. See the Google Cloud IAM and Service Accounts Guide.
Framework mapping
| Control area | NIST SP 800-53 Rev. 5 | NIST CSF 2.0 | CIS Controls v8.1 |
|---|---|---|---|
| Workload and node identities, no static keys | IA-5, AC-2 | PR.AA-01 | 5 Account Management |
| IAM and RBAC least privilege, groups | AC-6, AC-2 | PR.AA-05 | 6 Access Control Management |
| Legacy authentication disabled, hardened baseline | CM-7, IA-2 | PR.PS-01 | 4 Secure Configuration |
| Audit logging and review | AU-12, AU-6 | DE.CM-03, DE.CM-09 | 8 Audit Log Management |
Common misconfigurations
- Project-wide developer roles: roles/container.developer granted at project level gives every member write access to every namespace in every cluster, regardless of RBAC.
- Bindings to system:authenticated: a ClusterRoleBinding to
system:authenticatedin GKE grants that access to any Google account holder, not just your staff. - Node pools left on GCE_METADATA: one legacy pool keeps the node service account reachable from every Pod scheduled there, often the pool running older or third-party workloads.
- Over-permissioned in-cluster ServiceAccounts: default or shared ServiceAccounts that can create Pods let an attacker who lands in one container launch more. In the Kubeflow cryptomining attacks, notebook servers exposed to the internet had a mounted ServiceAccount that could deploy containers in its namespace, which the attackers used to run miners.
- Mixed-trust clusters in one project: a development cluster and a production cluster in the same project share the workload identity pool, so a matching namespace and ServiceAccount name in development inherits production Google Cloud access.
- Keys as Secrets: service account key JSON files committed to manifests or stored as Kubernetes Secrets, outliving the workloads they were made for.
Practitioner checklist
- Workload Identity Federation enabled on every Standard cluster and every node pool runs
GKE_METADATA. - No node pool uses the Compute Engine default service account; node accounts hold roles/container.defaultNodeServiceAccount only, plus registry read where needed.
- Humans hold roles/container.clusterViewer in IAM, with in-cluster rights through RBAC bound to groups nested under
gke-security-groups. - roles/container.admin has no standing members outside a monitored break-glass path.
- No RBAC role grants
escalate,bindorimpersonateoutside platform tooling, and no binding targetssystem:authenticatedbeyond GKE defaults. - Clusters of different trust levels sit in separate projects, or IAM conditions restrict workload grants to named clusters.
- Legacy client certificates, basic authentication and ABAC are disabled, and anonymous access is set to
LIMITED. - No user-managed service account keys are mounted into Pods; secrets come from Secret Manager.
- Data Access audit logs are enabled for the Kubernetes Engine API where secret reads matter, and audit logs flow to a sink administrators cannot alter.
- GKE managed organisation policy constraints are enforced at the organisation or folder level.
Standards and references
- Google Cloud: About Workload Identity Federation for GKE
- Google Cloud: Authenticate to Google Cloud APIs from GKE workloads
- Google Cloud: Harden your cluster's security
- Google Cloud: Authorize actions in clusters using role-based access control
- Google Cloud: Configure Google Groups for RBAC
- Google Cloud: Protect cluster metadata
- Google Cloud: GKE audit logging information
- MITRE ATT&CK: Cloud Instance Metadata API
Related NHI Mgmt Group resources: Google Cloud Security Guide · Kubernetes NHI Security Guide · Cloud Workload Identity Guide · Top 10 Kubernetes Security Risks