> ## Documentation Index
> Fetch the complete documentation index at: https://cloudanix.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# 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

<Tabs>
  <Tab title="Remediation">
    ### Remediation

    <AccordionGroup>
      <Accordion title="Manual Steps" defaultOpen="true">
        1. **Identify clusters and current private node setting**
           * Run on: any machine with `gcloud` configured.
           * List clusters and locations:
             ```bash theme={null}
             gcloud container clusters list --project PROJECT_ID
             ```
           * For each cluster, check whether private nodes are enabled:
             ```bash theme={null}
             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:
             ```bash theme={null}
             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):
             ```bash theme={null}
             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:
             ```bash theme={null}
             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:
             ```bash theme={null}
             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:
             ```bash theme={null}
             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.
      </Accordion>

      <Accordion title="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.
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/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
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>

### Additional Reading:

* [https://cloud.google.com/kubernetes-engine/docs/how-to/private-clusters](https://cloud.google.com/kubernetes-engine/docs/how-to/private-clusters)
