Detection rules › Elastic

Azure AKS Certificate Signing Request Created or Approved

Status
production
Severity
medium
Time window
9m
Author
Elastic
Source
github.com/elastic/detection-rules

Detects an identity creating a client-authentication CertificateSigningRequest (signer kubernetes.io/kube-apiserver-client) or approving a CSR on AKS (Azure Kubernetes Service), excluding node bootstrap and platform controllers. Adversaries submit and self-approve a CSR against the kube-apiserver-client signer to mint a long-lived client certificate for an arbitrary subject (for example a Common Name in system:masters), giving durable authenticated access that survives token revocation. Coverage includes workload service accounts (system:serviceaccount:*), so a compromised in-cluster token forging a certificate is not excluded.

Known false positives

  • Node bootstrap, cert-manager, and platform controllers legitimately create and approve CSRs. Validate the requesting identity and the certificate subject, and add exclusions for verified automation.

MITRE ATT&CK coverage

TacticTechniques
Credential Access

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Rule body

[metadata]
creation_date = "2026/07/09"
integration = ["azure"]
maturity = "production"
updated_date = "2026/07/09"

[rule]
author = ["Elastic"]
description = """
Detects an identity creating a client-authentication CertificateSigningRequest (signer
kubernetes.io/kube-apiserver-client) or approving a CSR on AKS (Azure Kubernetes Service), excluding node bootstrap and
platform controllers. Adversaries submit and self-approve a CSR against the kube-apiserver-client signer to mint a
long-lived client certificate for an arbitrary subject (for example a Common Name in system:masters), giving durable
authenticated access that survives token revocation. Coverage includes workload service accounts
(system:serviceaccount:*), so a compromised in-cluster token forging a certificate is not excluded.
"""
false_positives = [
    """
    Node bootstrap, cert-manager, and platform controllers legitimately create and approve CSRs. Validate the requesting
    identity and the certificate subject, and add exclusions for verified automation.
    """,
]
from = "now-9m"
index = ["logs-azure.platformlogs-*"]
language = "kuery"
license = "Elastic License v2"
name = "Azure AKS Certificate Signing Request Created or Approved"
note = """## Triage and analysis

### Investigating Azure AKS Certificate Signing Request Created or Approved

AKS kube-audit events are carried under the flattened `azure.platformlogs.properties.log.*` subtree and share the ARM
operation `event.action: Microsoft.ContainerService/managedClusters/diagnosticLogs/Read`. A CSR created against the
`kubernetes.io/kube-apiserver-client` signer lets the requester choose the certificate's subject (Common Name and
organization/groups); once approved it mints a client certificate for an arbitrary identity that yields access not tied
to a token. The default `CertificateSubjectRestriction` admission controller blocks requests for the `system:masters`
group, so attackers commonly request a Common Name matching an existing privileged user (or another privileged group)
instead, making the requested subject the key thing to decode. Node and kubelet certificates use the
`kube-apiserver-client-kubelet` and `kubelet-serving` signers (whose subject is constrained to the node) and are out of
scope; cert-manager and application CSRs use their own signers.

### Possible investigation steps

- Identify the requesting identity in `azure.platformlogs.properties.log.user.username` (and its groups in
  `azure.platformlogs.properties.log.user.groups`) and whether it should submit or approve CSRs. A workload service
  account (`system:serviceaccount:<ns>:<sa>`) or `masterclient` (the local cluster-admin cert) is the higher-concern
  case.
- Confirm the signer in `azure.platformlogs.properties.log.requestObject.spec.signerName` and decode the base64 CSR in
  `azure.platformlogs.properties.log.requestObject.spec.request` to read the requested Common Name and organization
  (groups); a subject in `system:masters` or another privileged group is the escalation.
- Determine whether the same or a related identity approved the CSR (`verb:update`/`patch` on the `approval`
  subresource), which indicates self-approval, and inspect `azure.platformlogs.properties.log.userAgent`.
- Evaluate the source in `azure.platformlogs.properties.log.sourceIPs` and pivot on it for follow-on privileged API
  activity using the newly issued certificate.

### False positive analysis

- Node bootstrap (`kube-apiserver-client-kubelet` signer), kubelet-serving CSRs, and cert-manager/application CSRs
  (custom signers) are out of scope by design; the kube-controller-manager `certificate-controller` and the AKS
  `aksService` approver are excluded by identity.
- Manual CSR approval by an administrator, or an operator that legitimately mints client certificates, may surface;
  baseline those identities and exclude the specific validated account rather than re-broadening to all `system:*`,
  which would blind the rule to compromised workload service accounts.

### Response and remediation

- If unauthorized, deny or delete the CSR, revoke the issued certificate, and rotate the cluster CA if a privileged
  certificate was minted.
- Review the RBAC that allowed CSR creation and approval, and audit actions taken with the certificate.
- Collect kube-audit and identity artifacts per incident response procedures.
"""
references = [
    "https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/",
    "https://microsoft.github.io/Threat-Matrix-for-Kubernetes/",
    "https://github.com/inguardians/peirates",
    "https://raesene.github.io/blog/2022/12/21/Kubernetes-persistence-with-Tocan-and-Teisteanas/",
    "https://cloud.google.com/blog/topics/threat-intelligence/escalating-privileges-azure-kubernetes-services",
    "https://www.aquasec.com/blog/kubernetes-rbac-privilige-escalation/",
]
risk_score = 47
rule_id = "3f6c636a-1ca5-410c-bdad-f8b59a1f2b49"
setup = """The Azure Fleet integration collecting AKS diagnostic logs forwarded through Event Hub into the `azure.platformlogs`
data stream is required for this rule. Enable either the `kube-audit` or the `kube-audit-admin` log category (Microsoft
recommends `kube-audit-admin` alone to reduce volume, as it only drops read-only get/list events). CSR create and
approval are mutating operations recorded in both categories with the same `auditID`, so clusters that enable both
categories may generate two alerts per event.
"""
severity = "medium"
tags = [
    "Domain: Cloud",
    "Domain: Kubernetes",
    "Data Source: Azure",
    "Data Source: Azure Platform Logs",
    "Data Source: Kubernetes",
    "Use Case: Threat Detection",
    "Tactic: Credential Access",
    "Resources: Investigation Guide",
]
timestamp_override = "event.ingested"
type = "query"

