Azure AKS Multi-Resource Setup Discovery

Detects an AKS (Azure Kubernetes Service) identity that lists or gets multiple distinct Kubernetes API resource types associated with cluster orientation (namespaces, nodes, pods, roles, clusterroles, configmaps, serviceaccounts, rolebindings, clusterrolebindings) within a short window. Cardinality of distinct resources touched, not raw event count, is the signal that separates recon/orientation behavior from steady-state controllers that repeatedly touch a narrow set of resources. Known platform and control-plane identities are excluded.

Elastic rule (View on GitHub)

  1[metadata]
  2creation_date = "2026/08/04"
  3integration = ["azure"]
  4maturity = "production"
  5min_stack_comments = "field_extract not available until 9.5.0"
  6min_stack_version = "9.5.0"
  7updated_date = "2026/08/04"
  8
  9[rule]
 10author = ["Elastic"]
 11description = """
 12Detects an AKS (Azure Kubernetes Service) identity that lists or gets multiple distinct Kubernetes API resource types
 13associated with cluster orientation (namespaces, nodes, pods, roles, clusterroles, configmaps, serviceaccounts,
 14rolebindings, clusterrolebindings) within a short window. Cardinality of distinct resources touched, not raw event
 15count, is the signal that separates recon/orientation behavior from steady-state controllers that repeatedly touch a
 16narrow set of resources. Known platform and control-plane identities are excluded.
 17"""
 18false_positives = [
 19    """
 20    GitOps, backup, and platform operators may span these resource types during sync. Exclude validated service accounts
 21    or raise the cardinality threshold after baselining.
 22    """,
 23]
 24from = "now-9m"
 25language = "esql"
 26license = "Elastic License v2"
 27name = "Azure AKS Multi-Resource Setup Discovery"
 28note = """## Triage and analysis
 29
 30### Investigating Azure AKS Multi-Resource Setup Discovery
 31
 32AKS kube-audit events are carried under the flattened `azure.platformlogs.properties.log.*` subtree, so this rule uses
 33`field_extract` to pull `stage`, `verb`, `objectRef.resource`, `objectRef.namespace`, `user.username`, `userAgent`,
 34and `sourceIPs` into named `Esql.*`/`Esql.*` fields. It groups `ResponseComplete`-stage `list`/`get` calls on
 35orientation-related resources by `Esql.username`, `Esql.user_agent`, and `azure.resource.id` (the AKS cluster),
 36and alerts when the distinct `Esql.resource` cardinality for that identity/client fingerprint is 3 or greater,
 37mirroring the Kubernetes ES|QL multi-resource discovery rule. Scoping by cluster keeps same-named identities (for
 38example the default `masterclient` admin or a GitOps service account deployed to several clusters) from pooling
 39across clusters and reaching the threshold on unrelated activity. Source IPs are not part of the group key because
 40AKS `sourceIPs` carries the client IP alongside a control-plane proxy address (`172.31.x.x`) that varies across proxy
 41replicas within a single session; they are instead collected into `Esql.source_ip_values` for triage. Known
 42platform/control-plane identities (`aksService`, `hcpService`, `acsService`, `readinessChecker`, node and
 43`kube-system`/`elastic-system` service accounts) and automation-tool user agents (Azure Arc, Helm, Terraform,
 44`OpenAPI-Generator`) are excluded; `kubectl` is never excluded by user agent alone, since both admins and attackers
 45with a stolen token typically drive activity through it.
 46
 47### Possible investigation steps
 48
 49- Review `Esql.username`, `Esql.user_agent`, and `Esql.source_ip_values` for the acting identity and client
 50  fingerprint, confirm which cluster was targeted via `azure.resource.id`, bound the window with
 51  `Esql.earliest_timestamp`/`Esql.latest_timestamp`, and pivot into raw `kube-audit` logs for call ordering.
 52  Unexpected `kubectl` or custom user agents (for example `kubectl-recon`) from human-style identities, or the same
 53  identity spanning multiple user agents/source IPs, warrant closer review.
 54- Check `Esql.enumerated_resources`/`Esql.enumerated_namespaces` against a known job, GitOps sync, or CI/CD pipeline.
 55  Both cluster-scoped kinds (nodes, clusterroles, clusterrolebindings) and an all-namespaces list of a
 56  namespace-scoped kind (for example `kubectl get pods -A`) surface as `(cluster-scoped)`; the latter is a broader
 57  enumeration than a single-namespace query.
 58- Correlate with follow-on activity from the same identity/source IP: secret reads, `SelfSubjectAccessReview`
 59  checks, pod exec, RBAC changes, and cross-reference the public address in `Esql.source_ip_values` (ignoring
 60  the `172.31.x.x` AKS control-plane proxy entries) against expected CI/CD or administrator egress ranges.
 61
 62### False positive analysis
 63
 64- Controllers/operators (GitOps, backup, service-mesh, autoscalers) and CI/CD or bootstrap tooling can legitimately
 65  span several resource types, or provision namespaces/roles/service accounts together, in one window; validate the
 66  service account and correlate with change/deploy windows before treating as suspicious.
 67- Helm, Terraform, and Azure Arc user agents are pre-filtered as the dominant automation noise source, and
 68  `elastic-system` namespace activity is excluded from the cardinality count; add newly validated automation
 69  identities/user agents to the exclusion list to reduce further noise.
 70- User agents are client-supplied and spoofable, so the exclusions narrow noise but aren't strong identity
 71  verification; corroborate with the acting identity and RBAC before dismissing an alert.
 72
 73### Response and remediation
 74
 75- If unauthorized, revoke or rotate the implicated credentials/tokens, tighten RBAC bindings for the identity, and
 76  inspect for follow-on data access or privilege escalation.
 77- Collect the relevant `kube-audit` events and AKS diagnostic logs for the identity, source IP, and time window for
 78  incident response.
 79- If the identity is a compromised service account, rotate its token/certificate and review any workloads that mount it.
 80"""
 81references = ["https://microsoft.github.io/Threat-Matrix-for-Kubernetes/"]
 82risk_score = 47
 83rule_id = "86bdaa43-9268-436f-b944-d9c64a68def3"
 84setup = """The Azure Fleet integration collecting AKS diagnostic logs with the `kube-audit` category forwarded through Event Hub
 85into the `azure.platformlogs` data stream is required for this rule.
 86"""
 87severity = "medium"
 88tags = [
 89    "Domain: Cloud",
 90    "Domain: Kubernetes",
 91    "Data Source: Azure",
 92    "Data Source: Azure Platform Logs",
 93    "Data Source: Kubernetes",
 94    "Use Case: Threat Detection",
 95    "Tactic: Discovery",
 96    "Resources: Investigation Guide",
 97    "Rule Type: ESQL",
 98    "Platform: Azure",
 99    "Platform: Kubernetes",
100]
101timestamp_override = "event.ingested"
102type = "esql"
103
104query = '''
105from logs-azure.platformlogs-* metadata _id, _index, _version
106| where data_stream.dataset == "azure.platformlogs"
107    and event.action == "Microsoft.ContainerService/managedClusters/diagnosticLogs/Read"
108    and azure.platformlogs.category == "kube-audit"
109| eval Esql.stage = field_extract(azure.platformlogs.properties, "log.stage"),
110    Esql.verb = field_extract(azure.platformlogs.properties, "log.verb"),
111    Esql.resource = field_extract(azure.platformlogs.properties, "log.objectRef.resource"),
112    Esql.namespace = field_extract(azure.platformlogs.properties, "log.objectRef.namespace"),
113    Esql.username = field_extract(azure.platformlogs.properties, "log.user.username"),
114    Esql.user_agent = field_extract(azure.platformlogs.properties, "log.userAgent"),
115    Esql.source_ips = field_extract(azure.platformlogs.properties, "log.sourceIPs")
116| where Esql.stage == "ResponseComplete"
117    and Esql.verb in ("list", "get")
118    and Esql.resource in ("namespaces", "nodes", "pods", "roles", "clusterroles", "configmaps", "serviceaccounts", "rolebindings", "clusterrolebindings")
119    and Esql.username is not null
120    and (Esql.namespace is null or Esql.namespace != "elastic-system")
121    and not Esql.username rlike "system:node:.*|system:serviceaccount:kube-system:.*|system:serviceaccount:elastic-system:.*"
122    and not Esql.username in ("aksService", "hcpService", "readinessChecker", "acsService", "system:apiserver", "system:kube-controller-manager", "system:kube-scheduler")
123    and not Esql.user_agent rlike """(resourcesyncoperator|identityOperator|telegraf|clustermetadatasync|ConfigurationAgent|manager|extensionmanager)/.*\(linux/amd64\) kubernetes/\$Format|Helm/.*|.*Terraform.*|OpenAPI-Generator/.*"""
124| eval Esql.namespace = coalesce(Esql.namespace, "(cluster-scoped)")
125| stats
126    Esql.unique_resources = count_distinct(Esql.resource),
127    Esql.enumerated_resources = values(Esql.resource),
128    Esql.enumerated_namespaces = values(Esql.namespace),
129    Esql.source_ip_values = values(Esql.source_ips),
130    Esql.username_values = values(Esql.username),
131    Esql.event_count = count(*),
132    Esql.earliest_timestamp = min(@timestamp),
133    Esql.latest_timestamp = max(@timestamp)
134  by Esql.username, Esql.user_agent, azure.resource.id
135| where Esql.unique_resources >= 3
136| keep Esql.*, azure.resource.id
137'''
138
139
140[[rule.threat]]
141framework = "MITRE ATT&CK"
142[[rule.threat.technique]]
143id = "T1613"
144name = "Container and Resource Discovery"
145reference = "https://attack.mitre.org/techniques/T1613/"
146
147
148[rule.threat.tactic]
149id = "TA0007"
150name = "Discovery"
151reference = "https://attack.mitre.org/tactics/TA0007/"
152
153[rule.alert_suppression]
154group_by = ["Esql.username", "Esql.user_agent", "azure.resource.id"]
155missing_fields_strategy = "suppress"
156
157[rule.alert_suppression.duration]
158unit = "m"
159value = 30

