> ## 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 disable public access, so the Kubernetes API is not reachable from outside the node network when not required.

### Risk Level

Critical

### Address

Security

### Compliance Standards

* CIS OKE

### Triage and Remediation

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

    <AccordionGroup>
      <Accordion title="Manual Steps" defaultOpen="true">
        1. **Identify the cluster and current endpoint configuration**
           * On any machine with access to your Oracle tenancy and CLI configured, run:
             ```bash theme={null}
             oci ce cluster list \
               --compartment-id <OCID_OF_COMPARTMENT> \
               --all \
               --output table \
               --query "data[].{Name:name, OCID:id, EndpointConfig:endpoint-config}"
             ```
           * Note for each cluster:
             * `is-public-ip-enabled` (public endpoint on/off)
             * `is-private-endpoint-enabled` (private endpoint on/off)
             * Any `nsg-ids` or `subnet-id` associated with the endpoint.

        2. **Review access requirements and network design**
           * With your platform/network/application owners, decide for each cluster:
             * Do administrators or CI/CD systems *outside* the VCN (or connected networks like VPN/DRG/peering) need direct API access?
             * Can all required API consumers run from within the VCN (bastion host, jumpbox, VPN, private peering, etc.)?
           * If all legitimate access can be provided from within private networks, plan to:
             * Enable private endpoint.
             * Disable public endpoint.

        3. **Check for dependencies on the public API endpoint**
           * On any machine with kubectl access to the cluster, inspect the current API server URL used by admins/automation:
             ```bash theme={null}
             kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}'; echo
             ```
           * Inventory all systems that use this endpoint (IDPs, CI/CD, monitoring, external operators, developer tools).
           * For each, decide if:
             * It can be moved inside the VCN / over VPN / over private peering, or
             * It strictly requires public access (in which case document and formally accept the risk as an exception).

        4. **Plan and apply configuration changes in OCI**
           * If you determine public access is not required for a cluster:
             * In the OCI Console:
               1. Go to “Developer Services” → “Kubernetes Clusters (OKE)” → select the cluster.
               2. Locate the “Endpoint” or “Cluster Access” section.
               3. Ensure a **private endpoint** is configured in an appropriate private subnet and NSGs.
               4. Disable the **public endpoint** / public access setting.
           * If using IaC (Terraform), review your code for the cluster resource, for example:
             * Ensure:
               ```hcl theme={null}
               endpoint_config {
                 is_private_endpoint_enabled = true
                 is_public_endpoint_enabled  = false
               }
               ```
             * Apply the change with your standard pipeline (e.g., `terraform plan` / `terraform apply`) following your change-management process.

        5. **Verify endpoint exposure after the change**
           * On any machine with OCI CLI:
             ```bash theme={null}
             oci ce cluster get --cluster-id <CLUSTER_OCID> --query "data.endpoint-config" --output json
             ```
           * Confirm:
             * `"isPrivateEndpointEnabled": true`
             * `"isPublicEndpointEnabled": false` (or equivalent field for public IP).
           * From an external network not connected to the VCN, confirm that the previous public API endpoint is no longer reachable (e.g., TLS handshake or `curl` now fails / times out).

        6. **Update access patterns and documentation**
           * Ensure all administrators and automation now use the private API endpoint (update `kubeconfig` distributions, bastion/jumpbox access, VPN instructions).
           * Document:
             * The decision to disable public access (or the justified exception if you kept it).
             * The VCN/subnet/NSG design that provides required private access to the Kubernetes API.
      </Accordion>

      <Accordion title="Using kubectl">
        kubectl cannot be used to enable a private control‑plane endpoint or disable public API access, because these settings are only configurable on the managed control plane via your cloud provider’s console, CLI, or IaC definitions. Please refer to the Manual Steps section for guidance on reviewing and updating the cluster’s control‑plane networking configuration.
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        #
        # Report OKE cluster endpoint exposure across compartments and regions.
        # Requirements:
        #   - OCI CLI installed and configured (https://docs.oracle.com/en-us/iaas/Content/API/SDKDocs/cliinstall.htm)
        #   - jq installed
        #
        # Run location:
        #   - Any machine with OCI CLI access and permissions to list compartments and clusters.

        set -euo pipefail

        # ----- CONFIGURATION -----
        # Optional: limit to specific regions or compartments by editing these arrays.
        REGIONS=()         # e.g. REGIONS=("eu-frankfurt-1" "us-ashburn-1")
        COMPARTMENT_OCIDS=() # e.g. COMPARTMENT_OCIDS=("ocid1.compartment.oc1..xxxx")

        # ----- HELPER FUNCTIONS -----
        oci_json() {
          oci "$@" --output json
        }

        get_all_regions() {
          if ((${#REGIONS[@]} > 0)); then
            printf '%s\n' "${REGIONS[@]}"
          else
            oci_json iam region list | jq -r '.data[].name'
          fi
        }

        get_all_compartments() {
          # Returns all active compartments in the tenancy (excluding root),
          # unless COMPARTMENT_OCIDS is explicitly set.
          if ((${#COMPARTMENT_OCIDS[@]} > 0)); then
            printf '%s\n' "${COMPARTMENT_OCIDS[@]}"
          else
            local tenancy_ocid
            tenancy_ocid="$(oci_json iam compartment list --compartment-id-in-subtree false --all | jq -r '.data[] | select(.["compartment-id"] == null) | .id')"

            oci_json iam compartment list \
              --compartment-id "${tenancy_ocid}" \
              --compartment-id-in-subtree true \
              --all \
              | jq -r '.data[] | select(.lifecycle-state=="ACTIVE") | .id'
          fi
        }

        echo "Scanning OKE clusters for API endpoint exposure..."
        echo

        header="region,compartment_name,cluster_name,cluster_ocid,endpoint_public_hostname,endpoint_private_endpoint_ip,endpoint_is_public_ip_enabled,endpoint_is_private_endpoint,has_public_access_issue"
        echo "${header}"

        for region in $(get_all_regions); do
          export OCI_CLI_REGION="${region}"

          # Map compartment OCID -> name (for readability in output)
          declare -A COMP_NAME
          while read -r cid cname; do
            COMP_NAME["$cid"]="$cname"
          done < <(
            oci_json iam compartment list --compartment-id-in-subtree true --all \
              | jq -r '.data[] | select(.lifecycle-state=="ACTIVE") | "\(.id) \(.name)"'
          )

          for compartment in $(get_all_compartments); do
            clusters_json="$(oci_json ce cluster list --compartment-id "${compartment}" --all 2>/dev/null || echo '{}')"
            cluster_count="$(jq '.data | length' <<<"${clusters_json}")"
            if [[ "${cluster_count}" -eq 0 ]]; then
              continue
            fi

            echo "${clusters_json}" | jq -c '.data[]' | while read -r c; do
              cluster_id="$(jq -r '.id' <<<"${c}")"
              cluster_name="$(jq -r '.name' <<<"${c}")"

              # Get full cluster details (includes endpoint config)
              detail="$(oci_json ce cluster get --cluster-id "${cluster_id}" 2>/dev/null | jq -c '.data')"

              ep_host="$(jq -r '.endpoints.kubernetes' <<<"${detail}" 2>/dev/null || echo "null")"
              ep_public_ip_enabled="$(jq -r '.endpoint-config.is-public-ip-enabled // "unknown"' <<<"${detail}" 2>/dev/null)"
              ep_is_private_endpoint="$(jq -r '.endpoint-config.is-private-endpoint // "unknown"' <<<"${detail}" 2>/dev/null)"
              ep_private_ip="$(jq -r '.endpoint-config.private-endpoint-ip // "null"' <<<"${detail}" 2>/dev/null)"

              # Define "problem" per CIS: public endpoint enabled (is-public-ip-enabled == true)
              # or not using private endpoint (is-private-endpoint != true) when private connectivity is possible.
              issue="false"
              if [[ "${ep_public_ip_enabled}" == "true" ]]; then
                issue="true"
              fi

              comp_name="${COMP_NAME[${compartment}]:-unknown}"

              printf '%s,%s,%s,%s,%s,%s,%s,%s,%s\n' \
                "${region}" \
                "${comp_name}" \
                "${cluster_name}" \
                "${cluster_id}" \
                "${ep_host}" \
                "${ep_private_ip}" \
                "${ep_public_ip_enabled}" \
                "${ep_is_private_endpoint}" \
                "${issue}"
            done
          done
        done

        cat <<'EOF'

        INTERPRETING RESULTS
        --------------------
        Each line describes an OKE cluster:

        - endpoint_is_public_ip_enabled:
            - "true"  -> The Kubernetes API is reachable via a public IP (POTENTIAL PROBLEM)
            - "false" -> Public IP disabled
            - "unknown" -> Could not determine (review in console/CLI manually)

        - endpoint_is_private_endpoint:
            - "true"  -> Cluster uses a private endpoint in your VCN
            - "false" -> Private endpoint not used
            - "unknown" -> Could not determine (review manually)

        - has_public_access_issue:
            - "true"  -> Fails the intent of CIS OKE 5.4.2: public API access is enabled
            - "false" -> Does not obviously violate the control, but still confirm design intent

        Flagged rows ("has_public_access_issue=true") should be reviewed to decide if:
          - Public API access is strictly required for operations, AND
          - Compensating controls (IP allowlisting, VPN, bastion, etc.) are in place.

        This script only reports configuration; disabling public access or switching to a private
        endpoint must be done manually via OCI Console/CLI/Terraform following your change process.
        EOF
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
