Back to Checks
azure

AKS cluster has a private endpoint and node public access is disabled

aks_clusters_public_access_disabled
Service: aksSeverity: high
OfficialBuilt-inVerified
Categories:internet-exposedtrust-boundariescluster-security
Resource Type:microsoft.containerservice/managedclusters

AKS clusters expose a private control plane FQDN and agent pools have enable_node_public_ip=false.

The evaluation focuses on the presence of a private FQDN and the absence of public IPs on nodes.

Risk

Exposed node IPs or a publicly reachable API increase attack surface, impacting confidentiality and integrity.

  • Internet scans reach kubelet, NodePort, or management agents
  • Exploits enable RCE and lateral movement
  • Metadata/secret theft leads to credential compromise

Availability is at risk from DDoS on exposed endpoints.

Run this check with Prowler

Run in Prowler Cloud
Run
prowler azure --checks aks_clusters_public_access_disabled

Recommendation

Adopt private clusters and eliminate node public IPs:

  • Set enable_node_public_ip=false on all pools
  • Use Private Link or peered VNets with VPN/ExpressRoute for admin access
  • If public API is unavoidable, restrict with IP ranges and strong auth
  • Enforce NSGs/firewalls and least privilege for layered defense

Remediation

Terraform

# AKS cluster with private endpoint and nodes without public IPs
resource "azurerm_kubernetes_cluster" "<example_resource_name>" {
  name                = "<example_resource_name>"
  location            = "<location>"
  resource_group_name = "<example_resource_name>"
  dns_prefix          = "dns"

  private_cluster_enabled = true # Critical: enables private cluster (private endpoint/FQDN)

  default_node_pool {
    name                  = "nodepool1"
    node_count            = 1
    vm_size               = "Standard_DS2_v2"
    enable_node_public_ip = false # Critical: disables public IPs on nodes
  }

  identity {
    type = "SystemAssigned"
  }
}

NativeIaC

// AKS cluster with private endpoint and nodes without public IPs
resource aks 'Microsoft.ContainerService/managedClusters@2023-11-01' = {
  name: '<example_resource_name>'
  location: '<location>'
  identity: {
    type: 'SystemAssigned'
  }
  properties: {
    dnsPrefix: 'dns'
    apiServerAccessProfile: {
      enablePrivateCluster: true // Critical: enables private cluster (private endpoint/FQDN)
    }
    agentPoolProfiles: [
      {
        name: 'nodepool1'
        count: 1
        vmSize: 'Standard_DS2_v2'
        enableNodePublicIP: false // Critical: disables public IPs on nodes
      }
    ]
  }
}

Other

1In the Azure portal, go to Azure Kubernetes Service and select your cluster
2Go to Networking > API server access
3Set Private cluster to Enabled and click Save
4Go to Node pools, open each pool, select Networking, set Public IP per node to Disabled, and Save
5Repeat step 4 for all node pools

Check Code

from prowler.lib.check.models import Check, Check_Report_Azure
from prowler.providers.azure.services.aks.aks_client import aks_client


class aks_clusters_public_access_disabled(Check):
    def execute(self) -> Check_Report_Azure:
        findings = []

        for subscription_id, clusters in aks_client.clusters.items():
            subscription_name = aks_client.subscriptions.get(
                subscription_id, subscription_id
            )
            for cluster in clusters.values():
                report = Check_Report_Azure(metadata=self.metadata(), resource=cluster)
                report.subscription = subscription_id
                report.status = "FAIL"
                report.status_extended = f"Public access to nodes is enabled for cluster '{cluster.name}' in subscription '{subscription_name} ({subscription_id})'"

                if cluster.private_fqdn:
                    for agent_pool in cluster.agent_pool_profiles:
                        if not getattr(agent_pool, "enable_node_public_ip", False):
                            report.status = "PASS"
                            report.status_extended = f"Public access to nodes is disabled for cluster '{cluster.name}' in subscription '{subscription_name} ({subscription_id})'"

                findings.append(report)

        return findings