query = '''
data_stream.dataset:azure.platformlogs and
  event.action:"Microsoft.ContainerService/managedClusters/diagnosticLogs/Read" and
  azure.platformlogs.category:("kube-audit" or "kube-audit-admin") and
  azure.platformlogs.properties.log.stage:"ResponseComplete" and
  azure.platformlogs.properties.log.objectRef.resource:"certificatesigningrequests" and
  azure.platformlogs.properties.log.responseStatus.code: "200" and
  azure.platformlogs.properties.log.requestObject.status.conditions.type: "Approved" and
  (
    (
      azure.platformlogs.properties.log.verb:"create" and
      azure.platformlogs.properties.log.requestObject.spec.signerName:"kubernetes.io/kube-apiserver-client"
    ) or (
      azure.platformlogs.properties.log.verb:("update" or "patch") and
      azure.platformlogs.properties.log.objectRef.subresource:"approval"
    )
  ) and
  not azure.platformlogs.properties.log.user.username:(
    system\:node\:* or system\:bootstrap\:* or "aksService" or "hcpService" or
    "readinessChecker" or system\:serviceaccount\:kube-system\:*
  )
'''


[[rule.threat]]
framework = "MITRE ATT&CK"
[[rule.threat.technique]]
id = "T1649"
name = "Steal or Forge Authentication Certificates"
reference = "https://attack.mitre.org/techniques/T1649/"


[rule.threat.tactic]
id = "TA0006"
name = "Credential Access"
reference = "https://attack.mitre.org/tactics/TA0006/"

