Back to Checks
azure

AKS cluster nodes do not have public IP addresses

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

AKS agent pools use only private addressing, with node public IP assignment disabled (enableNodePublicIP=false). Clusters where any pool assigns a public IP to nodes are identified.

Prowler logoProwlerprowler-provider-azure

Risk

Public node IPs expose worker VMs to Internet scanning and exploit attempts against OS or kubelet services. Successful compromise can lead to stolen secrets and images (confidentiality), workload tampering (integrity), and node/resource exhaustion via DDoS or cryptomining (availability).

Run this check with Prowler

Run in Prowler Cloud
Run
prowler azure --checks aks_clusters_created_with_private_nodes

Recommendation

Disable public node IPs on all agent pools (enableNodePublicIP=false) and route egress via a NAT gateway or Azure Firewall. Apply least privilege with NSGs blocking Internet ingress, use private endpoints and bastion/VPN for admin access, and adopt defense in depth with continuous hardening and monitoring.

Remediation

Terraform

# AKS cluster with node public IPs disabled
resource "azurerm_kubernetes_cluster" "<example_resource_name>" {
  name                = "<example_resource_name>"
  location            = "<location>"
  resource_group_name = "<example_resource_name>"
  dns_prefix          = "<example_resource_name>"

  default_node_pool {
    name                  = "default"
    node_count            = 1
    vm_size               = "Standard_DS2_v2"
    enable_node_public_ip = false # CRITICAL: ensures nodes have no public IPs
  }

  identity {
    type = "SystemAssigned"
  }
}

NativeIaC

// AKS cluster with nodes not assigned public IPs
resource mc '<example_resource_name>@Microsoft.ContainerService/managedClusters@2024-05-01' = {
  name: '<example_resource_name>'
  location: '<location>'
  identity: {
    type: 'SystemAssigned'
  }
  properties: {
    dnsPrefix: '<example_resource_name>'
    agentPoolProfiles: [
      {
        name: 'nodepool1'
        count: 1
        vmSize: 'Standard_DS2_v2'
        enableNodePublicIP: false // CRITICAL: ensures nodes have no public IPs
      }
    ]
  }
}

Other

1In the Azure portal, go to Kubernetes services > <your AKS cluster> > Node pools
2For any node pool showing Node public IP: Enabled, click Add node pool
3Create the new pool with the same size/count; set Node public IP to Disabled (or uncheck Enable node public IP)
4Wait until the new pool is Ready
5If replacing a system pool, set the new pool to Mode: System (or Set as default system pool)
6Delete the old node pool(s) that had Node public IP enabled
7Verify all node pools show Node public IP: Disabled

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_created_with_private_nodes(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 = "PASS"
                report.status_extended = f"Cluster '{cluster.name}' was created with private nodes in subscription '{subscription_name} ({subscription_id})'"

                for agent_pool in cluster.agent_pool_profiles:
                    if getattr(agent_pool, "enable_node_public_ip", True):
                        report.status = "FAIL"
                        report.status_extended = f"Cluster '{cluster.name}' was not created with private nodes in subscription '{subscription_name} ({subscription_id})'"
                        break

                findings.append(report)

        return findings