azure-aks-public-api-server

explicit azure emits ExposedToInternet

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 typeKubernetesCluster
target typeAnonymousIdentity
source<AKS managedCluster (public API server endpoint)>
target<AnonymousIdentity (any internet principal)>
conditionsnetwork_reachability
state logicACTIVE 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)."
move · open · esc close