GKE Multi-Resource Discovery

Adversaries who land credentials in a GKE cluster—or abuse an over-privileged token, often map the environment before exfiltration or privilege escalation. A practical first pass is to learn where workloads run, how the cluster is partitioned, and what RBAC exists at namespace vs cluster scope. Rapid get/list traffic across many distinct API resource kinds that answer those questions (namespaces, workloads, roles, cluster-wide roles) is a common setup and orientation pattern for both interactive attackers and automated recon scripts. This rule highlights that cross-resource burst from a single client fingerprint within a one-minute bucket when both cluster-layout and RBAC resource kinds are touched, so analysts can separate routine automation from potential discovery ahead of follow-on actions.

Elastic rule (View on GitHub)

  1[metadata]
  2creation_date = "2026/07/21"
  3integration = ["gcp"]
  4maturity = "production"
  5updated_date = "2026/09/18"
  6
  7[rule]
  8author = ["Elastic"]
  9description = """
 10Adversaries who land credentials in a GKE cluster—or abuse an over-privileged token, often map the environment before
 11exfiltration or privilege escalation. A practical first pass is to learn where workloads run, how the cluster is
 12partitioned, and what RBAC exists at namespace vs cluster scope. Rapid get/list traffic across many distinct API
 13resource kinds that answer those questions (namespaces, workloads, roles, cluster-wide roles) is a common setup and
 14orientation pattern for both interactive attackers and automated recon scripts. This rule highlights that
 15cross-resource burst from a single client fingerprint within a one-minute bucket when both cluster-layout and RBAC
 16resource kinds are touched, so analysts can separate routine automation from potential discovery ahead of follow-on
 17actions.
 18"""
 19false_positives = [
 20    """
 21    Platform operators, installers, or runbooks that reconcile RBAC and workload state may span these resource types
 22    in a short window; tune by identity, source IP, or user agent when documented.
 23    """,
 24    """
 25    GitOps controllers and cluster scanners can still match if not covered by built-in exclusions; baseline approved
 26    service accounts after review.
 27    """,
 28]
 29from = "now-6m"
 30interval = "5m"
 31language = "esql"
 32license = "Elastic License v2"
 33name = "GKE Multi-Resource Discovery"
 34note = """## Triage and analysis
 35
 36### Investigating GKE Multi-Resource Discovery
 37
 38The rule groups GKE audit get/list events on namespaces, nodes, pods, configmaps, serviceaccounts, roles,
 39rolebindings, clusterroles, and clusterrolebindings into one-minute windows per `client.user.email`, `source.ip`,
 40and `user_agent.original`. It alerts when five or more distinct resource kinds appear and the burst includes both
 41cluster-layout kinds (namespaces, pods, or nodes) and RBAC kinds. Allowed and denied authorizations are both
 42included: failures still signal probing.
 43
 44### Possible investigation steps
 45
 46- Review `Esql.enumerated_resources`, `Esql.enumerated_namespaces`, and `Esql.enumerated_resource_names` for
 47  ordering and targeted APIs.
 48- Confirm whether `source.ip` and `user_agent.original` match expected admin or automation clients.
 49- Correlate with follow-on secret reads, RoleBinding changes, pod exec, or unusual user agents from the same actor.
 50
 51### False positive analysis
 52
 53- Documented platform sync jobs that read layout and RBAC together; exclude known service accounts after validation.
 54- Upgrade or install windows that briefly query many resource kinds; correlate with change records.
 55
 56### Response and remediation
 57
 58- If malicious, revoke or rotate the implicated credentials, tighten RBAC, and inspect for data access or persistence
 59  established after the burst.
 60"""
 61setup = "The GCP Fleet integration with GKE audit logs enabled is required to be compatible with this rule."
 62references = [
 63    "https://microsoft.github.io/Threat-Matrix-for-Kubernetes/",
 64]
 65risk_score = 47
 66rule_id = "0302d3b2-1892-4448-a805-980aa4a36ba3"
 67severity = "medium"
 68tags = [
 69    "Domain: Cloud",
 70    "Domain: Kubernetes",
 71    "Data Source: GCP",
 72    "Data Source: Google Cloud Platform",
 73    "Use Case: Threat Detection",
 74    "Tactic: Discovery",
 75    "Resources: Investigation Guide",
 76    "Noise: Medium",
 77    "Performance: Fast",
 78    "Rule Type: ES|QL",
 79    "Platform: GCP",
 80    "Domain: Containers",
 81    "Platform: Kubernetes",
 82]
 83timestamp_override = "event.ingested"
 84type = "esql"
 85
 86query = '''
 87from logs-gcp.audit-* metadata _id, _index, _version
 88| where data_stream.dataset == "gcp.audit"
 89    and service.name == "k8s.io"
 90    and event.action in (
 91      "io.k8s.core.v1.namespaces.get",
 92      "io.k8s.core.v1.namespaces.list",
 93      "io.k8s.core.v1.nodes.get",
 94      "io.k8s.core.v1.nodes.list",
 95      "io.k8s.core.v1.pods.get",
 96      "io.k8s.core.v1.pods.list",
 97      "io.k8s.core.v1.configmaps.get",
 98      "io.k8s.core.v1.configmaps.list",
 99      "io.k8s.core.v1.serviceaccounts.get",
100      "io.k8s.core.v1.serviceaccounts.list",
101      "io.k8s.authorization.rbac.v1.roles.get",
102      "io.k8s.authorization.rbac.v1.roles.list",
103      "io.k8s.authorization.rbac.v1.rolebindings.get",
104      "io.k8s.authorization.rbac.v1.rolebindings.list",
105      "io.k8s.authorization.rbac.v1.clusterroles.get",
106      "io.k8s.authorization.rbac.v1.clusterroles.list",
107      "io.k8s.authorization.rbac.v1.clusterrolebindings.get",
108      "io.k8s.authorization.rbac.v1.clusterrolebindings.list"
109    )
110    and source.ip is not null
111    and client.user.email is not null
112    and not to_string(source.ip) in ("127.0.0.1", "::1")
113    and not client.user.email like "system:kube-*"
114    and not client.user.email like "system:gke-*"
115    and not client.user.email like "system:node:*"
116    and not client.user.email like "system:serviceaccount:kube-system:*"
117    and not client.user.email like "system:serviceaccount:gke-managed*"
118    and not client.user.email in (
119      "system:apiserver",
120      "system:addon-manager",
121      "system:kubestore-collector",
122      "gcp:kube-bootstrap",
123      "system:serviceaccount:security:trivy-operator"
124    )
125    and not client.user.email like "system:serviceaccount:flux-system:*"
126    and not client.user.email like "system:serviceaccount:argocd:*"
127    and not client.user.email like "system:serviceaccount:argocd-system:*"
128    and not client.user.email like "system:serviceaccount:cattle-turtles-system:*"
129    and not client.user.email like "system:serviceaccount:*:palette-manager"
130| eval Esql.time_interval = date_trunc(1 minute, @timestamp),
131  Esql.resource_kind = case(
132    event.action in ("io.k8s.core.v1.namespaces.get", "io.k8s.core.v1.namespaces.list"), "namespaces",
133    event.action in ("io.k8s.core.v1.nodes.get", "io.k8s.core.v1.nodes.list"), "nodes",
134    event.action in ("io.k8s.core.v1.pods.get", "io.k8s.core.v1.pods.list"), "pods",
135    event.action in ("io.k8s.core.v1.configmaps.get", "io.k8s.core.v1.configmaps.list"), "configmaps",
136    event.action in ("io.k8s.core.v1.serviceaccounts.get", "io.k8s.core.v1.serviceaccounts.list"), "serviceaccounts",
137    event.action in ("io.k8s.authorization.rbac.v1.roles.get", "io.k8s.authorization.rbac.v1.roles.list"), "roles",
138    event.action in ("io.k8s.authorization.rbac.v1.rolebindings.get", "io.k8s.authorization.rbac.v1.rolebindings.list"), "rolebindings",
139    event.action in ("io.k8s.authorization.rbac.v1.clusterroles.get", "io.k8s.authorization.rbac.v1.clusterroles.list"), "clusterroles",
140    event.action in ("io.k8s.authorization.rbac.v1.clusterrolebindings.get", "io.k8s.authorization.rbac.v1.clusterrolebindings.list"), "clusterrolebindings",
141    null
142  ),
143  Esql.is_rbac = case(
144    event.action in (
145      "io.k8s.authorization.rbac.v1.roles.get",
146      "io.k8s.authorization.rbac.v1.roles.list",
147      "io.k8s.authorization.rbac.v1.rolebindings.get",
148      "io.k8s.authorization.rbac.v1.rolebindings.list",
149      "io.k8s.authorization.rbac.v1.clusterroles.get",
150      "io.k8s.authorization.rbac.v1.clusterroles.list",
151      "io.k8s.authorization.rbac.v1.clusterrolebindings.get",
152      "io.k8s.authorization.rbac.v1.clusterrolebindings.list"
153    ),
154    1,
155    0
156  ),
157  Esql.is_layout = case(
158    event.action in (
159      "io.k8s.core.v1.namespaces.get",
160      "io.k8s.core.v1.namespaces.list",
161      "io.k8s.core.v1.pods.get",
162      "io.k8s.core.v1.pods.list",
163      "io.k8s.core.v1.nodes.get",
164      "io.k8s.core.v1.nodes.list"
165    ),
166    1,
167    0
168  )
169| stats
170    Esql.unique_resources = count_distinct(Esql.resource_kind),
171    Esql.rbac_event_count = sum(Esql.is_rbac),
172    Esql.layout_event_count = sum(Esql.is_layout),
173    Esql.enumerated_resources = values(Esql.resource_kind),
174    Esql.enumerated_namespaces = values(orchestrator.namespace),
175    Esql.enumerated_resource_names = values(gcp.audit.resource_name),
176    Esql.event_outcome_values = values(event.outcome)
177  by client.user.email, source.ip, user_agent.original, Esql.time_interval
178| where Esql.unique_resources >= 5
179    and Esql.rbac_event_count > 0
180    and Esql.layout_event_count > 0
181| keep Esql.*, client.user.email, source.ip, user_agent.original
182'''
183
184[rule.alert_suppression]
185group_by = ["client.user.email", "source.ip"]
186duration = {value = 30, unit = "m"}
187missing_fields_strategy = "suppress"
188
189[[rule.threat]]
190framework = "MITRE ATT&CK"
191
192[[rule.threat.technique]]
193id = "T1613"
194name = "Container and Resource Discovery"
195reference = "https://attack.mitre.org/techniques/T1613/"
196
197[rule.threat.tactic]
198id = "TA0007"
199name = "Discovery"
200reference = "https://attack.mitre.org/tactics/TA0007/"

Triage and analysis

Investigating GKE Multi-Resource Discovery

The rule groups GKE audit get/list events on namespaces, nodes, pods, configmaps, serviceaccounts, roles, rolebindings, clusterroles, and clusterrolebindings into one-minute windows per client.user.email, source.ip, and user_agent.original. It alerts when five or more distinct resource kinds appear and the burst includes both cluster-layout kinds (namespaces, pods, or nodes) and RBAC kinds. Allowed and denied authorizations are both included: failures still signal probing.

Possible investigation steps

  • Review Esql.enumerated_resources, Esql.enumerated_namespaces, and Esql.enumerated_resource_names for ordering and targeted APIs.
  • Confirm whether source.ip and user_agent.original match expected admin or automation clients.
  • Correlate with follow-on secret reads, RoleBinding changes, pod exec, or unusual user agents from the same actor.

False positive analysis

  • Documented platform sync jobs that read layout and RBAC together; exclude known service accounts after validation.
  • Upgrade or install windows that briefly query many resource kinds; correlate with change records.

Response and remediation

  • If malicious, revoke or rotate the implicated credentials, tighten RBAC, and inspect for data access or persistence established after the burst.

References

Related rules

to-top