GKE Certificate Signing Request Self-Approved

Detects when the same non-system GKE identity creates a CertificateSigningRequest (CSR) and then approves that same CSR within five minutes, consistent with self-approval abuse. Attackers who gain CSR create and approval RBAC can submit a certificate request and approve it themselves to obtain a long-lived client certificate without involving cluster operators, a pattern documented in Kubernetes persistence research and adversary emulation.

Elastic rule (View on GitHub)

  1[metadata]
  2creation_date = "2026/07/10"
  3integration = ["gcp"]
  4maturity = "production"
  5updated_date = "2026/09/18"
  6
  7[rule]
  8author = ["Elastic"]
  9description = """
 10Detects when the same non-system GKE identity creates a CertificateSigningRequest (CSR) and then approves that same CSR
 11within five minutes, consistent with self-approval abuse. Attackers who gain CSR create and approval RBAC
 12can submit a certificate request and approve it themselves to obtain a long-lived client certificate without involving
 13cluster operators, a pattern documented in Kubernetes persistence research and adversary emulation.
 14"""
 15false_positives = [
 16    """
 17    Automation that both submits and approves CSRs in one workflow may trigger this rule. Baseline cert-manager or
 18    internal PKI pipelines and tune exclusions for known service accounts.
 19    """,
 20]
 21from = "now-11m"
 22language = "esql"
 23license = "Elastic License v2"
 24name = "GKE Certificate Signing Request Self-Approved"
 25note = """## Triage and analysis
 26
 27### Investigating GKE Certificate Signing Request Self-Approved
 28
 29This rule groups CSR create and approval events by `client.user.email` and normalized CSR name (`Esql.csr_name`).
 30GKE logs approval on subresource paths (`.../csr-name/approval`), so the query strips `/approval` suffixes before
 31correlating. An alert means the same identity both submitted and approved the same CSR within five minutes, a strong
 32indicator of manual self-approval rather than normal `system:gcp-controller-manager` auto-approval of node
 33certificates.
 34
 35### Possible investigation steps
 36
 37- Review `Esql.event_action_values` for the sequence of create followed by `approval.update`.
 38- Inspect `gcp.audit.request.spec.signerName` and decode `gcp.audit.request.spec.request` on create events for the
 39  requested identity.
 40- Validate whether the actor should hold both CSR create and approval permissions.
 41- Hunt for subsequent API activity authenticated as the minted certificate identity.
 42
 43### False positive analysis
 44
 45- cert-manager or internal PKI automation that creates and approves CSRs under the same service account in one workflow.
 46- GitOps or bootstrap tooling that submits and signs CSRs programmatically. Baseline known automation and tune exclusions
 47  for those principals.
 48- Two unrelated CSR events from the same user within five minutes should not match because the query requires at least
 49  one create and one approval-class action on the same normalized CSR name.
 50
 51### Related rules
 52
 53- GKE Certificate Signing Request API Client Signer Requested - 1e344fba-a2f7-462b-aaec-d6c8f80d5a28
 54- GKE Certificate Signing Request Privileged Identity Requested - 4159bec9-76ad-4cdc-a797-4a8572073bbe
 55- GKE Client Certificate Signing Request Created or Approved - ec67ab57-945a-4edb-84f8-1d7a51f46544
 56
 57### Response and remediation
 58
 59- Revoke or deny the CSR, remove approval RBAC from untrusted principals, and rotate cluster signing credentials if
 60  abuse is confirmed.
 61
 62"""
 63setup = "The GCP Fleet integration with GKE audit logs enabled is required to be compatible with this rule."
 64references = [
 65    "https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/",
 66    "https://kubernetes.io/docs/concepts/security/rbac-good-practices/",
 67    "https://stratus-red-team.cloud/attack-techniques/kubernetes/k8s.persistence.create-client-certificate/",
 68    "https://raesene.github.io/blog/2022/12/21/Kubernetes-persistence-with-Tocan-and-Teisteanas/",
 69    "https://www.aquasec.com/blog/kubernetes-rbac-privilige-escalation/",
 70]
 71risk_score = 73
 72rule_id = "e155e658-3dcd-4d27-a4e5-1d8da6704b0e"
 73severity = "high"
 74tags = [
 75    "Domain: Cloud",
 76    "Domain: Kubernetes",
 77    "Data Source: GCP",
 78    "Data Source: Google Cloud Platform",
 79    "Data Source: GCP Audit Logs",
 80    "Use Case: Threat Detection",
 81    "Tactic: Persistence",
 82    "Tactic: Privilege Escalation",
 83    "Resources: Investigation Guide",
 84    "Noise: Unknown",
 85    "Performance: Fast",
 86    "Rule Type: ES|QL",
 87    "Platform: GCP",
 88    "Domain: Containers",
 89    "Platform: Kubernetes",
 90]
 91timestamp_override = "event.ingested"
 92type = "esql"
 93
 94query = '''
 95from logs-gcp.audit-* metadata _id, _index, _version
 96| where data_stream.dataset == "gcp.audit"
 97    and service.name == "k8s.io"
 98    and event.outcome == "success"
 99    and event.action in (
100      "io.k8s.certificates.v1.certificatesigningrequests.create",
101      "io.k8s.certificates.v1.certificatesigningrequests.approval.update"
102    )
103    and client.user.email is not null
104    and gcp.audit.resource_name is not null
105    and not client.user.email in (
106      "system:gcp-controller-manager",
107      "system:kube-controller-manager",
108      "system:serviceaccount:kube-system:certificate-controller",
109      "kubelet-bootstrap",
110      "kubelet-nodepool-bootstrap"
111    )
112    and not client.user.email like "system:node:*"
113| eval Esql.csr_name = replace(gcp.audit.resource_name, "/approval", "")
114| stats
115    Esql.create_count = count(*) where event.action == "io.k8s.certificates.v1.certificatesigningrequests.create",
116    Esql.approval_count = count(*) where event.action == "io.k8s.certificates.v1.certificatesigningrequests.approval.update",
117    Esql.event_action_values = values(event.action),
118    Esql.timestamp_first_seen = min(@timestamp),
119    Esql.timestamp_last_seen = max(@timestamp),
120    Esql.source_ip_values = values(source.ip),
121    Esql.user_agent_original_values = values(user_agent.original),
122    Esql.data_stream_namespace_values = values(data_stream.namespace)
123  by client.user.email, Esql.csr_name
124| where Esql.create_count >= 1
125    and Esql.approval_count >= 1
126    and date_diff("seconds", Esql.timestamp_first_seen, Esql.timestamp_last_seen) <= 300
127| keep client.user.email, Esql.*
128'''
129
130[[rule.threat]]
131framework = "MITRE ATT&CK"
132
133[[rule.threat.technique]]
134id = "T1098"
135name = "Account Manipulation"
136reference = "https://attack.mitre.org/techniques/T1098/"
137
138[[rule.threat.technique.subtechnique]]
139id = "T1098.006"
140name = "Additional Container Cluster Roles"
141reference = "https://attack.mitre.org/techniques/T1098/006/"
142
143[rule.threat.tactic]
144id = "TA0003"
145name = "Persistence"
146reference = "https://attack.mitre.org/tactics/TA0003/"
147
148[[rule.threat]]
149framework = "MITRE ATT&CK"
150
151[[rule.threat.technique]]
152id = "T1098"
153name = "Account Manipulation"
154reference = "https://attack.mitre.org/techniques/T1098/"
155
156[[rule.threat.technique.subtechnique]]
157id = "T1098.006"
158name = "Additional Container Cluster Roles"
159reference = "https://attack.mitre.org/techniques/T1098/006/"
160
161[rule.threat.tactic]
162id = "TA0004"
163name = "Privilege Escalation"
164reference = "https://attack.mitre.org/tactics/TA0004/"
165
166[rule.investigation_fields]
167field_names = [
168    "client.user.email",
169    "Esql.csr_name",
170    "Esql.create_count",
171    "Esql.approval_count",
172    "Esql.event_action_values",
173    "Esql.timestamp_first_seen",
174    "Esql.timestamp_last_seen",
175    "Esql.source_ip_values",
176    "Esql.user_agent_original_values",
177    "Esql.data_stream_namespace_values",
178]