[rule.investigation_fields]
field_names = [
    "@timestamp",
    "event.action",
    "azure.platformlogs.category",
    "azure.platformlogs.properties.log.verb",
    "azure.platformlogs.properties.log.user.username",
    "azure.platformlogs.properties.log.user.groups",
    "azure.platformlogs.properties.log.userAgent",
    "azure.platformlogs.properties.log.sourceIPs",
    "azure.platformlogs.properties.log.objectRef.resource",
    "azure.platformlogs.properties.log.objectRef.name",
    "azure.platformlogs.properties.log.objectRef.subresource",
    "azure.platformlogs.properties.log.requestObject.spec.signerName",
    "azure.platformlogs.properties.log.requestObject.spec.request",
    "azure.platformlogs.properties.log.responseStatus.code",
]

Stages and Predicates

Stage 1: query

data_stream.dataset:azure.platformlogs and
  event.action:"Microsoft.ContainerService/managedClusters/diagnosticLogs/Read" and
  azure.platformlogs.category:("kube-audit" or "kube-audit-admin") and
  azure.platformlogs.properties.log.stage:"ResponseComplete" and
  azure.platformlogs.properties.log.objectRef.resource:"certificatesigningrequests" and
  azure.platformlogs.properties.log.responseStatus.code: "200" and
  azure.platformlogs.properties.log.requestObject.status.conditions.type: "Approved" and
  (
    (
      azure.platformlogs.properties.log.verb:"create" and
      azure.platformlogs.properties.log.requestObject.spec.signerName:"kubernetes.io/kube-apiserver-client"
    ) or (
      azure.platformlogs.properties.log.verb:("update" or "patch") and
      azure.platformlogs.properties.log.objectRef.subresource:"approval"
    )
  ) and
  not azure.platformlogs.properties.log.user.username:(
    system\:node\:* or system\:bootstrap\:* or "aksService" or "hcpService" or
    "readinessChecker" or system\:serviceaccount\:kube-system\:*
  )

Exclusions

The rule actively suppresses these predicates.

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
azure.platformlogs.categoryin
  • kube-audit
  • kube-audit-admin
field:"azure.platformlogs.category" kind:in
azure.platformlogs.properties.log.objectRef.resourceeq
  • certificatesigningrequests
field:"azure.platformlogs.properties.log.objectRef.resource" kind:eq value:"certificatesigningrequests"
azure.platformlogs.properties.log.objectRef.subresourceeq
  • approval
field:"azure.platformlogs.properties.log.objectRef.subresource" kind:eq value:"approval"
azure.platformlogs.properties.log.requestObject.spec.signerNameeq
  • kubernetes.io/kube-apiserver-client
field:"azure.platformlogs.properties.log.requestObject.spec.signerName" kind:eq value:"kubernetes.io/kube-apiserver-client"
azure.platformlogs.properties.log.requestObject.status.conditions.typeeq
  • Approved
field:"azure.platformlogs.properties.log.requestObject.status.conditions.type" kind:eq value:"Approved"
azure.platformlogs.properties.log.responseStatus.codeeq
  • 200
field:"azure.platformlogs.properties.log.responseStatus.code" kind:eq value:"200"
azure.platformlogs.properties.log.stageeq
  • ResponseComplete
field:"azure.platformlogs.properties.log.stage" kind:eq value:"ResponseComplete"
azure.platformlogs.properties.log.verbeq
  • create
field:"azure.platformlogs.properties.log.verb" kind:eq value:"create"
azure.platformlogs.properties.log.verbin
  • patch
  • update
field:"azure.platformlogs.properties.log.verb" kind:in
data_stream.dataseteq
  • azure.platformlogs
field:"data_stream.dataset" kind:eq value:"azure.platformlogs"
event.actioneq
  • Microsoft.ContainerService/managedClusters/diagnosticLogs/Read
field:"EventType" kind:eq value:"Microsoft.ContainerService/managedClusters/diagnosticLogs/Read"