Detection rules › Elastic
Kubernetes Secret or ConfigMap Access via Azure Arc Proxy
Detects when secrets or configmaps are accessed, created, modified, or deleted in a Kubernetes cluster by the Azure Arc AAD proxy service account. When operations are routed through the Azure Arc Cluster Connect proxy, the Kubernetes audit log records the acting user as system:serviceaccount:azure-arc:azure-arc-kube-aad-proxy-sa with the actual caller identity in the impersonatedUser field. This pattern indicates that someone is accessing the cluster through the Azure ARM API rather than directly via kubectl against the API server. While legitimate for Arc-managed workflows, adversaries with stolen service principal credentials can abuse Arc Cluster Connect to read, exfiltrate, or modify secrets and configmaps while appearing as the Arc proxy service account in K8s audit logs.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Credential Access | T1552.007 Unsecured Credentials: Container API |
| Collection | T1213 Data from Information Repositories, T1530 Data from Cloud Storage |
| Impact | T1565.001 Data Manipulation: Stored Data Manipulation |
Event coverage
| Provider | Event | Title |
|---|---|---|
| Kubernetes-configmaps | get-configmaps | get configmaps |
| Kubernetes-configmaps | list-configmaps | list configmaps |
| Kubernetes-configmaps | create-configmaps | create configmaps |
| Kubernetes-configmaps | update-configmaps | update configmaps |
| Kubernetes-configmaps | patch-configmaps | patch configmaps |
| Kubernetes-configmaps | delete-configmaps | delete configmaps |
| Kubernetes-secrets | get-secrets | get secrets |
| Kubernetes-secrets | list-secrets | list secrets |
| Kubernetes-secrets | create-secrets | create secrets |
| Kubernetes-secrets | update-secrets | update secrets |
| Kubernetes-secrets | patch-secrets | patch secrets |
| Kubernetes-secrets | delete-secrets | delete secrets |
Rules detecting the same action
Other rules on this platform that filter on the same API call or operation.
- EKS Authentication Configuration Modified (Elastic)
- Kubernetes Abuse of Secret by Unusual Location (Splunk)
- Kubernetes Abuse of Secret by Unusual User Agent (Splunk)
- Kubernetes Abuse of Secret by Unusual User Group (Splunk)
- Kubernetes Abuse of Secret by Unusual User Name (Splunk)
- Kubernetes CoreDNS or Kube-DNS Configuration Modified (Elastic)
- Kubernetes Secret Access via Unusual User Agent (Elastic)
- Kubernetes Secrets Enumeration (Sigma)
Rule body elastic
[metadata]
creation_date = "2026/03/10"
integration = ["kubernetes"]
maturity = "production"
updated_date = "2026/03/24"
[rule]
author = ["Elastic"]
description = """
Detects when secrets or configmaps are accessed, created, modified, or deleted in a Kubernetes cluster by the Azure Arc
AAD proxy service account. When operations are routed through the Azure Arc Cluster Connect proxy, the Kubernetes audit
log records the acting user as `system:serviceaccount:azure-arc:azure-arc-kube-aad-proxy-sa` with the actual caller
identity in the `impersonatedUser` field. This pattern indicates that someone is accessing the cluster through the Azure
ARM API rather than directly via kubectl against the API server. While legitimate for Arc-managed workflows, adversaries
with stolen service principal credentials can abuse Arc Cluster Connect to read, exfiltrate, or modify secrets and
configmaps while appearing as the Arc proxy service account in K8s audit logs.
"""
false_positives = [
"""
Azure Arc system components may create or update secrets and configmaps in the azure-arc and azure-arc-release
namespaces during normal cluster management. Filter by namespace to exclude these.
""",
"""
Helm operations managed through Arc may create release secrets (prefixed with sh.helm.release.v1). These are normal
Arc lifecycle operations.
""",
]
from = "now-5d"
interval = "9m"
language = "esql"
license = "Elastic License v2"
name = "Kubernetes Secret or ConfigMap Access via Azure Arc Proxy"
note = """## Triage and analysis
### Investigating Kubernetes Secret or ConfigMap Access via Azure Arc Proxy
When Kubernetes operations are performed through Azure Arc Cluster Connect, the K8s audit log shows the Arc AAD proxy
service account as the authenticated user, with the actual Azure AD identity in the `impersonatedUser` field. This
rule detects non-system secret and configmap access — including reads, writes, and deletions — routed through this
proxy path. Read operations (`get`, `list`) are particularly important to detect as they represent the most common
adversary action: exfiltrating secrets without leaving obvious modification traces.
### Possible investigation steps
- Check the `kubernetes.audit.impersonatedUser.username` field — this contains the Azure AD object ID of the actual
caller. Cross-reference with Azure AD to identify the service principal or user.
- Review the `kubernetes.audit.impersonatedUser.extra.oid` field for the Azure AD object ID.
- Examine the namespace — operations in `default` or application namespaces are more suspicious than `azure-arc` or
`kube-system`.
- Check the `kubernetes.audit.objectRef.name` — look for suspicious secret/configmap names that don't match known
application resources.
- Correlate with Azure Activity Logs for the same time window to find the `LISTCLUSTERUSERCREDENTIAL` operation that
initiated the Arc proxy session.
- Review Azure Sign-In Logs for the impersonated identity's authentication source IP and geolocation.
### Response and remediation
- If the impersonated identity is not recognized, revoke its Azure AD credentials immediately.
- Remove the ClusterRoleBinding or RoleBinding that grants the identity access to secrets/configmaps.
- Rotate any Kubernetes secrets that may have been read or exfiltrated.
- Review the Arc connection and consider disconnecting it if compromised.
"""
references = [
"https://learn.microsoft.com/en-us/azure/azure-arc/kubernetes/cluster-connect",
"https://microsoft.github.io/Threat-Matrix-for-Kubernetes/",
"https://www.ibm.com/think/x-force/identifying-abusing-azure-arc-for-hybrid-escalation-persistence",
"https://cloud.google.com/blog/topics/threat-intelligence/escalating-privileges-azure-kubernetes-services",
"https://www.wiz.io/blog/lateral-movement-risks-in-the-cloud-and-how-to-prevent-them-part-3-from-compromis",
]
risk_score = 47
rule_id = "220d92c6-479d-4a49-9cc0-3a29756dad0c"
severity = "medium"
tags = [
"Data Source: Kubernetes",
"Domain: Kubernetes",
"Domain: Cloud",
"Use Case: Threat Detection",
"Tactic: Credential Access",
"Tactic: Collection",
"Resources: Investigation Guide",
]
timestamp_override = "event.ingested"
type = "esql"
query = '''
FROM logs-kubernetes.audit_logs-* metadata _id, _version, _index
| WHERE STARTS_WITH(kubernetes.audit.user.username, "system:serviceaccount:azure-arc:")
AND kubernetes.audit.objectRef.resource IN ("secrets", "configmaps")
AND kubernetes.audit.verb IN ("get", "list", "create", "update", "patch", "delete")
AND kubernetes.audit.objectRef.namespace NOT IN ("azure-arc", "azure-arc-release", "kube-system")
AND NOT STARTS_WITH(kubernetes.audit.objectRef.name, "sh.helm.release.v1")
| STATS
Esql.verb_values = VALUES(kubernetes.audit.verb),
Esql.resource_type_values = VALUES(kubernetes.audit.objectRef.resource),
Esql.resource_name_values = VALUES(kubernetes.audit.objectRef.name),
Esql.namespace_values = VALUES(kubernetes.audit.objectRef.namespace),
Esql.acting_user_values = VALUES(kubernetes.audit.user.username),
Esql.user_agent_values = VALUES(kubernetes.audit.userAgent),
Esql.source_ips_values = VALUES(kubernetes.audit.sourceIPs),
Esql.response_code_values = VALUES(kubernetes.audit.responseStatus.code),
Esql.timestamp_first_seen = MIN(@timestamp),
Esql.timestamp_last_seen = MAX(@timestamp),
Esql.event_count = COUNT(*)
BY kubernetes.audit.impersonatedUser.username
| WHERE Esql.timestamp_first_seen >= NOW() - 9 minutes
| KEEP *
'''
[[rule.threat]]
framework = "MITRE ATT&CK"
[[rule.threat.technique]]
id = "T1552"
name = "Unsecured Credentials"
reference = "https://attack.mitre.org/techniques/T1552/"
[[rule.threat.technique.subtechnique]]
id = "T1552.007"
name = "Container API"
reference = "https://attack.mitre.org/techniques/T1552/007/"
[rule.threat.tactic]
id = "TA0006"
name = "Credential Access"
reference = "https://attack.mitre.org/tactics/TA0006/"
[[rule.threat]]
framework = "MITRE ATT&CK"
[[rule.threat.technique]]
id = "T1213"
name = "Data from Information Repositories"
reference = "https://attack.mitre.org/techniques/T1213/"
[[rule.threat.technique]]
id = "T1530"
name = "Data from Cloud Storage"
reference = "https://attack.mitre.org/techniques/T1530/"
[rule.threat.tactic]
id = "TA0009"
name = "Collection"
reference = "https://attack.mitre.org/tactics/TA0009/"
[[rule.threat]]
framework = "MITRE ATT&CK"
[[rule.threat.technique]]
id = "T1565"
name = "Data Manipulation"
reference = "https://attack.mitre.org/techniques/T1565/"
[[rule.threat.technique.subtechnique]]
id = "T1565.001"
name = "Stored Data Manipulation"
reference = "https://attack.mitre.org/techniques/T1565/001/"
[rule.threat.tactic]
id = "TA0040"
name = "Impact"
reference = "https://attack.mitre.org/tactics/TA0040/"
Stages and Predicates
Stage 1: from
FROM logs-kubernetes.audit_logs-* metadata _id, _version, _index
Stage 2: where
| WHERE STARTS_WITH(kubernetes.audit.user.username, "system:serviceaccount:azure-arc:")
AND kubernetes.audit.objectRef.resource IN ("secrets", "configmaps")
AND kubernetes.audit.verb IN ("get", "list", "create", "update", "patch", "delete")
AND kubernetes.audit.objectRef.namespace NOT IN ("azure-arc", "azure-arc-release", "kube-system")
AND NOT STARTS_WITH(kubernetes.audit.objectRef.name, "sh.helm.release.v1")
Stage 3: stats
| STATS
Esql.verb_values = VALUES(kubernetes.audit.verb),
Esql.resource_type_values = VALUES(kubernetes.audit.objectRef.resource),
Esql.resource_name_values = VALUES(kubernetes.audit.objectRef.name),
Esql.namespace_values = VALUES(kubernetes.audit.objectRef.namespace),
Esql.acting_user_values = VALUES(kubernetes.audit.user.username),
Esql.user_agent_values = VALUES(kubernetes.audit.userAgent),
Esql.source_ips_values = VALUES(kubernetes.audit.sourceIPs),
Esql.response_code_values = VALUES(kubernetes.audit.responseStatus.code),
Esql.timestamp_first_seen = MIN(@timestamp),
Esql.timestamp_last_seen = MAX(@timestamp),
Esql.event_count = COUNT(*)
BY kubernetes.audit.impersonatedUser.username
Stage 4: where
| WHERE Esql.timestamp_first_seen >= NOW() - 9 minutes
Stage 5: keep
| KEEP *
Exclusions
Top-level NOT(...) conjuncts: predicates this rule actively suppresses.
| Field | Kind | Excluded values |
|---|---|---|
kubernetes.audit.objectRef.name | starts_with | sh.helm.release.v1 |
kubernetes.audit.objectRef.namespace | in | azure-arc, azure-arc-release, kube-system |
Indicators
Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.
| Field | Kind | Values |
|---|---|---|
Esql.timestamp_first_seen | ge |
|
kubernetes.audit.objectRef.resource | in |
|
kubernetes.audit.user.username | starts_with |
|
kubernetes.audit.verb | in |
|
Output fields
Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.
| Field | Source |
|---|---|
* | KEEP * |