> ## 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 Endpoint Enabled And Public Access Disabled

### More Info:

Create clusters with a private control plane endpoint and public access disabled so the control plane is not reachable from the public internet. A public endpoint significantly increases exposure.

### Risk Level

Critical

### Address

Security

### Compliance Standards

* CIS GKE

### Triage and Remediation

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

    <AccordionGroup>
      <Accordion title="Manual Steps" defaultOpen="true">
        1. **Identify and review current endpoint configuration (any machine with gcloud installed)**
           ```bash theme={null}
           PROJECT_ID="<your-project-id>"
           LOCATION="<your-cluster-location>"
           CLUSTER_NAME="<your-cluster-name>"

           gcloud container clusters describe "$CLUSTER_NAME" \
             --location "$LOCATION" \
             --project "$PROJECT_ID" \
             --format="json(privateClusterConfig,privateCluster,endpoint,masterAuthorizedNetworksConfig)"
           ```
           Review:
           * `privateClusterConfig.enablePrivateEndpoint` is `true` (private endpoint enabled)
           * Public endpoint exposure (presence of `endpoint`, `privateCluster: true`)
           * Any `masterAuthorizedNetworksConfig` use, which still indicates a public control-plane endpoint.

        2. **Decide on remediation strategy (console/CLI/IaC review, no command)**\
           Based on your org’s policies, decide whether to:
           * Replace this cluster with a new private-cluster-only control plane, or
           * Accept and document the risk for this cluster (e.g., dependency on public endpoint, legacy tooling, no Private Service Connect / VPC access).

        3. **Plan a replacement private cluster with private endpoint only (any machine with gcloud installed)**\
           Draft the cluster creation command ensuring private endpoint and required flags are set (do not run in production until planned and approved):
           ```bash theme={null}
           PROJECT_ID="<your-project-id>"
           LOCATION="<your-cluster-location>"
           CLUSTER_NAME_NEW="<new-private-cluster-name>"
           MASTER_CIDR="<10.0.0.0/28>"   # choose an unused RFC1918 range in your VPC

           gcloud container clusters create "$CLUSTER_NAME_NEW" \
             --project "$PROJECT_ID" \
             --location "$LOCATION" \
             --enable-private-nodes \
             --enable-ip-alias \
             --enable-private-endpoint \
             --master-ipv4-cidr="$MASTER_CIDR"
           ```
           Review network prerequisites (VPC, subnets, routes, firewall rules, access paths for admins/CI) before actually creating the cluster.

        4. **If using IaC, update templates instead of ad‑hoc CLI (any IaC workstation)**
           * Modify Terraform/Deployment Manager/other templates so new clusters include:
             * `enable_private_nodes = true`
             * `enable_private_endpoint = true`
             * IP aliasing and `master_ipv4_cidr_block` set.
           * Run your normal plan/review process (e.g., `terraform plan`) to confirm only the intended changes will apply, then apply in a maintenance window.

        5. **Migrate workloads and cut over (any machine with kubectl and gcloud)**
           * Export and re‑apply workloads to the new private cluster. Example to switch context:
             ```bash theme={null}
             gcloud container clusters get-credentials "$CLUSTER_NAME_NEW" \
               --location "$LOCATION" \
               --project "$PROJECT_ID"
             ```
           * Reconfigure CI/CD, monitoring, and access paths to use the private endpoint (through VPC/PSC/Bastion as appropriate).
           * Once validated, decommission the old cluster:
             ```bash theme={null}
             gcloud container clusters delete "$CLUSTER_NAME" \
               --location "$LOCATION" \
               --project "$PROJECT_ID"
             ```

        6. **Verify the new cluster complies (any machine with gcloud installed)**
           ```bash theme={null}
           gcloud container clusters describe "$CLUSTER_NAME_NEW" \
             --location "$LOCATION" \
             --project "$PROJECT_ID" \
             --format="json(privateClusterConfig,privateCluster,endpoint)"
           ```
           Confirm:
           * `privateClusterConfig.enablePrivateEndpoint` is `true`.
           * The cluster is a private cluster (`privateCluster: true`).
           * Access to the control plane is only via the private endpoint (no reliance on a public endpoint in your operational procedures).
      </Accordion>

      <Accordion title="Using kubectl">
        kubectl cannot modify GKE control plane endpoint settings; they must be changed at the cloud provider level (gcloud/console/Terraform) where the cluster is created or updated. Refer to the Manual Steps section for the exact remediation process.
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        # Check GKE clusters for private endpoint enabled and public access disabled.
        # Requirements:
        # - Run on any machine with gcloud, jq, and access to the target projects.
        # - Authenticated to gcloud with sufficient IAM permissions.

        set -euo pipefail

        # Comma-separated list of projects to check (edit this list as needed)
        PROJECTS="<PROJECT_ID_1>,<PROJECT_ID_2>"

        IFS=',' read -r -a PROJECT_ARRAY <<< "${PROJECTS}"

        printf "project,location,cluster_name,enablePrivateNodes,enablePrivateEndpoint,masterIpv4Cidr,publicEndpointAccessible\n"

        for PROJECT in "${PROJECT_ARRAY[@]}"; do
          PROJECT="$(echo "${PROJECT}" | xargs)"  # trim spaces
          if [[ -z "${PROJECT}" ]]; then
            continue
          fi

          # List all clusters in the project across all locations
          CLUSTERS_JSON="$(gcloud container clusters list \
            --project "${PROJECT}" \
            --format=json 2>/dev/null || echo "[]")"

          echo "${CLUSTERS_JSON}" | jq -c '.[]' | while read -r CLUSTER; do
            NAME="$(echo "${CLUSTER}" | jq -r '.name')"
            LOCATION="$(echo "${CLUSTER}" | jq -r '.location')"

            DESC="$(gcloud container clusters describe "${NAME}" \
              --location "${LOCATION}" \
              --project "${PROJECT}" \
              --format=json 2>/dev/null || echo "{}")"

            ENABLE_PRIVATE_NODES="$(echo "${DESC}" | jq -r '.privateClusterConfig.enablePrivateNodes // false')"
            ENABLE_PRIVATE_ENDPOINT="$(echo "${DESC}" | jq -r '.privateClusterConfig.enablePrivateEndpoint // false')"
            MASTER_IPV4_CIDR="$(echo "${DESC}" | jq -r '.privateClusterConfig.masterIpv4CidrBlock // ""')"

            # Public endpoint is considered accessible if:
            # - private endpoint is NOT enabled, or
            # - the public endpoint access is not explicitly disabled
            # GKE does not have a direct "disable public" flag; access is controlled via masterAuthorizedNetworksConfig.
            # For this benchmark, treat any cluster without enablePrivateEndpoint=true as exposing a public endpoint.
            if [[ "${ENABLE_PRIVATE_ENDPOINT}" != "true" ]]; then
              PUBLIC_ENDPOINT_ACCESSIBLE="true"
            else
              # When private endpoint is enabled, there is still an external control plane address,
              # but access can be restricted. For a conservative interpretation, flag as false
              # only when private endpoint is enabled and authorized networks is empty/disabled.
              MAN_AUTH_ENABLED="$(echo "${DESC}" | jq -r '.masterAuthorizedNetworksConfig.enabled // false')"
              if [[ "${MAN_AUTH_ENABLED}" == "true" ]]; then
                # There are explicitly allowed external CIDRs; treat as public-accessible for this control.
                PUBLIC_ENDPOINT_ACCESSIBLE="true"
              else
                PUBLIC_ENDPOINT_ACCESSIBLE="false"
              fi
            fi

            printf "%s,%s,%s,%s,%s,%s,%s\n" \
              "${PROJECT}" \
              "${LOCATION}" \
              "${NAME}" \
              "${ENABLE_PRIVATE_NODES}" \
              "${ENABLE_PRIVATE_ENDPOINT}" \
              "${MASTER_IPV4_CIDR}" \
              "${PUBLIC_ENDPOINT_ACCESSIBLE}"
          done
        done
        ```

        Explanation of output indicating a problem:

        * The script prints one CSV line per cluster.
        * A cluster is non-compliant and needs manual review when:
          * `enablePrivateEndpoint` is `false`, or
          * `enablePrivateNodes` is `false`, or
          * `masterIpv4Cidr` is empty, or
          * `publicEndpointAccessible` is `true`.

        According to the benchmark remediation, a fully aligned cluster should show:

        * `enablePrivateNodes` = `true`
        * `enablePrivateEndpoint` = `true`
        * `masterIpv4Cidr` = non-empty CIDR
        * `publicEndpointAccessible` = `false`
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
