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
- Remediation
Remediation
Manual Steps
-
Gather current API server access profile (AKS CLI)
- Run on any machine with Azure CLI and access to the subscription:
az aks show \--name <CLUSTER_NAME> \--resource-group <RESOURCE_GROUP> \--query "apiServerAccessProfile" \--output json- Record:
enablePrivateClusterauthorizedIpRanges- Any existing private endpoint configuration in your VNet.
-
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.
-
Enable private endpoint access if not already enabled
- If
enablePrivateClusterisfalse, 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):
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.
- If
-
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:
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
authorizedIpRangesif public access is enabled; omitting it (or setting it empty) effectively allows0.0.0.0/0.
-
Optionally disable the public endpoint entirely
- If all administrative and workload access can use the private endpoint, disable public access after confirming alternative connectivity:
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.
-
Verify and document the final state
- Re-run:
az aks show \--name <CLUSTER_NAME> \--resource-group <RESOURCE_GROUP> \--query "apiServerAccessProfile" \--output json- Confirm:
enablePrivateClusteristrue.- If public endpoint is used,
authorizedIpRangesis present and matches the approved minimal CIDR list.
- Attempt
kubectlaccess from an allowed IP (should succeed) and from a clearly disallowed IP (should fail), then record the results and the approved configuration.
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.
Automation
#!/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:
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=falseandAuthorizedIpRangesis empty- Problem: public endpoint is open to the internet (equivalent to
0.0.0.0/0).
- Problem: public endpoint is open to the internet (equivalent to
-
PrivateCluster=falseandAuthorizedIpRangescontains broad ranges (for example0.0.0.0/0,0.0.0.0/1, or other very large CIDRs)- Likely problem: public endpoint too permissive; requires manual review.
-
PrivateCluster=trueandPublicFQDNnon-empty with empty or broadAuthorizedIpRanges- 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
authorizedIpRangesto specific admin/jump-host CIDRs viaaz aks updateor IaC.