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, andEsql.source_ip_valuesfor the acting identity and client fingerprint, confirm which cluster was targeted viaazure.resource.id, bound the window withEsql.earliest_timestamp/Esql.latest_timestamp, and pivot into rawkube-auditlogs for call ordering. Unexpectedkubectlor custom user agents (for examplekubectl-recon) from human-style identities, or the same identity spanning multiple user agents/source IPs, warrant closer review. - Check
Esql.enumerated_resources/Esql.enumerated_namespacesagainst 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 examplekubectl 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,
SelfSubjectAccessReviewchecks, pod exec, RBAC changes, and cross-reference the public address inEsql.source_ip_values(ignoring the172.31.x.xAKS 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-systemnamespace 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-auditevents 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
- Azure AKS Potential API Enumeration by User
- Azure AKS Suspicious Self-Subject Review by Service Account or Node Identity
- Azure AKS API Server Proxying Request to Kubelet
- Azure AKS Attempted User Exec into Pod
- Azure AKS Certificate Signing Request Created or Approved