azure-aks-public-api-server
match (record)
{
"field": "properties.apiServerAccessProfile",
"resource_type": "Microsoft.ContainerService/managedClusters"
}
where
apiServerAccessProfile.authorizedIPRanges is empty, null, or absent
cluster has no privateLinkResources (not a fully private cluster)
publicFQDN is set and non-empty
emit
| source type | KubernetesCluster |
|---|---|
| target type | AnonymousIdentity |
| source | <AKS managedCluster (public API server endpoint)> |
| target | <AnonymousIdentity (any internet principal)> |
| conditions | network_reachability |
| state logic | ACTIVE when the cluster has a public FQDN and authorizedIPRanges is empty/absent — any internet host can reach the Kubernetes API server and attempt authentication (no IP-level filtering). CONDITIONAL(network_reachability) when authorizedIPRanges is set — only listed IP ranges can reach the API server. BLOCKED for a fully private cluster (no public endpoint + privateLinkResources set + no public FQDN). Note: runCommand bypasses this exposure gate — even a private cluster is reachable via the ARM runCommand action regardless of this edge. |
Narrative
AKS cluster {cluster.name} has a public API server endpoint with no authorized-IP restrictions; any internet host can reach the Kubernetes API server and attempt authentication (authentication still required - see azure-aks-list-admin-cred for the cert bypass).
Raw rule rules/explicit/azure-aks.yaml
id: azure-aks-public-api-server
emits: ExposedToInternet
applies_to:
- azure
match_record:
resource_type: Microsoft.ContainerService/managedClusters
field: properties.apiServerAccessProfile
where:
- apiServerAccessProfile.authorizedIPRanges is empty, null, or absent
- cluster has no privateLinkResources (not a fully private cluster)
- publicFQDN is set and non-empty
emit:
source_type: KubernetesCluster
target_type: AnonymousIdentity
source: <AKS managedCluster (public API server endpoint)>
target: <AnonymousIdentity (any internet principal)>
api_source: az aks show --name <name> --resource-group <rg>
evidence_field: properties.apiServerAccessProfile + properties.publicFQDN
conditions:
- network_reachability
state_logic: "ACTIVE when the cluster has a public FQDN and authorizedIPRanges is empty/absent \u2014\
\ any internet host can reach the Kubernetes API server and attempt authentication (no IP-level filtering).\
\ CONDITIONAL(network_reachability) when authorizedIPRanges is set \u2014 only listed IP ranges can\
\ reach the API server. BLOCKED for a fully private cluster (no public endpoint + privateLinkResources\
\ set + no public FQDN). Note: runCommand bypasses this exposure gate \u2014 even a private cluster\
\ is reachable via the ARM runCommand action regardless of this edge."
confidence: 0.9
false_positive_note: "A public FQDN with no authorized-IP restriction is internet-exposed at the TCP/TLS\
\ layer; actual exploitation still requires valid Entra credentials or a static cert (from listClusterAdminCredential).\
\ Do NOT conflate network exposure with authentication bypass \u2014 ExposedToInternet models the\
\ reachability fact, not the auth bypass (which is azure-aks-list-admin-cred). For a private cluster,\
\ the API server is NOT internet-reachable, but runCommand still works from the ARM plane."
narrative: "AKS cluster {cluster.name} has a public API server endpoint with no authorized-IP restrictions;\
\ any internet host can reach the Kubernetes API server and attempt authentication (authentication\
\ still required \u2014 see azure-aks-list-admin-cred for the cert bypass)."