Detection rules › Panther
Kubernetes Secret Access Denied
This detection monitors for failed attempts to read Kubernetes secrets. While occasional failed access attempts may indicate RBAC misconfigurations, repeated failures suggest enumeration or brute-force attempts by compromised accounts. With 15-minute deduplication, 20 or more failed attempts within this window indicates active secret enumeration and should be investigated immediately.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Credential Access | T1552.007 Unsecured Credentials: Container API |
| Discovery | T1613 Container and Resource Discovery |
Rule body yaml
AnalysisType: rule
RuleID: "Kubernetes.Secret.AccessDenied"
DisplayName: "Kubernetes Secret Access Denied"
Enabled: true
Status: Experimental
Filename: k8s_secret_access_denied.py
LogTypes:
- Amazon.EKS.Audit
- Azure.MonitorActivity
- GCP.AuditLog
Tags:
- Kubernetes
- Credential Access
- Enumeration
- Secrets Management
- Unified Detection
Severity: Medium
Description: >
This detection monitors for failed attempts to read Kubernetes secrets. While occasional failed access attempts
may indicate RBAC misconfigurations, repeated failures suggest enumeration or brute-force attempts by compromised
accounts. With 15-minute deduplication, 20 or more failed attempts within this window indicates active secret
enumeration and should be investigated immediately.
Runbook: |
1. Review the total count of failed secret access attempts for this user in the 15-minute dedup window and identify all secrets they attempted to access
2. Determine if the user or service account should have access to any secrets and check if this represents a misconfiguration or malicious enumeration
3. Search for successful secret access by this user in the past 24 hours and review all other API operations to identify if the account is compromised
Reports:
MITRE ATT&CK:
- TA0006:T1552.007 # Credential Access: Unsecured Credentials - Container API
- TA0007:T1613 # Discovery: Container and Resource Discovery
Reference: https://kubernetes.io/docs/concepts/configuration/secret/
DedupPeriodMinutes: 15
Threshold: 20
SummaryAttributes:
- username
- namespace
- p_source_label
Tests:
- Name: EKS failed secret get (403 Forbidden)
ExpectedResult: true
Log:
{
"kind": "Event",
"apiVersion": "audit.k8s.io/v1",
"verb": "get",
"user": {"username": "attacker@example.com"},
"sourceIPs": ["203.0.113.42"],
"userAgent": "kubectl/v1.28.0",
"objectRef": {
"resource": "secrets",
"namespace": "production",
"name": "db-password",
"apiVersion": "v1"
},
"responseStatus": {
"code": 403,
"message": "Forbidden: User cannot get resource secrets in namespace production"
},
"p_log_type": "Amazon.EKS.Audit",
"p_source_label": "eks-cluster"
}
- Name: EKS failed secret get (404 Not Found)
ExpectedResult: true
Log:
{
"kind": "Event",
"apiVersion": "audit.k8s.io/v1",
"verb": "get",
"user": {"username": "attacker@example.com"},
"sourceIPs": ["203.0.113.42"],
"userAgent": "kubectl/v1.28.0",
"objectRef": {
"resource": "secrets",
"namespace": "production",
"name": "nonexistent-secret",
"apiVersion": "v1"
},
"responseStatus": {
"code": 404,
"message": "secrets \"nonexistent-secret\" not found"
},
"p_log_type": "Amazon.EKS.Audit",
"p_source_label": "eks-cluster"
}
- Name: AKS failed secret get (401 Unauthorized)
ExpectedResult: true
Log:
{
"p_log_type": "Azure.MonitorActivity",
"category": "kube-audit",
"operationName": "Microsoft.ContainerService/managedClusters/diagnosticLogs/Read",
"properties": {
"log": "{\"kind\":\"Event\",\"apiVersion\":\"audit.k8s.io/v1\",\"verb\":\"get\",\"user\":{\"username\":\"compromised-user@example.com\"},\"sourceIPs\":[\"1.2.3.4\"],\"objectRef\":{\"resource\":\"secrets\",\"namespace\":\"default\",\"name\":\"api-token\"},\"responseStatus\":{\"code\":401,\"message\":\"Unauthorized\"}}"
},
"p_source_label": "aks-cluster"
}
- Name: GKE failed secret get
ExpectedResult: true
Log:
{
"protoPayload": {
"authenticationInfo": {"principalEmail": "user@company.com"},
"authorizationInfo": [{
"granted": false,
"permission": "io.k8s.core.v1.secrets.get",
"resource": "core/v1/namespaces/production/secrets/tls-cert"
}],
"methodName": "io.k8s.core.v1.secrets.get",
"requestMetadata": {"callerIP": "8.8.8.8"},
"resourceName": "core/v1/namespaces/production/secrets/tls-cert",
"serviceName": "k8s.io",
"status": {
"code": 7,
"message": "PERMISSION_DENIED"
}
},
"resource": {
"type": "k8s_cluster",
"labels": {"project_id": "test-project"}
},
"p_log_type": "GCP.AuditLog",
"p_source_label": "gke-cluster"
}
- Name: Service account failed to get secret
ExpectedResult: true
Log:
{
"kind": "Event",
"verb": "get",
"user": {"username": "system:serviceaccount:default:app-sa"},
"objectRef": {
"resource": "secrets",
"namespace": "production",
"name": "admin-token"
},
"responseStatus": {
"code": 403,
"message": "Forbidden"
},
"p_log_type": "Amazon.EKS.Audit",
"p_source_label": "eks-cluster"
}
- Name: Successful secret get
ExpectedResult: false
Log:
{
"kind": "Event",
"verb": "get",
"user": {"username": "admin@example.com"},
"objectRef": {
"resource": "secrets",
"namespace": "default",
"name": "app-config"
},
"responseStatus": {"code": 200},
"p_log_type": "Amazon.EKS.Audit"
}
- Name: System service account failed get (excluded)
ExpectedResult: false
Log:
{
"kind": "Event",
"verb": "get",
"user": {"username": "system:serviceaccount:kube-system:controller"},
"objectRef": {
"resource": "secrets",
"namespace": "kube-system",
"name": "token"
},
"responseStatus": {"code": 403},
"p_log_type": "Amazon.EKS.Audit"
}
- Name: Failed get on non-secret resource
ExpectedResult: false
Log:
{
"kind": "Event",
"verb": "get",
"user": {"username": "user@example.com"},
"objectRef": {
"resource": "configmaps",
"namespace": "default",
"name": "app-config"
},
"responseStatus": {"code": 403},
"p_log_type": "Amazon.EKS.Audit"
}
- Name: List secrets operation (not get)
ExpectedResult: false
Log:
{
"kind": "Event",
"verb": "list",
"user": {"username": "attacker@example.com"},
"objectRef": {
"resource": "secrets",
"namespace": "production"
},
"responseStatus": {"code": 403},
"p_log_type": "Amazon.EKS.Audit"
}
Detection logic
Condition
not (verb ne "get" or resource ne "secrets")
responseStatus is_not_null
responseStatus.code ge "400" or (responseStatus.code ge "1" and responseStatus.code le "16")
not ((namespace is_not_null and namespace in ["kube-system", "gke-system", "kube-node-lease", "kube-public"]) or (username is_not_null and (username in ["masterclient", "aksService"] or (username starts_with "system:" and username not contains "serviceaccount"))))
This rule also runs imperative logic the parser cannot express as a filter; the conditions above are the structured part it could extract.
Exclusions
Top-level NOT(...) conjuncts: predicates this rule actively suppresses.
| Field | Kind | Excluded values |
|---|---|---|
username | contains | serviceaccount |
username | starts_with | system: |
username | in | aksService, masterclient |
username | is_not_null | |
namespace | in | gke-system, kube-node-lease, kube-public, kube-system |
namespace | is_not_null | |
resource | ne | secrets |
verb | ne | get |
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 |
|---|---|---|
responseStatus | is_not_null | |
responseStatus.code | ge |
|
responseStatus.code | le |
|
username | contains |
|
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 |
|---|---|
username | |
sourceIPs | |
userAgent | |
namespace | |
verb | |
resource | |
requestURI | |
responseStatus | |
cluster | p_source_label |
name | |
code | responseStatus.code |