Triage and analysis

Investigating Azure AKS Multi-Resource Setup Discovery

AKS kube-audit events are carried under the flattened azure.platformlogs.properties.log.* subtree, so this rule uses field_extract to pull stage, verb, objectRef.resource, objectRef.namespace, user.username, userAgent, and sourceIPs into named Esql.*/Esql.* fields. It groups ResponseComplete-stage list/get calls on orientation-related resources by Esql.username, Esql.user_agent, and azure.resource.id (the AKS cluster), and alerts when the distinct Esql.resource cardinality for that identity/client fingerprint is 3 or greater, mirroring the Kubernetes ES|QL multi-resource discovery rule. Scoping by cluster keeps same-named identities (for example the default masterclient admin or a GitOps service account deployed to several clusters) from pooling across clusters and reaching the threshold on unrelated activity. Source IPs are not part of the group key because AKS sourceIPs carries the client IP alongside a control-plane proxy address (172.31.x.x) that varies across proxy replicas within a single session; they are instead collected into Esql.source_ip_values for triage. Known platform/control-plane identities (aksService, hcpService, acsService, readinessChecker, node and kube-system/elastic-system service accounts) and automation-tool user agents (Azure Arc, Helm, Terraform, OpenAPI-Generator) are excluded; kubectl is never excluded by user agent alone, since both admins and attackers with a stolen token typically drive activity through it.

