> ## 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:

Create clusters with private nodes so nodes have only internal IP addresses and are not directly reachable from the internet. This reduces the node attack surface.

### Risk Level

High

### Address

Security

### Compliance Standards

* CIS GKE

### Triage and Remediation

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

    <AccordionGroup>
      <Accordion title="Manual Steps" defaultOpen="true">
        1. **Identify clusters without private nodes**
           * Run on: any machine with `gcloud` access.
           ```bash theme={null}
           PROJECT_ID="<PROJECT_ID>"
           for LOCATION in $(gcloud container clusters list --project "$PROJECT_ID" --format="value(location)" | sort -u); do
             echo "== $LOCATION =="
             gcloud container clusters list --project "$PROJECT_ID" --region "$LOCATION" --format="value(name)" \
               | xargs -I{} sh -c '
                 echo "Cluster: {}"
                 gcloud container clusters describe {} \
                   --location '"$LOCATION"' --project '"$PROJECT_ID"' \
                   --format="value(name,privateClusterConfig.enablePrivateNodes)"
               '
           done
           ```
           * Note clusters where `privateClusterConfig.enablePrivateNodes` is empty or `false`.

        2. **Assess exposure and necessity of public nodes**
           * For each non-private cluster from step 1, review:
             * Whether workloads require direct node access from the internet.
             * Whether connectivity can be provided via load balancers, Ingress, VPN, or Cloud Interconnect instead of public node IPs.
           * Document clusters where private nodes are feasible and desirable.

        3. **Check use of IP aliases and master CIDR (for planning migration)**
           * Run on: any machine with `gcloud` access.
           ```bash theme={null}
           CLUSTER_NAME="<CLUSTER_NAME>"
           LOCATION="<REGION_OR_ZONE>"
           PROJECT_ID="<PROJECT_ID>"

           gcloud container clusters describe "$CLUSTER_NAME" \
             --location "$LOCATION" --project "$PROJECT_ID" \
             --format='value(ipAllocationPolicy.useIpAliases, privateClusterConfig.masterIpv4CidrBlock)'
           ```
           * If `useIpAliases` is not `True`, note that the cluster cannot simply be flipped to private nodes; a new cluster will be required to meet the remediation.

        4. **Decide remediation approach per cluster**
           * If the cluster already has `useIpAliases=True` and your networking design allows it, plan to recreate the cluster with `--enable-private-nodes` (GKE does not support toggling this on an existing non-private cluster).
           * If `useIpAliases=False`, plan a migration to a new cluster that meets all three flags: `--enable-private-nodes`, `--enable-ip-alias`, and `--master-ipv4-cidr`.

        5. **Create a compliant replacement cluster**
           * Run on: any machine with `gcloud` access. Choose appropriate ranges for your environment.
           ```bash theme={null}
           PROJECT_ID="<PROJECT_ID>"
           LOCATION="<REGION_OR_ZONE>"
           NEW_CLUSTER_NAME="<NEW_PRIVATE_CLUSTER_NAME>"

           gcloud container clusters create "$NEW_CLUSTER_NAME" \
             --project "$PROJECT_ID" \
             --location "$LOCATION" \
             --enable-private-nodes \
             --enable-ip-alias \
             --master-ipv4-cidr="172.16.0.0/28"
           ```
           * Migrate workloads (namespaces, workloads, services, ingress, and associated IAM, firewall, and networking rules) from the old cluster to the new private one, then decommission the old cluster.

        6. **Verify remediation**
           * Run on: any machine with `gcloud` access.
           ```bash theme={null}
           CLUSTER_NAME="<NEW_PRIVATE_CLUSTER_NAME>"
           LOCATION="<REGION_OR_ZONE>"
           PROJECT_ID="<PROJECT_ID>"

           gcloud container clusters describe "$CLUSTER_NAME" \
             --location "$LOCATION" --project "$PROJECT_ID" \
             --format=json | jq '.privateClusterConfig.enablePrivateNodes, .ipAllocationPolicy.useIpAliases, .privateClusterConfig.masterIpv4CidrBlock'
           ```
           * Confirm that `enablePrivateNodes` is `true`, `useIpAliases` is `true`, and `masterIpv4CidrBlock` is set.
      </Accordion>

      <Accordion title="Using kubectl">
        kubectl cannot enable private nodes or change GKE cluster networking; this configuration is managed at the cloud provider control-plane level via gcloud, the GCP console, or IaC. Refer to the Manual Steps section for how to review the current cluster and recreate or reconfigure it with private nodes enabled.
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        # Reports whether each GKE cluster in a project/locations has private nodes enabled.
        # Run on: any machine with gcloud, jq, and appropriate IAM permissions.

        set -euo pipefail

        PROJECT_ID="YOUR_PROJECT_ID"    # <-- set explicitly
        LOCATIONS="-"                   # "-" = all locations; or space-separated list, e.g. "us-central1 us-east1"

        if [[ "$PROJECT_ID" == "YOUR_PROJECT_ID" ]]; then
          echo "ERROR: Set PROJECT_ID in this script before running." >&2
          exit 1
        fi

        echo "Project: $PROJECT_ID"
        echo "Locations: $LOCATIONS"
        echo

        # Get clusters (optionally filtered by locations)
        if [[ "$LOCATIONS" == "-" ]]; then
          CLUSTERS_JSON="$(gcloud container clusters list \
            --project "$PROJECT_ID" \
            --format=json)"
        else
          CLUSTERS_JSON="$(gcloud container clusters list \
            --project "$PROJECT_ID" \
            --filter="location:(${LOCATIONS// / OR })" \
            --format=json)"
        fi

        if [[ "$(echo "$CLUSTERS_JSON" | jq 'length')" -eq 0 ]]; then
          echo "No clusters found."
          exit 0
        fi

        printf "%-40s %-20s %-8s %-16s %-22s\n" "CLUSTER_NAME" "LOCATION" "PRIVATE" "MASTER_IPV4_CIDR" "NOTE"
        printf "%0.s-" {1..110}; echo

        echo "$CLUSTERS_JSON" | jq -r '.[] | @base64' | while read -r row; do
          _jq() { echo "$row" | base64 --decode | jq -r "$1"; }

          NAME=$(_jq '.name')
          LOCATION=$(_jq '.location')

          DESC_JSON="$(gcloud container clusters describe "$NAME" \
            --location "$LOCATION" \
            --project "$PROJECT_ID" \
            --format=json)"

          ENABLE_PRIVATE_NODES="$(echo "$DESC_JSON" | jq -r '.privateClusterConfig.enablePrivateNodes // "false"')"
          MASTER_IPV4_CIDR="$(echo "$DESC_JSON" | jq -r '.privateClusterConfig.masterIpv4CidrBlock // ""')"
          ENABLE_IP_ALIAS="$(echo "$DESC_JSON" | jq -r '.ipAllocationPolicy.useIpAliases // "false"')"

          NOTE=""
          if [[ "$ENABLE_PRIVATE_NODES" != "true" ]]; then
            NOTE="NOT PRIVATE (finding)"
          elif [[ "$ENABLE_PRIVATE_NODES" == "true" && "$ENABLE_IP_ALIAS" != "true" ]]; then
            NOTE="privateNodes=true but ip-alias DISABLED (misconfigured)"
          elif [[ "$ENABLE_PRIVATE_NODES" == "true" && -z "$MASTER_IPV4_CIDR" ]]; then
            NOTE="privateNodes=true but masterIpv4CidrBlock EMPTY (check config)"
          fi

          printf "%-40s %-20s %-8s %-16s %-22s\n" \
            "$NAME" "$LOCATION" "$ENABLE_PRIVATE_NODES" "$MASTER_IPV4_CIDR" "$NOTE"
        done
        ```

        Explanation of output indicating a problem:

        * Any cluster row with:
          * `PRIVATE` column showing `false`, or
          * `NOTE` column containing `NOT PRIVATE (finding)`

        indicates a non-compliant cluster for this control (nodes are not configured as private). Use this list as input to manual redesign/recreation/migration decisions; enabling private nodes on an existing non-private cluster is not a deterministic one-shot fix.
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
