Techniques
Sample rules
GKE Multi-Resource Discovery
- source: elastic
- technicques:
- T1613
Description
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.
Detection logic
from logs-gcp.audit-* metadata _id, _index, _version
| where data_stream.dataset == "gcp.audit"
and service.name == "k8s.io"
and event.action in (
"io.k8s.core.v1.namespaces.get",
"io.k8s.core.v1.namespaces.list",
"io.k8s.core.v1.nodes.get",
"io.k8s.core.v1.nodes.list",
"io.k8s.core.v1.pods.get",
"io.k8s.core.v1.pods.list",
"io.k8s.core.v1.configmaps.get",
"io.k8s.core.v1.configmaps.list",
"io.k8s.core.v1.serviceaccounts.get",
"io.k8s.core.v1.serviceaccounts.list",
"io.k8s.authorization.rbac.v1.roles.get",
"io.k8s.authorization.rbac.v1.roles.list",
"io.k8s.authorization.rbac.v1.rolebindings.get",
"io.k8s.authorization.rbac.v1.rolebindings.list",
"io.k8s.authorization.rbac.v1.clusterroles.get",
"io.k8s.authorization.rbac.v1.clusterroles.list",
"io.k8s.authorization.rbac.v1.clusterrolebindings.get",
"io.k8s.authorization.rbac.v1.clusterrolebindings.list"
)
and source.ip is not null
and client.user.email is not null
and not to_string(source.ip) in ("127.0.0.1", "::1")
and not client.user.email like "system:kube-*"
and not client.user.email like "system:gke-*"
and not client.user.email like "system:node:*"
and not client.user.email like "system:serviceaccount:kube-system:*"
and not client.user.email like "system:serviceaccount:gke-managed*"
and not client.user.email in (
"system:apiserver",
"system:addon-manager",
"system:kubestore-collector",
"gcp:kube-bootstrap",
"system:serviceaccount:security:trivy-operator"
)
and not client.user.email like "system:serviceaccount:flux-system:*"
and not client.user.email like "system:serviceaccount:argocd:*"
and not client.user.email like "system:serviceaccount:argocd-system:*"
and not client.user.email like "system:serviceaccount:cattle-turtles-system:*"
and not client.user.email like "system:serviceaccount:*:palette-manager"
| eval Esql.time_interval = date_trunc(1 minute, @timestamp),
Esql.resource_kind = case(
event.action in ("io.k8s.core.v1.namespaces.get", "io.k8s.core.v1.namespaces.list"), "namespaces",
event.action in ("io.k8s.core.v1.nodes.get", "io.k8s.core.v1.nodes.list"), "nodes",
event.action in ("io.k8s.core.v1.pods.get", "io.k8s.core.v1.pods.list"), "pods",
event.action in ("io.k8s.core.v1.configmaps.get", "io.k8s.core.v1.configmaps.list"), "configmaps",
event.action in ("io.k8s.core.v1.serviceaccounts.get", "io.k8s.core.v1.serviceaccounts.list"), "serviceaccounts",
event.action in ("io.k8s.authorization.rbac.v1.roles.get", "io.k8s.authorization.rbac.v1.roles.list"), "roles",
event.action in ("io.k8s.authorization.rbac.v1.rolebindings.get", "io.k8s.authorization.rbac.v1.rolebindings.list"), "rolebindings",
event.action in ("io.k8s.authorization.rbac.v1.clusterroles.get", "io.k8s.authorization.rbac.v1.clusterroles.list"), "clusterroles",
event.action in ("io.k8s.authorization.rbac.v1.clusterrolebindings.get", "io.k8s.authorization.rbac.v1.clusterrolebindings.list"), "clusterrolebindings",
null
),
Esql.is_rbac = case(
event.action in (
"io.k8s.authorization.rbac.v1.roles.get",
"io.k8s.authorization.rbac.v1.roles.list",
"io.k8s.authorization.rbac.v1.rolebindings.get",
"io.k8s.authorization.rbac.v1.rolebindings.list",
"io.k8s.authorization.rbac.v1.clusterroles.get",
"io.k8s.authorization.rbac.v1.clusterroles.list",
"io.k8s.authorization.rbac.v1.clusterrolebindings.get",
"io.k8s.authorization.rbac.v1.clusterrolebindings.list"
),
1,
0
),
Esql.is_layout = case(
event.action in (
"io.k8s.core.v1.namespaces.get",
"io.k8s.core.v1.namespaces.list",
"io.k8s.core.v1.pods.get",
"io.k8s.core.v1.pods.list",
"io.k8s.core.v1.nodes.get",
"io.k8s.core.v1.nodes.list"
),
1,
0
)
| stats
Esql.unique_resources = count_distinct(Esql.resource_kind),
Esql.rbac_event_count = sum(Esql.is_rbac),
Esql.layout_event_count = sum(Esql.is_layout),
Esql.enumerated_resources = values(Esql.resource_kind),
Esql.enumerated_namespaces = values(orchestrator.namespace),
Esql.enumerated_resource_names = values(gcp.audit.resource_name),
Esql.event_outcome_values = values(event.outcome)
by client.user.email, source.ip, user_agent.original, Esql.time_interval
| where Esql.unique_resources >= 5
and Esql.rbac_event_count > 0
and Esql.layout_event_count > 0
| keep Esql.*, client.user.email, source.ip, user_agent.original