Skip to main content

Ensure Clusters Are Created With Private Nodes

More Info:​

Disable Public Ip Addresses For Cluster Nodes, So That They Only Have Private Ip Addresses. Private Nodes Are Nodes With No Public Ip Addresses

Risk Level​

Medium

Address​

Security

Compliance Standards​

  • APRA CPS 234 (Australia)
  • BSI C5 (Germany)
  • Brazil LGPD
  • CCPA / CPRA (California)
  • CIS Critical Security Controls v8
  • CIS GKE
  • CMMC 2.0
  • CSA Cloud Controls Matrix v4
  • DPDPA
  • Digital Operational Resilience Act (EU)
  • ISO/IEC 27017
  • ISO/IEC 27018
  • ISO/IEC 27701
  • KSA PDPL
  • MAS Technology Risk Management (Singapore)
  • MITRE ATT&CK (Cloud)
  • NIS2 Directive
  • NIST SP 800-171
  • NYDFS 23 NYCRR 500
  • SWIFT Customer Security Controls Framework
  • Sarbanes-Oxley IT General Controls
  • UK NCSC Cyber Assessment Framework

Triage and Remediation​

Remediation​

Manual Steps
  1. Identify clusters and current private node setting

    • Run on: any machine with gcloud configured.
    • List clusters and locations:
      gcloud container clusters list --project PROJECT_ID
    • For each cluster, check whether private nodes are enabled:
      gcloud container clusters describe CLUSTER_NAME \
      --location LOCATION \
      --project PROJECT_ID \
      --format='value(privateClusterConfig.enablePrivateNodes)'
    • Record which clusters return false or empty; these do not have private nodes.
  2. Confirm networking prerequisites and potential impact

    • For each non‑private cluster, review its networking:
      gcloud container clusters describe CLUSTER_NAME \
      --location LOCATION \
      --project PROJECT_ID \
      --format='yaml(networkConfig, privateClusterConfig, ipAllocationPolicy)'
    • Validate that:
      • You can create/use a VPC‑native (IP alias) cluster (ipAllocationPolicy.useIpAliases=true requirement for private nodes).
      • Your workloads and admins do not rely on node public IPs (e.g., SSH, direct internet egress). Plan alternatives such as Cloud NAT, private bastion, or IAP.
  3. Decide remediation strategy: migrate vs. accept risk

    • For each non‑private cluster, decide one of:
      • Migrate to a new private cluster (recommended).
      • Accept and document an exception (include justification, expiration, and periodic review).
    • Capture the decision in your internal risk register or change tracking system.
  4. Plan creation of a new private cluster (if migrating)

    • Choose or create a VPC and subnets for node and pod ranges.
    • Decide on --master-ipv4-cidr (control plane CIDR that does not overlap with existing ranges).
    • Define the full create command (example; adjust values):
      gcloud container clusters create NEW_CLUSTER_NAME \
      --project PROJECT_ID \
      --location LOCATION \
      --enable-private-nodes \
      --enable-ip-alias \
      --master-ipv4-cidr=10.0.0.0/28 \
      --network=VPC_NAME \
      --subnetwork=SUBNET_NAME
    • Have this reviewed and approved via your normal change management process.
  5. Migrate workloads and decommission old cluster (if migrating)

    • Create the new private cluster using the planned command from step 4.
    • Update kubeconfig:
      gcloud container clusters get-credentials NEW_CLUSTER_NAME \
      --location LOCATION \
      --project PROJECT_ID
    • Migrate workloads (e.g., export manifests, helm releases, or use GitOps) and cut over traffic (DNS, load balancers, external dependencies) to the new cluster.
    • When fully migrated and validated, delete the old non‑private cluster:
      gcloud container clusters delete OLD_CLUSTER_NAME \
      --location LOCATION \
      --project PROJECT_ID
  6. Verify compliance for all in-scope clusters

    • Re-run for each cluster:
      gcloud container clusters describe CLUSTER_NAME \
      --location LOCATION \
      --project PROJECT_ID \
      --format='value(privateClusterConfig.enablePrivateNodes)'
    • Confirm the value is true for all clusters where you intended private nodes; ensure any remaining false entries have an approved, documented exception.
