Safeguard
Container Security

Hardening Google Kubernetes Engine clusters against attacks

A step-by-step guide to GKE security best practices: private clusters, Workload Identity, Shielded Nodes, Binary Authorization, and verification checks for real audits.

Karan Patel
Cloud Security Engineer
7 min read

Kubernetes gives attackers a lot of surface area to work with: an exposed API server, an over-permissioned service account, or a node pool running an outdated image can turn a single compromised pod into full cluster takeover. On Google Kubernetes Engine, Google manages the control plane, but everything around it — network exposure, IAM bindings, workload identity, admission policy, and node configuration — is still your responsibility. That shared-responsibility gap is exactly where most GKE breaches start.

This guide walks through GKE security best practices you can apply today, in the order that matters most: locking down access to the API server, enforcing identity boundaries between workloads and Google Cloud resources, hardening nodes and container runtimes, and controlling what workloads are allowed to run at all. By the end, you'll have a cluster configuration you can verify against a checklist, plus a troubleshooting section for the misconfigurations that show up most often in real audits.

1. Choose the Right Cluster Mode and Lock Down Network Access

Before touching workloads, decide how much of the underlying infrastructure you want Google to manage. GKE Autopilot security is a strong default for most teams: Google enforces node hardening, restricts privileged containers, and manages upgrades automatically, which eliminates an entire class of misconfiguration that Standard clusters leave to you. If you need node-level customization (custom daemonsets, GPU drivers, specific kernel modules), Standard mode is still viable, but you inherit the hardening work manually.

Regardless of mode, make the cluster private and restrict who can reach the control plane:

gcloud container clusters create prod-cluster \
  --enable-autopilot \
  --enable-private-nodes \
  --enable-private-endpoint \
  --master-authorized-networks=203.0.113.0/24 \
  --enable-master-global-access

Private nodes remove public IPs from your workloads entirely, and master-authorized-networks ensures only traffic from your VPN or bastion CIDR range can reach the Kubernetes API. If you already run a Standard cluster, retrofit these settings with gcloud container clusters update rather than rebuilding from scratch.

2. Apply GKE Security Best Practices to Identity: Workload Identity Over Static Keys

The single most common GKE misconfiguration we see in audits is workloads authenticating to Google Cloud APIs with a downloaded service account JSON key mounted as a Kubernetes secret. That key doesn't expire, isn't scoped to a namespace, and survives pod deletion. GKE workload identity replaces it with short-lived, automatically rotated credentials tied to a Kubernetes service account.

gcloud container clusters update prod-cluster \
  --workload-pool=PROJECT_ID.svc.id.goog

gcloud iam service-accounts add-iam-policy-binding \
  gsa-storage-reader@PROJECT_ID.iam.gserviceaccount.com \
  --role roles/iam.workloadIdentityUser \
  --member "serviceAccount:PROJECT_ID.svc.id.goog[NAMESPACE/KSA_NAME]"

Then annotate the Kubernetes service account so pods pick up the binding automatically:

kubectl annotate serviceaccount KSA_NAME \
  --namespace NAMESPACE \
  iam.gke.io/gcp-service-account=gsa-storage-reader@PROJECT_ID.iam.gserviceaccount.com

Audit every namespace for leftover key-based secrets (kubectl get secrets --all-namespaces | grep -i key) and migrate them before disabling legacy metadata server access with --metadata disable-legacy-endpoints=true.

3. Harden Nodes and the Container Runtime

If you're on Standard mode, node hardening is on you. Enable Shielded GKE Nodes to get secure boot, vTPM, and integrity monitoring, and keep nodes on the auto-upgrading Container-Optimized OS image rather than Ubuntu unless you have a specific driver requirement:

gcloud container node-pools create secure-pool \
  --cluster=prod-cluster \
  --shielded-secure-boot \
  --shielded-integrity-monitoring \
  --enable-autoupgrade \
  --enable-autorepair \
  --image-type=COS_CONTAINERD

Constrain what containers can do at runtime by setting a restrictive SecurityContext as a baseline for every workload:

securityContext:
  runAsNonRoot: true
  allowPrivilegeEscalation: false
  readOnlyRootFilesystem: true
  capabilities:
    drop: ["ALL"]
  seccompProfile:
    type: RuntimeDefault

