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

# Restrict Access To The Control Plane Endpoint

### More Info:

Restrict access to the Kubernetes API server by enabling private endpoint access and limiting authorized public IP CIDR ranges so the control plane is not reachable from all IP addresses.

### Risk Level

High

### Address

Security

### Compliance Standards

* APRA CPS 234 (Australia)
* BSI C5 (Germany)
* Brazil LGPD
* CCPA / CPRA (California)
* CIS AKS
* CIS Critical Security Controls v8
* CMMC 2.0
* CSA Cloud Controls Matrix v4
* DPDPA
* Digital Operational Resilience Act (EU)
* Essential 8
* 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. **Gather current API server access profile (AKS CLI)**
           * Run on any machine with Azure CLI and access to the subscription:
           ```bash theme={null}
           az aks show \
             --name <CLUSTER_NAME> \
             --resource-group <RESOURCE_GROUP> \
             --query "apiServerAccessProfile" \
             --output json
           ```
           * Record:
             * `enablePrivateCluster`
             * `authorizedIpRanges`
             * Any existing private endpoint configuration in your VNet.

        2. **Decide desired access model with security/network owners**
           * Option A (strongest): Private endpoint only (no public access).
           * Option B: Private endpoint + restricted public access from a small set of trusted office/VPN/jump-host CIDR blocks.
           * Explicitly document:
             * Whether the cluster must be reachable from the public internet at all.
             * Exact CIDR ranges that should be allowed if public access is required.
             * The VNet/subnets that should host the private endpoint and workloads.

        3. **Enable private endpoint access if not already enabled**
           * If `enablePrivateCluster` is `false`, plan that enabling it will change how API access works and may require VNet routing/firewall changes.
           * Update the cluster (this triggers control-plane reconfiguration):
           ```bash theme={null}
           az aks update \
             --name <CLUSTER_NAME> \
             --resource-group <RESOURCE_GROUP> \
             --api-server-access-profile enablePrivateCluster=true
           ```
           * Ensure your admin/workload networks can reach the private endpoint IPs inside your VNet.

        4. **Restrict public endpoint (if it must remain enabled)**
           * Determine the minimal set of CIDR ranges that need access (for example, corporate VPN and bastion hosts).
           * Apply the restriction:
           ```bash theme={null}
           az aks update \
             --name <CLUSTER_NAME> \
             --resource-group <RESOURCE_GROUP> \
             --api-server-access-profile enablePrivateCluster=true \
             --api-server-access-profile authorizedIpRanges="<CIDR_1>,<CIDR_2>,<CIDR_3>"
           ```
           * Do not omit `authorizedIpRanges` if public access is enabled; omitting it (or setting it empty) effectively allows `0.0.0.0/0`.

        5. **Optionally disable the public endpoint entirely**
           * If all administrative and workload access can use the private endpoint, disable public access after confirming alternative connectivity:
           ```bash theme={null}
           az aks update \
             --name <CLUSTER_NAME> \
             --resource-group <RESOURCE_GROUP> \
             --api-server-access-profile enablePrivateCluster=true \
             --public-fqdn false
           ```
           * Coordinate this change with all teams that currently access the cluster from the internet.

        6. **Verify and document the final state**
           * Re-run:
           ```bash theme={null}
           az aks show \
             --name <CLUSTER_NAME> \
             --resource-group <RESOURCE_GROUP> \
             --query "apiServerAccessProfile" \
             --output json
           ```
           * Confirm:
             * `enablePrivateCluster` is `true`.
             * If public endpoint is used, `authorizedIpRanges` is present and matches the approved minimal CIDR list.
           * Attempt `kubectl` access from an allowed IP (should succeed) and from a clearly disallowed IP (should fail), then record the results and the approved configuration.
      </Accordion>

      <Accordion title="Using kubectl">
        kubectl cannot restrict access to the AKS control plane endpoint, because this setting is managed at the Azure/cluster configuration layer (via Azure Portal, `az aks`, or IaC) rather than through Kubernetes API objects. To address this finding, make the required changes on the managed control plane as described in the Manual Steps section.
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        #
        # Report AKS control plane endpoint exposure for all clusters in all subscriptions.
        # Requires: az CLI (logged in), jq
        #
        # Runs on: any machine with Azure CLI access.

        set -euo pipefail

        echo "Subscription,ResourceGroup,ClusterName,PrivateCluster,PublicFQDN,AuthorizedIpRanges" 

        # List all subscriptions
        az account list --query '[].id' -o tsv | while read -r SUB_ID; do
          az account set --subscription "$SUB_ID"

          # List all AKS clusters in this subscription
          az aks list -o json | jq -r '.[] | [.name, .resourceGroup] | @tsv' | while IFS=$'\t' read -r CLUSTER RG; do
            # Get detailed properties for each cluster
            CLUSTER_JSON=$(az aks show -g "$RG" -n "$CLUSTER" -o json)

            PRIVATE_CLUSTER=$(echo "$CLUSTER_JSON" | jq -r '.apiServerAccessProfile.enablePrivateCluster // false')
            PUBLIC_FQDN=$(echo "$CLUSTER_JSON" | jq -r '.fqdn // ""')
            AUTH_IPS=$(echo "$CLUSTER_JSON" | jq -r '.apiServerAccessProfile.authorizedIpRanges // [] | join(";")')

            echo "\"$SUB_ID\",\"$RG\",\"$CLUSTER\",\"$PRIVATE_CLUSTER\",\"$PUBLIC_FQDN\",\"$AUTH_IPS\""
          done
        done
        ```

        ### How to run

        On any machine with Azure CLI and `jq`:

        ```bash theme={null}
        chmod +x report-aks-api-endpoints.sh
        ./report-aks-api-endpoints.sh > aks-api-endpoints.csv
        ```

        ### How to interpret the output

        Each line:

        * `PrivateCluster` = `true`
          * API server has private endpoint enabled.

        * `PrivateCluster` = `false` **and** `AuthorizedIpRanges` is empty
          * Problem: public endpoint is open to the internet (equivalent to `0.0.0.0/0`).

        * `PrivateCluster` = `false` **and** `AuthorizedIpRanges` contains broad ranges (for example `0.0.0.0/0`, `0.0.0.0/1`, or other very large CIDRs)
          * Likely problem: public endpoint too permissive; requires manual review.

        * `PrivateCluster` = `true` **and** `PublicFQDN` non-empty with empty or broad `AuthorizedIpRanges`
          * Problem: private endpoint is enabled, but public access remains overly open.

        Use this report to manually decide, per cluster, whether to:

        * Enable private cluster, and/or
        * Restrict `authorizedIpRanges` to specific admin/jump-host CIDRs via `az aks update` or IaC.
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