Using kubectl

kubectl cannot modify whether GKE nodes use public or private IPs; this setting is defined on the managed control plane via GCP (gcloud/console/Terraform). To address this finding, make the change at the cloud provider level as described in the Manual Steps section.

Automation
#!/usr/bin/env bash
#
# Report GKE clusters that do NOT have private nodes enabled.
# Requirements:
# - gcloud and jq installed
# - gcloud authenticated, with access to the target projects
#
# Usage examples:
# ./check_gke_private_nodes.sh my-project
# ./check_gke_private_nodes.sh proj-1 proj-2
# GCP_PROJECTS="proj-1 proj-2" ./check_gke_private_nodes.sh
# ./check_gke_private_nodes.sh # uses gcloud config get-value core/project

set -euo pipefail

# Get project list from args or env or gcloud default
if [[ "$#" -gt 0 ]]; then
PROJECTS=("$@")
elif [[ -n "${GCP_PROJECTS:-}" ]]; then
# shellcheck disable=SC2206
PROJECTS=(${GCP_PROJECTS})
else
DEFAULT_PROJECT="$(gcloud config get-value core/project 2>/dev/null || true)"
if [[ -z "${DEFAULT_PROJECT}" || "${DEFAULT_PROJECT}" == "(unset)" ]]; then
echo "ERROR: No projects specified and no default project set." >&2
echo "Usage: $0 <project-1> [project-2 ...]" >&2
exit 1
fi
PROJECTS=("${DEFAULT_PROJECT}")
fi

echo "Checking GKE clusters for private nodes..."
echo

for PROJECT in "${PROJECTS[@]}"; do
echo "Project: ${PROJECT}"

# List all clusters in all locations for this project
# Output: name, location, private-nodes-enabled
if ! CLUSTERS_JSON="$(gcloud container clusters list \
--project "${PROJECT}" \
--format=json 2>/dev/null)"; then
echo " ERROR: Unable to list clusters (insufficient permissions or invalid project)." >&2
echo
continue
fi

if [[ "${CLUSTERS_JSON}" == "[]" ]]; then
echo " No clusters found."
echo
continue
fi

echo " name,location,private_nodes_enabled"
echo "${CLUSTERS_JSON}" | jq -r '
.[] |
# For each cluster, look up full description to get privateClusterConfig
.name as $name |
.location as $loc |
@sh "gcloud container clusters describe \($name) --location \($loc) --project '"${PROJECT}"' --format=json"
' | while read -r DESCRIBE_CMD; do
# Run each generated gcloud describe command
# shellcheck disable=SC2086
if ! CLUSTER_DESC_JSON=$(eval ${DESCRIBE_CMD} 2>/dev/null); then
echo " ERROR: Failed to describe cluster with: ${DESCRIBE_CMD}" >&2
continue
fi

NAME=$(echo "${CLUSTER_DESC_JSON}" | jq -r '.name')
LOC=$(echo "${CLUSTER_DESC_JSON}" | jq -r '.location // .zone // ""')
PRIVATE_NODES=$(echo "${CLUSTER_DESC_JSON}" | jq -r '.privateClusterConfig.enablePrivateNodes // false')

echo " ${NAME},${LOC},${PRIVATE_NODES}"
done

echo
done

cat <<'EOF'
Interpretation:

- A cluster is COMPLIANT with CIS GKE 5.6.5 when:
private_nodes_enabled == true

- A cluster is NON-COMPLIANT (problem) when:
private_nodes_enabled == false

Any line in the output where the last column is "false" indicates a cluster
that was created without private nodes enabled and should be reviewed. There is
no deterministic automated fix; you typically need to recreate the cluster with
--enable-private-nodes (and required flags) after planning migration.
EOF

Additional Reading:​