A Google Kubernetes Engine cluster built with default settings still exposes more than most teams expect: public node IPs, an IP-based control plane endpoint, an open kubelet read-only port and no restriction on which images can run. Google manages the control plane, but the node pools, network exposure, admission policy, workload isolation and logging choices are yours, and they are where most GKE incidents start. This guide covers the non-identity layers of GKE hardening in depth: Autopilot versus Standard, private nodes and the DNS-based control plane endpoint, Binary Authorization, GKE Sandbox, Pod Security admission, the CIS GKE Benchmark and the managed organisation policy constraints that enforce the baseline at scale. It is written for platform engineers, cloud security architects and reviewers who run or assess GKE fleets. Identity (RBAC, IAM and Workload Identity Federation) is summarised here and covered in depth in the GKE identity guide.
Key takeaways
- Autopilot removes whole classes of risk by default: privileged containers, hostNetwork, writable hostPath, node SSH and unsafe sysctls are blocked, and upgrades are always on. Standard mode needs each of these set and enforced by you.
- Close the network paths first: private nodes, the DNS-based control plane endpoint with IP-based endpoints disabled, and Kubernetes NetworkPolicy shrink the attack surface more than any other single change.
- Binary Authorization decides what may run: start in "Dry Run: Audit Log Only", fix the violations, then move to "Block and Audit Log" with attestation rules per cluster or namespace.
- Use the managed constraints: Google publishes
constraints/container.managed.*organisation policy constraints that turn most hardening recommendations into preventive guardrails across every project. - The CIS GKE Benchmark, not the CIS Kubernetes Benchmark, is the yardstick: the control plane cannot be assessed by customers, so scope audits to worker nodes, node pools and workloads.
How identity and access work in GKE
Every hardening layer in this guide assumes the identity layer is already sound, so a short recap. Two authorisation systems apply to every request to the Kubernetes API: Kubernetes RBAC and Google Cloud IAM, and either can grant access. Humans authenticate with Google identities; workloads should reach Google Cloud APIs through Workload Identity Federation for GKE, which is always on in Autopilot and must be enabled on each Standard cluster and node pool. Google recommends a separate namespace and Kubernetes ServiceAccount per workload, and granting access to Google Groups rather than individuals.
The non-identity point that matters here: access control is only as strong as the network path to the API server. Default discovery roles grant broad read access to system:authenticated, which in GKE means any authenticated Google identity, so restricting who can reach the endpoint is part of access control. From GKE 1.32.2-gke.1234000 the flag --anonymous-authentication-config=LIMITED restricts anonymous requests to /healthz, /livez and /readyz and returns HTTP 401 for everything else; Google's hardening page states this becomes the default only for clusters newly created on 1.35.0-gke.1171000 and later, so existing clusters need it set explicitly. See the GKE Identity Hardening Guide for RBAC, IAM roles and Workload Identity Federation grant models.
Admin roles and privileged access
Privileged access in GKE is not only a matter of who holds roles/container.admin. Several cluster settings decide how much damage any privileged path can do.
- Legacy authentication off: static passwords are disabled and cannot be used in Autopilot. Block legacy client certificate issuance with
constraints/container.managed.disableLegacyClientCertificateIssuance, because an issued client certificate is a long-lived credential that IAM cannot revoke. - ABAC off: legacy attribute-based access control is disabled by default and cannot be enabled in Autopilot. Enforce it in Standard with
constraints/container.managed.disableABAC. - Node access: Autopilot blocks SSH to nodes (kubectl exec into containers still works). In Standard, treat node SSH and the
compute.instances.setMetadatapermission on node VMs as privileged, since they bypass Kubernetes admission entirely. - Policy administrators are admins: anyone who can edit the Binary Authorization policy, add patterns to
admissionWhitelistPatterns, or change Policy Controller constraints can disable the guardrails in this guide. Keep those permissions with the platform security team, not with application owners. - Break-glass: Binary Authorization supports a documented breakglass path for emergency deployments. Alert on every use and review it after the event; see the Break-Glass Emergency Access Guide for the wider pattern.
Integrations, API tokens and service accounts
The node service account is the most common hidden over-privilege in GKE. By default Standard node pools can use the Compute Engine default service account, which in many projects holds the broad Editor role. Google's recommendation is a dedicated node service account holding only roles/container.defaultNodeServiceAccount, enforced with constraints/container.managed.disallowDefaultComputeServiceAccount.
| Credential or path | Risk if left at default | Hardened setting |
|---|---|---|
| Node service account | Any pod that reaches node metadata inherits the node's Google Cloud permissions | Custom account with roles/container.defaultNodeServiceAccount; Workload Identity Federation so pods cannot read node credentials |
| Kubernetes Secrets | Stored in the cluster datastore; readable by anyone with get on secrets | Prefer Secret Manager client libraries or the Secret Manager add-on; if Secrets are needed, application-layer secrets encryption with a key you manage |
| Kubelet read-only port 10255 | Unauthenticated read of pod and node information | Disable it and move monitoring to port 10250; enforce with constraints/container.managed.disableInsecureKubeletReadOnlyPort |
Service spec.externalIPs | Traffic interception (GCP-2020-015) | DenyServiceExternalIPs, on by default for clusters created on 1.21 and later; older clusters need --no-enable-service-externalips |
| CI/CD deploy identity | A pipeline key that can deploy any image to any namespace | Keyless federation from the CI system, namespace-scoped RBAC, and Binary Authorization attestations created by the build |
GKE Sandbox adds a useful property here: with gVisor, pods cannot reach node-level cluster metadata at all, and reach Google Cloud services only through Workload Identity Federation. For service account token handling inside the cluster, see the Kubernetes Service Account Token Guide and the Kubernetes Secrets Security Guide.
Logging and detection
GKE writes two families of audit records to Cloud Logging. Kubernetes API audit logs use the service name k8s.io and resource type k8s_cluster; Google Cloud audit logs record changes to the cluster resource itself. Admin Activity audit logs are always enabled and cannot be disabled. Data Access audit logs (admin read, data read, data write) are off by default and must be enabled explicitly, which matters if you want to see who read Secrets. Do not disable log collection on Standard clusters; Google provides constraints/container.managed.enableCloudLogging to enforce it. Note that kubectl --dry-run=client never reaches the API server and so is never logged.
| What to detect | Log source and filter idea | ATT&CK v20 |
|---|---|---|
| kubectl exec into a running pod | Admin Activity, protoPayload.methodName matching io.k8s.core.v1.pods.exec | T1609 Container Administration Command |
| Anonymous API requests | Admin Activity, principalEmail equal to system:anonymous | T1552.007 Container API |
| Unexpected workload or privileged pod created | Admin Activity, deployments.create, pods.create with privileged or host namespace fields | T1610 Deploy Container |
| Image blocked or allowed in dry run | Binary Authorization entries in Cloud Audit Logs | T1525 Implant Internal Image |
| Container escape indicators | Security Command Center threat findings, node-level runtime telemetry | T1611 Escape to Host |
| Bulk discovery of cluster resources | Data Access logs (list and watch across namespaces) | T1613 Container and Resource Discovery |
| Crypto-mining on nodes | Node CPU anomalies, SCC findings | T1496 Resource Hijacking |
The security posture dashboard (Container Security API) gives configuration scanning of workloads and security bulletin surfacing at no extra charge; Kubernetes security posture standard tier is on by default for new clusters on GKE 1.27 and later. Workload vulnerability scanning is off by default, and Google lists Container OS scanning and Advanced Vulnerability Insights as deprecated, so check the current status before relying on it and plan image scanning in Artifact Analysis or your registry instead. With network policy logging enabled you can confirm that NetworkPolicies allow and deny what you expect. Route audit logs to a central project, BigQuery or a SIEM with retention that meets your obligations; see the Cloud Logging and Audit Trail Guide.
Metrics that show the programme is working:
- Percentage of clusters on a release channel with accelerated patch auto-upgrades enabled.
- Percentage of clusters with private nodes and no public IP-based control plane endpoint.
- Percentage of node pools using a custom node service account (target 100%).
- Binary Authorization: share of clusters in enforce mode, and dry-run violations per week trending to zero.
- Open posture dashboard concerns rated Critical or High, and mean days to close.
- Breakglass deployments per quarter, each with a closed review.
Hardening baseline
Autopilot or Standard
The first hardening decision is the cluster mode. Autopilot enforces much of the baseline itself: Container-Optimized OS with containerd, a release channel with auto-upgrades always on for control plane and nodes, Workload Identity Federation, seccomp RuntimeDefault, AppArmor docker-default, CAP_NET_RAW dropped, and no deployments into GKE-managed namespaces such as kube-system. Containers can still run as root, and NET_ADMIN can be opted into with --workload-policies=allow-net-admin, which Google warns grants broad network access.
| Control | Autopilot | Standard |
|---|---|---|
| Privileged containers, host namespaces, hostNetwork | Blocked (verified partner workloads allowlisted) | Allowed unless blocked by Pod Security admission or Policy Controller |
| hostPath volumes | Write blocked; read-only /var/log prefixes allowed | Allowed by default |
| Node SSH | Blocked | Possible; restrict through IAM and firewall |
| Upgrades | Always on, release channel at creation | Enrol in a release channel; node auto-upgrade on |
| Node image | Container-Optimized OS only | COS default; Ubuntu and Windows available |
| Shielded GKE Nodes | Always on | On by default; do not disable; enable secure boot per node pool |
| Workload Identity Federation | Always on | Enable per cluster and node pool |
| GKE Sandbox (gVisor) | Enabled; request with RuntimeClass gvisor | Dedicated node pool; the default pool cannot be sandboxed |
Decision guidance: choose Autopilot for most new application clusters, especially in smaller teams without a dedicated platform function, because the secure defaults cannot drift. Choose Standard when you need Windows nodes, custom kernels or node-level agents that Autopilot does not allow, and accept that you then own every row in the right-hand column. Large estates often run both and use the managed constraints to make Standard clusters behave close to Autopilot.
Network baseline
- Control plane endpoint: use the DNS-based endpoint only and disable IP-based endpoints (
constraints/container.managed.enableControlPlaneDNSOnlyAccess). Where an IP endpoint must stay, remove its external IP or restrict it with authorized networks, remembering that authorized networks also admit nodes in the same VPC and Google management ranges. - Private nodes: nodes get external IPs by default; enable private nodes (
constraints/container.managed.enablePrivateNodes) and give egress through Cloud NAT or a proxy. - Pod-to-pod traffic: enable network policy (
constraints/container.managed.enableNetworkPolicy), start each namespace with a default-deny policy and allow flows explicitly. Cloud Service Mesh adds mutual TLS where you need encryption between services. - Firewall rules: least privilege, without conflicting with GKE's own default rules. For perimeter controls around Google APIs see the VPC Service Controls Guide and the Google Cloud VPC and NGFW Security Guide.
Node and workload baseline
- Node OS: Container-Optimized OS unless there is a specific need. Keep integrity monitoring on and enable secure boot on Standard node pools; Google notes that on COS secure boot does not change whether unsigned kernel modules can load.
- Pod Security admission: enforce the Restricted Pod Security Standard on application namespaces and Baseline at minimum elsewhere, with
warnandauditlabels first. For fleets, Policy Controller applies the same constraints to many clusters. See the Kubernetes Admission Control Guide. - Untrusted code: run it in GKE Sandbox. gVisor intercepts most system calls in a userspace kernel, which limits the blast radius of a container escape. It does not support privileged containers, hostPath, raw block volumes, port forwarding or sysctl changes, and it is generally not needed for trusted first-party workloads.
- Data in use: Confidential GKE Nodes encrypt memory for sensitive workloads.
- Patching signal: enable security bulletin notifications (
constraints/container.managed.enableSecurityBulletinNotifications) and allow node auto-upgrades at least monthly if you use maintenance windows.
Binary Authorization
Binary Authorization evaluates a policy at deploy time. The evaluationMode of a rule is "Allow all images", "Disallow all images" or REQUIRE_ATTESTATION; the enforcementMode is "Block and Audit Log" or "Dry Run: Audit Log Only". A defaultAdmissionRule applies everywhere, and clusterAdmissionRule entries let you set stricter rules per cluster, Kubernetes namespace or Kubernetes service account. Attestations are signed records of an image digest created by an attestor, typically your build pipeline after tests and vulnerability checks. Exempt images go in admissionWhitelistPatterns; Google advises against using that to allowlist its own system images and offers the "Trust all Google-maintained system images" setting (globalPolicyEvaluationMode: ENABLE) instead. Continuous validation (Preview at the time of writing) re-checks running pods against check-based platform policies such as image freshness, Sigstore signatures, SLSA and trusted directories. For signing, see the Sigstore Guide and the Container Image Security Guide.
Phased rollout
- Inventory and benchmark: list every cluster with mode, version, release channel, endpoint type, node service account and network policy status. Run kube-bench against worker nodes using the CIS GKE Benchmark and check which benchmark version the tool evaluates.
- Quick wins with low breakage risk: release channels, security bulletin notifications, Shielded Nodes, integrity monitoring, logging on, ABAC and legacy client certificates off.
- Audit-only guardrails: Pod Security admission in
warn/audit, Binary Authorization in dry run, network policy logging on. Collect two to four weeks of violations. - Pilot ring: apply private nodes, DNS-only control plane access, custom node service accounts, kubelet read-only port off and default-deny NetworkPolicies on one non-production cluster, then a low-risk production cluster.
- Enforce: switch Binary Authorization to "Block and Audit Log" per cluster, Pod Security admission to
enforce, and apply the managed organisation policy constraints at folder level. - Roll back safely: every step above is reversible per cluster or namespace: return a rule to dry run, relabel a namespace to
warn, or exempt a project from a constraint. Record each exception with an owner and an expiry date.
Framework mapping
| GKE control | NIST SP 800-53 Rev. 5 | NIST CSF 2.0 | CIS Controls v8.1 | ISO/IEC 27001:2022 |
|---|---|---|---|---|
| Managed constraints, CIS GKE Benchmark | CM-2, CM-6 | PR.PS-01 | 4 | A.8.9 |
| Release channels and auto-upgrade | SI-2 | PR.PS-02, ID.RA-01 | 7 | A.8.8 |
| Private nodes, DNS endpoint, NetworkPolicy | SC-7 | PR.IR-01 | 12, 13 | A.8.20, A.8.22 |
| Binary Authorization, Shielded Nodes | SI-7, CM-7 | PR.PS-01 | 2 | A.8.19 |
| GKE Sandbox, Pod Security admission | SC-39, AC-6 | PR.PS-01 | 4 | A.8.9 |
| Application-layer secrets encryption | SC-12, SC-28 | PR.DS-01 | 3 | A.8.24 |
| Audit logs, posture dashboard | AU-2, AU-12, CA-7 | DE.CM-09 | 8 | A.8.15, A.8.16 |
Common misconfigurations
- Auditing against the wrong benchmark: scoring GKE with the CIS Kubernetes Benchmark produces failures on controls nobody can change, such as API server audit flags and admission controllers like AlwaysPullImages that GKE disables, and the noise hides the node and workload findings that matter.
- Public nodes and a public endpoint left from a proof of concept: the cluster is promoted to production without enabling private nodes or restricting the control plane, and the exposed API is then protected only by authentication, at a time when discovery roles are readable by any authenticated identity.
- Compute Engine default service account on node pools: one compromised pod reads the node's token from metadata and inherits project-wide Editor. Exposed cloud credentials are what attackers look for first, as the AWS crypto-mining campaign shows on another cloud: miners were running within minutes of compromise.
- Binary Authorization left in dry run, or allowlisted into irrelevance: a broad pattern such as
gcr.io/example-project/**inadmissionWhitelistPatternslets any image pushed to that path deploy unattested. - NetworkPolicy enabled but no default-deny: enabling enforcement does nothing until a policy selects the pods, so every namespace stays flat.
- Relying on deprecated scanning: teams assume the posture dashboard scans images while the scanning tier they enabled is deprecated or off, leaving no vulnerability signal for running workloads.
Practitioner checklist
- Every cluster is on a release channel; Standard node pools auto-upgrade and accelerated patch auto-upgrades are on.
- Private nodes are enabled and the control plane is reached through the DNS-based endpoint, with IP-based endpoints disabled or restricted.
- No node pool uses the Compute Engine default service account; Workload Identity Federation is on everywhere.
- The kubelet read-only port 10255 is disabled, ABAC and legacy client certificates are off, and anonymous authentication is set to LIMITED.
- Pod Security admission enforces Restricted on application namespaces; untrusted code runs in GKE Sandbox.
- Each namespace has a default-deny NetworkPolicy and network policy logging is used to verify it.
- Binary Authorization runs in "Block and Audit Log" with attestation rules for production clusters, and breakglass use raises an alert.
- Admin Activity logs are centralised, Data Access logs are enabled where Secret reads must be visible, and detections cover exec, anonymous access and privileged pods.
- The managed
container.managed.*constraints are applied at organisation or folder level, with documented, expiring exceptions. - Worker nodes are assessed with kube-bench against the CIS GKE Benchmark at least quarterly.
Standards and references
- Google Cloud: Harden your cluster's security
- Google Cloud: Autopilot security measures
- Google Cloud: Binary Authorization key concepts
- Google Cloud: CIS Benchmarks for GKE
- Google Cloud: GKE Sandbox
- Google Cloud: GKE audit logging
- Google Cloud: About the security posture dashboard
- MITRE ATT&CK: Escape to Host
Related NHI Mgmt Group resources: GKE Identity Hardening Guide · Kubernetes Security Guide · Amazon EKS Security Hardening Guide · Top 10 Kubernetes Security Risks