This check flags GKE clusters whose Kubernetes control plane (API server) is reachable over the public internet. An exposed API endpoint is a prime target for credential stuffing and exploit attempts, so lock it down by enabling a private endpoint and restricting authorized networks with a single cluster update.
The control plane is the brain of a GKE cluster. It runs the Kubernetes API server, the scheduler, and the controller manager, and every kubectl command, every CI/CD deploy, and every operator you run talks to it. When that endpoint has a public IP, anyone on the internet can reach the front door of your cluster. They still need credentials to get in, but giving the world network access to your API server is a risk you almost never need to take.
This Lensix check, gke_publiccontrolplane in the gke_checks module, fires when a GKE cluster's control plane endpoint is publicly accessible.
What this check detects
GKE clusters can expose their control plane in a few ways. The check looks at how the API server endpoint is configured and flags clusters where:
- The cluster was created without a private endpoint, so the API server has a public IP that resolves on the open internet.
- A private cluster has
enablePrivateEndpointset to false, meaning the public endpoint is still active alongside the private one. - Master authorized networks are either disabled or configured with an overly broad CIDR like
0.0.0.0/0, which effectively whitelists the entire internet.
Note: GKE has two separate dials here. Private endpoint controls whether the control plane has a public IP at all. Master authorized networks controls which source IP ranges are allowed to reach the endpoint. A cluster can be "public" but still reasonably safe if authorized networks are tightly scoped, but the safest posture is a private endpoint with authorized networks layered on top.
Why it matters
A public control plane is not an instant breach, but it widens your attack surface in ways that compound quickly.
Credential and token exposure
The API server accepts authentication tokens, client certificates, and OIDC logins. If a service account key, a leaked kubeconfig, or a CI token ends up in a public repo or a compromised laptop, an attacker with that credential and a public endpoint can reach your cluster from anywhere. With a private endpoint, the same leaked credential is useless without network access to your VPC.
Exploit and CVE exposure
The Kubernetes API server has had its share of CVEs over the years. When a new authentication bypass or request-handling bug drops, a publicly reachable endpoint means you are exposed the moment the exploit is public, before you have a chance to patch. Network isolation buys you time.
Reconnaissance and noise
Public endpoints get scanned constantly. Bots probe for anonymous access, misconfigured RBAC, and known vulnerable versions. Even if none of those probes succeed, your audit logs fill with noise that makes real incident detection harder.
Warning: A common misconfiguration is enabling master authorized networks but then adding 0.0.0.0/0 to "make CI work." That setting defeats the entire purpose. If you see this, treat it as a public endpoint with extra steps.
How to fix it
The right fix depends on whether you are creating a new cluster or hardening an existing one.
1. Check the current state
gcloud container clusters describe CLUSTER_NAME \
--location LOCATION \
--format="yaml(privateClusterConfig, masterAuthorizedNetworksConfig)"
Look at privateClusterConfig.enablePrivateEndpoint and the list under masterAuthorizedNetworksConfig.cidrBlocks. If the private endpoint is false or authorized networks are missing or wide open, this check will fire.
2. Restrict authorized networks (quickest mitigation)
If you cannot move to a private endpoint immediately, the fastest improvement is to scope down which IPs can reach the API server. Replace the example ranges with your office VPN, NAT gateway, or CI runner egress IPs.
gcloud container clusters update CLUSTER_NAME \
--location LOCATION \
--enable-master-authorized-networks \
--master-authorized-networks 203.0.113.10/32,198.51.100.0/24
Tip: Routing your CI/CD egress through a Cloud NAT with a static IP gives you a single stable CIDR to whitelist, instead of chasing the rotating IP ranges of hosted runners.
3. Enable the private endpoint (recommended)
For clusters that are already private clusters but still expose the public endpoint, you can disable public access to the control plane:
Warning: Disabling the public endpoint means anything that talks to the API server must now have a network path into the cluster's VPC, such as a bastion, VPN, Cloud Interconnect, or peered network. Make sure your CI/CD and operator access can still reach the control plane before you flip this, or your deploys will start failing.
gcloud container clusters update CLUSTER_NAME \
--location LOCATION \
--enable-private-endpoint \
--enable-master-authorized-networks \
--master-authorized-networks 10.0.0.0/8
With --enable-private-endpoint, kubectl traffic must come from inside the authorized network ranges, which now refer to internal RFC 1918 ranges that can reach the private control plane IP.
4. New clusters: do it from the start
Creating a fully private cluster is far easier than retrofitting one:
gcloud container clusters create CLUSTER_NAME \
--location LOCATION \
--enable-private-nodes \
--enable-private-endpoint \
--enable-master-authorized-networks \
--master-authorized-networks 10.0.0.0/8 \
--master-ipv4-cidr 172.16.0.0/28
5. Terraform example
If you manage GKE with Terraform, set the private cluster block explicitly so the public endpoint is never created:
resource "google_container_cluster" "primary" {
name = "prod-cluster"
location = "us-central1"
private_cluster_config {
enable_private_nodes = true
enable_private_endpoint = true
master_ipv4_cidr_block = "172.16.0.0/28"
}
master_authorized_networks_config {
cidr_blocks {
cidr_block = "10.0.0.0/8"
display_name = "internal-vpc"
}
}
}
Danger: Switching enable_private_endpoint on an existing Terraform-managed cluster can, depending on provider version and the rest of your config, trigger a cluster replacement. Run terraform plan and confirm the change is an in-place update before applying. A forced replacement of a production cluster means downtime and lost in-cluster state.
How to prevent it from happening again
Fixing one cluster is easy. Stopping the next public cluster from being created is what actually moves your risk down over time.
Policy-as-code in CI
Add an OPA or Conftest policy that fails any plan creating a GKE cluster without a private endpoint. A simple Rego rule against a Terraform plan JSON:
package gke
deny[msg] {
resource := input.resource_changes[_]
resource.type == "google_container_cluster"
config := resource.change.after.private_cluster_config[_]
config.enable_private_endpoint == false
msg := sprintf("Cluster '%s' must use a private control plane endpoint", [resource.change.after.name])
}
Org Policy guardrails
GCP's Organization Policy Service can enforce private clusters across an entire org or folder, so no project can opt out:
gcloud resource-manager org-policies enable-enforce \
constraints/container.restrictPrivateGKEClusterCreation \
--organization ORGANIZATION_ID
Tip: Layer your defenses. Org Policy stops the misconfiguration at the API level, CI policy gives developers fast feedback before they ever apply, and Lensix continuously scans for drift in case something slips through a manual change. No single control catches everything.
Continuous scanning
Keep the gke_publiccontrolplane check running on a schedule so that any cluster created outside your pipeline, or modified by hand during an incident, gets flagged before it lingers.
Best practices
- Default to private. Make a private endpoint plus private nodes your standard cluster template. Public should be a deliberate, documented exception.
- Never use
0.0.0.0/0in authorized networks. If you find yourself reaching for it, the real fix is a stable egress IP or a bastion, not opening the cluster to the world. - Use a bastion or proxy for human access. An IAP-tunneled jump host lets engineers reach the private endpoint without a VPN and without a public IP on the control plane.
- Keep nodes private too. A private control plane with public nodes is half a solution. Pair
--enable-private-nodeswith the private endpoint. - Enable Control Plane audit logging. Ship API server logs to Cloud Logging so you can see who is talking to the control plane and from where.
- Review authorized network lists quarterly. Stale CIDRs accumulate. An old office IP that no longer belongs to you is a quiet liability.
A public GKE control plane is one of those settings that feels convenient on day one and becomes a liability the moment a credential leaks or a CVE lands. Moving to a private endpoint with tightly scoped authorized networks costs you a little setup effort and removes an entire category of attack from the table.