Triage and analysis

Investigating GKE Certificate Signing Request Self-Approved

This rule groups CSR create and approval events by client.user.email and normalized CSR name (Esql.csr_name). GKE logs approval on subresource paths (.../csr-name/approval), so the query strips /approval suffixes before correlating. An alert means the same identity both submitted and approved the same CSR within five minutes, a strong indicator of manual self-approval rather than normal system:gcp-controller-manager auto-approval of node certificates.

Possible investigation steps

  • Review Esql.event_action_values for the sequence of create followed by approval.update.
  • Inspect gcp.audit.request.spec.signerName and decode gcp.audit.request.spec.request on create events for the requested identity.
  • Validate whether the actor should hold both CSR create and approval permissions.
  • Hunt for subsequent API activity authenticated as the minted certificate identity.

False positive analysis

  • cert-manager or internal PKI automation that creates and approves CSRs under the same service account in one workflow.
  • GitOps or bootstrap tooling that submits and signs CSRs programmatically. Baseline known automation and tune exclusions for those principals.
  • Two unrelated CSR events from the same user within five minutes should not match because the query requires at least one create and one approval-class action on the same normalized CSR name.
  • GKE Certificate Signing Request API Client Signer Requested - 1e344fba-a2f7-462b-aaec-d6c8f80d5a28
  • GKE Certificate Signing Request Privileged Identity Requested - 4159bec9-76ad-4cdc-a797-4a8572073bbe
  • GKE Client Certificate Signing Request Created or Approved - ec67ab57-945a-4edb-84f8-1d7a51f46544

Response and remediation

  • Revoke or deny the CSR, remove approval RBAC from untrusted principals, and rotate cluster signing credentials if abuse is confirmed.

References

Related rules

to-top