GKE Security Best Practices: What Actually Needs Hardening
GKE security best practices: Workload Identity, the default service account problem, private endpoints, Dataplane V2 network policy, and audit logs.
GKE Security Best Practices: What Actually Needs Hardening
GKE has a better security story out of the box than most managed Kubernetes. Shielded Nodes are on by default, clusters land in a release channel so they don’t rot on an ancient version, and admin audit logging literally cannot be switched off. Credit where due.
And yet GKE clusters fail audits all the time, usually on the same handful of items. The pattern is familiar: Google secured their half well, and their defaults nudge you in the right direction, but the sharp edges that remain are exactly the ones nobody looks at until a pentest report shows up.
Here’s my list, ordered by how much damage each item causes when it’s wrong.
1. Get nodes off the default service account
This is the GKE-specific finding I see most, and it’s the one with the worst blast radius. Node pools created the quick way often run as the Compute Engine default service account, and in a lot of projects that account still has the project-wide Editor role, either directly or through legacy habits nobody cleaned up.
Follow the chain: pod gets compromised, pod reaches the node’s credentials, node’s credentials can edit half the GCP project. That’s not a cluster incident anymore, it’s a cloud incident.
Check what your node pools actually run as:
gcloud container clusters describe my-cluster --location us-central1 \
--format="value(nodePools[].config.serviceAccount)"
That lists the service account per node pool, which matters because one pool can be fine while another still runs the default.
If any of it says default, or ends in -compute@developer.gserviceaccount.com, go look at what roles that account holds. The fix is a dedicated service account with only the logging and monitoring writer roles, set per node pool. Google has documented this as a best practice for years, and it’s still the most commonly skipped step I come across.
2. Workload Identity for pods, nothing else
Pods that need GCP access should get it through Workload Identity Federation: the pod’s Kubernetes service account maps to a GCP service account, tokens are short-lived, and nothing sensitive sits on the node. If you’re still mounting exported service account JSON keys into pods as secrets, that’s the thing to kill first. Those keys don’t expire, they leak into Git and CI logs, and revoking them is a manual hunt.
Workload Identity has a second benefit people miss: with it enabled, the GKE metadata server stops handing node credentials to pods. You close the classic SSRF-to-cloud-credentials path and get per-workload identity in one move.
Same rule as anywhere else in IAM: keep the mapped roles boring. A workload that reads one bucket gets roles/storage.objectViewer on that bucket, not project-wide storage admin.
3. Stop exposing the API endpoint to the internet
New GKE clusters come with the Kubernetes API reachable from any IP on the internet, authenticated but exposed. Same finding as on EKS, same fix philosophy: private endpoint if your team can work that way, or at minimum authorized networks so only your office and VPN ranges can reach it.
gcloud container clusters update my-cluster --location us-central1 \
--enable-master-authorized-networks \
--master-authorized-networks 203.0.113.0/24
This one takes ten minutes and removes the entire class of “leaked kubeconfig plus exposed endpoint equals immediate compromise” incidents. Worth knowing about too: GKE now offers a DNS-based endpoint for the control plane, gated by IAM instead of source IP, which gets you off the public IP endpoint without running a bastion. If your team is remote-heavy, that’s the modern answer.
4. Make sure network policy is actually enforced
Here’s a GKE trap that has burned real teams: you can apply NetworkPolicy resources to a cluster that doesn’t enforce them. On a Standard cluster running the legacy dataplane without the network policy add-on, kubectl accepts your carefully written default-deny policies and then nothing happens. No error, no warning, no enforcement. And the legacy dataplane is still what a Standard cluster gets by default, this is not just an old-cluster problem.
Where you stand depends on the cluster type. Autopilot and anything on Dataplane V2 enforce policies natively. Standard clusters on the legacy dataplane need network policy explicitly enabled, unless you passed --enable-dataplane-v2 when the cluster was created. Check first:
gcloud container clusters describe my-cluster --location us-central1 \
--format="value(networkConfig.datapathProvider,networkPolicy.enabled)"
If you see ADVANCED_DATAPATH, you’re on Dataplane V2 and policies work. Otherwise, either enable the add-on or plan the move to Dataplane V2. Then do the usual: default-deny per namespace, explicit allows, and don’t forget the DNS exception to kube-system or the first policy you ship will take down name resolution.
5. Turn on the audit logs that are off
GKE’s logging default is half good. Admin Activity audit logs, who created, changed, or deleted what, are always on and can’t be disabled. Data Access logs, who read what, are off by default, and for a security investigation you usually want both.
Enable Data Access logging for the Kubernetes Engine API in the project’s IAM audit log settings, and make sure cluster logging includes workloads and system components. Then set retention somewhere sane. The default 30 days in Cloud Logging is shorter than the average time-to-detection for an intrusion, which is a polite way of saying the logs may be gone before you know you need them.
6. Pod security is entirely on you (unless Autopilot)
Nothing in a Standard GKE cluster stops a developer from deploying a privileged container with hostPath mounts running as root. Google hardened the infrastructure; what runs inside it is your problem.
The fix is the same as vanilla Kubernetes: Pod Security Admission set to baseline or restricted per namespace, or a policy engine like Kyverno or Gatekeeper if you need real policies with exceptions. Autopilot users get a chunk of this for free, privileged pods and host access are blocked at the platform level, which is honestly one of the better reasons to pick it.
While you’re here, the boring baseline applies unchanged: no :latest tags, resource limits on everything, automountServiceAccountToken: false for pods that never call the Kubernetes API, and RBAC without wildcards. None of this is GKE-specific, which is exactly why it gets skipped on GKE.
For a fast pass over that category I use k8s-audit, an open-source script that runs 16 read-only checks with kubectl and jq. Takes about 30 seconds and tells you which of these are actually present in your cluster instead of theoretically possible.
7. Sign and verify images if you’re past the basics
Binary Authorization is GKE’s supply-chain enforcement: only images signed by your CI pipeline, or from allowed registries, get to run. It’s the control that turns “we scan images” into “unscanned images cannot deploy”. Not the first thing to enable on day one, but if you’re preparing for SOC2 or handling anything regulated, it’s the difference between a policy on paper and a policy in the admission controller.
Start in dry-run mode, watch what would have been blocked, then enforce. Going straight to enforce on an existing cluster is a great way to discover every undocumented image source your teams have been using.
8. Let release channels do their job
GKE enrolls clusters in a release channel and auto-upgrades them, and some teams’ first instinct is to fight this. Don’t. The clusters I see with exploitable known CVEs are almost always the ones where someone opted out of the channel “for stability” and then stopped tracking versions entirely.
Pick Regular unless you have a concrete reason for Stable, use maintenance windows to control when upgrades land, and treat the channel as a security control, because that’s what it is.
Where this fits in an audit
If a SOC2 audit or a customer security review is what brought you here: items 1 through 5 map to specific CIS GKE Benchmark controls, and the gcloud output above is most of the evidence. The in-cluster half, pod security, RBAC, network policies, is covered by the same checks whether you run GKE, EKS, or bare metal.
That’s what our 50-point checklist is for: every item with the check command, the fix, and the CIS/SOC2 mapping, so the evidence pack builds itself while you harden. The GKE-specific items above plus that list is what the last few cloud audits I’ve seen actually asked for.
FAQ
What should I fix first on an existing GKE cluster?
The node service account, then Workload Identity. Together they decide whether a compromised pod stays a Kubernetes problem or becomes a GCP project problem. Everything else on the list limits smaller blast radii.
Does Autopilot remove the need for all of this?
It removes items 1, 4, and most of 6, which is real value. You still own the API endpoint exposure, Data Access logs, workload IAM scoping, RBAC, and network policies themselves. Autopilot narrows your half of the shared responsibility model, it doesn’t eliminate it.
Do VPC firewall rules replace NetworkPolicies?
No. Firewall rules see VM-to-VM traffic, and pods on the same node never cross a VM boundary. Pod-level isolation needs NetworkPolicy with a dataplane that enforces it, which on GKE means Dataplane V2 or the network policy add-on.
How do I check my cluster quickly?
The gcloud commands in this post cover the GCP side and they’re all read-only. For the in-cluster side, k8s-audit gives you the 30-second pass, then kube-bench with the GKE benchmark for depth.
Get the Complete 50-Point Security Checklist
5 production-ready templates + audit checklist highlights, free for K8s engineers.
Get the Free K8s Security Quick-Start Kit
Get 5 essential templates + audit checklist highlights delivered to your inbox.
No spam. Unsubscribe anytime.
Secure Your Kubernetes Clusters
Get the complete 50-point audit checklist and 20+ production-ready YAML templates.
View Pricing Plans