Autopilot enforces most of this for you by rejecting privileged pods and hostPath mounts outright, which is one of the strongest arguments for GKE Autopilot security in production namespaces that don't need low-level host access.

4. Control What's Allowed to Run

Network and identity hardening won't stop a malicious or vulnerable image from being deployed in the first place. Turn on Binary Authorization so only images signed by your CI/CD pipeline or a trusted attestor can be scheduled:

gcloud container clusters update prod-cluster \
  --binauthz-evaluation-mode=PROJECT_SINGLETON_POLICY_ENFORCE

Pair this with an admission policy (Policy Controller, built on OPA Gatekeeper) that blocks common anti-patterns — :latest tags, missing resource limits, host network access — before they reach the scheduler. This is where a software-supply-chain approach pays off: signing and attestation stop compromised images from a poisoned registry or a hijacked build pipeline from ever running, which is a gap that network and IAM controls alone don't close.

5. Encrypt Secrets and Remove Default Service Accounts

Disable the Compute Engine default service account for new node pools — it carries broad Editor-equivalent permissions that no workload actually needs:

gcloud container node-pools create secure-pool \
  --cluster=prod-cluster \
  --service-account=gke-node-sa@PROJECT_ID.iam.gserviceaccount.com

Encrypt Kubernetes secrets at the application layer with a Cloud KMS key (application-layer secrets encryption), and prefer Secret Manager with the Secret Manager CSI driver over native Kubernetes secrets for anything sensitive — native secrets are only base64-encoded, not encrypted, once they leave etcd.

6. Turn On Continuous Detection

Enable GKE Security Posture dashboard and Container Threat Detection so drift and runtime anomalies surface automatically instead of during your next audit:

gcloud container clusters update prod-cluster \
  --security-posture=standard \
  --workload-vulnerability-scanning=standard

Route Cloud Audit Logs (especially admin activity and data access logs for the GKE API) into your SIEM, and alert on RoleBinding or ClusterRoleBinding changes granting cluster-admin — that's one of the highest-signal indicators of privilege escalation in a Kubernetes environment.

Verification and Troubleshooting

Once these controls are in place, verify them rather than assuming they stuck:

  • Confirm private cluster status: gcloud container clusters describe prod-cluster --format="value(privateClusterConfig)" should show enablePrivateNodes: true.
  • Check for stale workload key secrets: kubectl get secrets -A -o json | jq '.items[] | select(.data."key.json" != null)' — anything returned here is a candidate for Workload Identity migration.
  • Validate Binary Authorization is enforcing, not just logging: deploy an unsigned test image and confirm the pod is rejected with an ImagePullBackOff/admission-denied event, not just a warning in logs.
  • "Workload Identity binding not found" errors: this almost always means the IAM policy binding's [NAMESPACE/KSA_NAME] member string doesn't exactly match the pod's actual namespace and service account name — a mismatch here is the most common cause of authentication failures during migration.
  • Nodes not picking up Shielded VM settings: Shielded options apply at node pool creation only; updating an existing pool requires creating a new pool and migrating workloads with kubectl drain, not an in-place update.
  • Autopilot pods stuck in Pending: check for resource requests that don't match Autopilot's supported compute classes, or a SecurityContext requesting privileges (like hostNetwork or privileged: true) that Autopilot rejects by policy — this is usually the fix, not a quota issue.

How Safeguard Helps

Hardening the cluster itself is necessary but not sufficient — most real-world GKE compromises originate upstream, in the images, dependencies, and CI/CD pipelines that feed the cluster, not in a Kubernetes manifest. Safeguard extends the GKE security best practices above across the full software supply chain: it verifies image provenance and signatures before they're allowed into your Binary Authorization policy, continuously scans dependencies and containers for known and emerging vulnerabilities, and flags anomalous changes to build pipelines or IAM bindings that feed workload identity configurations. Instead of treating cluster hardening and supply chain security as separate efforts, Safeguard gives you one place to see whether an image running in production can be trusted all the way back to its source commit — closing the gap that firewall rules and admission controllers alone can't cover.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.