Possible investigation steps

  • Review Esql.username, Esql.user_agent, and Esql.source_ip_values for the acting identity and client fingerprint, confirm which cluster was targeted via azure.resource.id, bound the window with Esql.earliest_timestamp/Esql.latest_timestamp, and pivot into raw kube-audit logs for call ordering. Unexpected kubectl or custom user agents (for example kubectl-recon) from human-style identities, or the same identity spanning multiple user agents/source IPs, warrant closer review.
  • Check Esql.enumerated_resources/Esql.enumerated_namespaces against a known job, GitOps sync, or CI/CD pipeline. Both cluster-scoped kinds (nodes, clusterroles, clusterrolebindings) and an all-namespaces list of a namespace-scoped kind (for example kubectl get pods -A) surface as (cluster-scoped); the latter is a broader enumeration than a single-namespace query.
  • Correlate with follow-on activity from the same identity/source IP: secret reads, SelfSubjectAccessReview checks, pod exec, RBAC changes, and cross-reference the public address in Esql.source_ip_values (ignoring the 172.31.x.x AKS control-plane proxy entries) against expected CI/CD or administrator egress ranges.

False positive analysis

  • Controllers/operators (GitOps, backup, service-mesh, autoscalers) and CI/CD or bootstrap tooling can legitimately span several resource types, or provision namespaces/roles/service accounts together, in one window; validate the service account and correlate with change/deploy windows before treating as suspicious.
  • Helm, Terraform, and Azure Arc user agents are pre-filtered as the dominant automation noise source, and elastic-system namespace activity is excluded from the cardinality count; add newly validated automation identities/user agents to the exclusion list to reduce further noise.
  • User agents are client-supplied and spoofable, so the exclusions narrow noise but aren't strong identity verification; corroborate with the acting identity and RBAC before dismissing an alert.

Response and remediation

  • If unauthorized, revoke or rotate the implicated credentials/tokens, tighten RBAC bindings for the identity, and inspect for follow-on data access or privilege escalation.
  • Collect the relevant kube-audit events and AKS diagnostic logs for the identity, source IP, and time window for incident response.
  • If the identity is a compromised service account, rotate its token/certificate and review any workloads that mount it.

References

Related rules

to-top