Kubernetes Secret Access Denied


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.

Query · python

from panther_kubernetes_helpers import (
    is_failed_request,
    is_system_namespace,
    is_system_principal,
    k8s_alert_context,
)


def rule(event):
    verb = event.udm("verb")
    resource = event.udm("resource")
    namespace = event.udm("namespace")
    username = event.udm("username")
    response_status = event.udm("responseStatus")

    # Only check secret get operations
    if verb != "get" or resource != "secrets":
        return False

    # Only alert on failed requests (access denied)
    if not is_failed_request(response_status):
        return False

    # Exclude system namespaces and system principals
    if is_system_namespace(namespace) or is_system_principal(username):
        return False

    return True


def title(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    namespace = event.udm("namespace") or "<UNKNOWN_NAMESPACE>"
    name = event.udm("name") or "<UNKNOWN_SECRET>"
    response_status = event.udm("responseStatus") or {}
    status_code = response_status.get("code", "UNKNOWN")

    return (
        f"[{username}] failed secret enumeration attempt "
        f"in [{namespace}/{name}] "
        f"(response: {status_code})"
    )


def dedup(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    namespace = event.udm("namespace") or "<UNKNOWN_NAMESPACE>"
    return f"k8s_secret_denied_{username}_{namespace}"


def alert_context(event):
    response_status = event.udm("responseStatus") or {}

    return k8s_alert_context(
        event,
        extra_fields={
            "secret_name": event.udm("name"),
            "response_code": response_status.get("code"),
            "response_message": response_status.get("message"),
        },
    )

Analyst notes

  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
Raw source Kubernetes Secret Access Denied · Panther Python
Esc
Published by panther-labs/panther-analysis ↗, licensed under Apache 2.0 ↗. Reproduced here unmodified.
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"
      }


# ------ paired body: k8s_secret_access_denied.py ------

from panther_kubernetes_helpers import (
    is_failed_request,
    is_system_namespace,
    is_system_principal,
    k8s_alert_context,
)


def rule(event):
    verb = event.udm("verb")
    resource = event.udm("resource")
    namespace = event.udm("namespace")
    username = event.udm("username")
    response_status = event.udm("responseStatus")

    # Only check secret get operations
    if verb != "get" or resource != "secrets":
        return False

    # Only alert on failed requests (access denied)
    if not is_failed_request(response_status):
        return False

    # Exclude system namespaces and system principals
    if is_system_namespace(namespace) or is_system_principal(username):
        return False

    return True


def title(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    namespace = event.udm("namespace") or "<UNKNOWN_NAMESPACE>"
    name = event.udm("name") or "<UNKNOWN_SECRET>"
    response_status = event.udm("responseStatus") or {}
    status_code = response_status.get("code", "UNKNOWN")

    return (
        f"[{username}] failed secret enumeration attempt "
        f"in [{namespace}/{name}] "
        f"(response: {status_code})"
    )


def dedup(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    namespace = event.udm("namespace") or "<UNKNOWN_NAMESPACE>"
    return f"k8s_secret_denied_{username}_{namespace}"


def alert_context(event):
    response_status = event.udm("responseStatus") or {}

    return k8s_alert_context(
        event,
        extra_fields={
            "secret_name": event.udm("name"),
            "response_code": response_status.get("code"),
            "response_message": response_status.get("message"),
        },
    )

Detection rules belong to the projects that publish them and remain under their own licenses. This site indexes and links to them; it claims no rights in them.