Skip to main content

Clusters Are Created With Private Endpoint Enabled And Public Access Disabled

More Info:​

Disable access to the Kubernetes API from outside the node network if it is not required.

Risk Level​

Low

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)
  • NIST SP 800-171
  • NYDFS 23 NYCRR 500
  • SWIFT Customer Security Controls Framework
  • Sarbanes-Oxley IT General Controls
  • Securities and Exchange Board of India (SEBI) - Cloud Security Adoption Framework
  • UK NCSC Cyber Assessment Framework

Triage and Remediation​

Remediation​

Manual Steps
  1. Gather current API server access configuration

    • On any machine with az access:
      az aks show \
      --resource-group MY_RESOURCE_GROUP \
      --name MY_AKS_CLUSTER \
      --query "apiServerAccessProfile" \
      --output jsonc
    • Review:
      • enablePrivateCluster
      • enablePrivateClusterPublicFQDN
      • authorizedIpRanges
      • enablePublicFqdn (if present)
  2. Decide if public API access is required

    • Identify all users/automation that access the API:
      • From on-prem networks
      • From developer laptops
      • From other Azure services or VNets
    • Determine if these can instead:
      • Access from within the AKS VNet or peered VNets
      • Use a VPN/ExpressRoute into the VNet
    • If any use case truly requires access from the public internet, document the minimum set of source IP ranges required.
  3. If keeping public endpoint, restrict it to specific CIDR ranges

    • On any machine with az access, if you must retain public access:
      az aks update \
      --resource-group MY_RESOURCE_GROUP \
      --name MY_AKS_CLUSTER \
      --api-server-authorized-ip-ranges 203.0.113.0/24 198.51.100.10/32
    • Replace the CIDRs with the minimal, documented ranges needed.
  4. If feasible, disable the public endpoint and use private access

    • Confirm private connectivity is or will be in place:
      • AKS cluster deployed as a private cluster (enablePrivateCluster: true), or plan to recreate it as such.
      • VNets/peering/VPN/ExpressRoute are configured so administrators and CI/CD can reach the AKS VNet.
    • When ready to disable the public endpoint:
      az aks update \
      --resource-group MY_RESOURCE_GROUP \
      --name MY_AKS_CLUSTER \
      --api-server-access-profile enablePublicFqdn=false
    • Be aware this can break all existing accesses relying on the public FQDN; ensure alternative access paths first.
  5. Configure or validate private DNS for the API server

    • On any machine with az access, list private DNS zone links for the AKS VNet:
      az network private-dns link vnet list \
      --resource-group MY_DNS_RESOURCE_GROUP \
      --zone-name "privatelink.<region>.azmk8s.io" \
      --output table
    • If no appropriate link exists, create one between the AKS VNet and the AKS private DNS zone according to your network design and Azure documentation.
  6. Verify final state against the intent and document

    • Re-run:
      az aks show \
      --resource-group MY_RESOURCE_GROUP \
      --name MY_AKS_CLUSTER \
      --query "apiServerAccessProfile" \
      --output jsonc
    • Confirm:
      • If the goal is full compliance: enablePrivateCluster is true and enablePublicFqdn (or equivalent) is false.
      • If public access is retained: authorizedIpRanges contains only the minimal, documented CIDR ranges.
    • Record your decision (private only vs restricted public) and the evidence output for future reviews.
Using kubectl

kubectl cannot modify AKS control-plane networking (private endpoint or public API access); these settings are managed at the cloud provider/cluster configuration layer (Azure portal/CLI/IaC), not via Kubernetes API objects. Use kubectl only to verify cluster reachability; follow the guidance in the Manual Steps section to actually remediate this finding.

Automation
#!/usr/bin/env bash
#
# Check AKS clusters for:
# - Private endpoint enabled
# - Public API disabled
#
# Requirements:
# - Azure CLI logged in and with subscription access
# - jq installed

set -euo pipefail

# If you want to scope to a single subscription, uncomment and set this:
# az account set --subscription "SUBSCRIPTION_ID"

echo "subscription_id,resource_group,cluster_name,private_cluster_enabled,public_fqdn_enabled,problem"

# List all AKS clusters in all accessible subscriptions
az aks list --query '[].{id:id, name:name, rg:resourceGroup, sub:split(id, "/")[2]}' -o tsv | while IFS=$'\t' read -r id name rg sub; do
# Get full cluster properties in JSON
json="$(az aks show --ids "$id" -o json)"

# Extract fields
private_cluster_enabled="$(printf '%s' "$json" | jq -r '.apiServerAccessProfile.enablePrivateCluster // false')"
public_fqdn_enabled="$(printf '%s' "$json" | jq -r '.apiServerAccessProfile.enablePublicFqdn // true')"

# Determine if this violates the intent:
# - We prefer: private_cluster_enabled == true AND public_fqdn_enabled == false
problem="false"
reason=""

if [ "$private_cluster_enabled" != "true" ]; then
problem="true"
reason="no_private_endpoint"
fi

if [ "$public_fqdn_enabled" != "false" ]; then
if [ "$problem" = "true" ]; then
reason="${reason}+public_endpoint_enabled"
else
problem="true"
reason="public_endpoint_enabled"
fi
fi

echo "${sub},${rg},${name},${private_cluster_enabled},${public_fqdn_enabled},${problem}${reason:+($reason)}"
done

Run location:

  • Any machine with Azure CLI access and appropriate RBAC rights.

How to interpret the output:

  • private_cluster_enabled=true AND public_fqdn_enabled=false AND problem=false → aligned with the benchmark intent.
  • Any row with problem=true(...) indicates something to review:
    • problem=true(no_private_endpoint) → cluster not using a private endpoint.
    • problem=true(public_endpoint_enabled) → public API endpoint still enabled.
    • problem=true(no_private_endpoint+public_endpoint_enabled) → both issues present (most concerning case).

Additional Reading:​