Kubernetes NodePort Service Deployed


Description

This detection monitors for any Kubernetes service deployed with type NodePort. A NodePort service allows an attacker to expose a set of pods hosting the service to the internet by opening their port and redirecting traffic here. This can be used to bypass network controls and intercept traffic, creating a direct line to the outside network.

Query · python

from panther_kubernetes_helpers import is_failed_request, k8s_alert_context


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

    # Only check service creation events
    if verb != "create" or resource != "services":
        return False

    # Skip failed requests
    if is_failed_request(response_status):
        return False

    # Check if service type is NodePort
    service_type = event.udm("serviceType") or ""
    if service_type == "NodePort":
        return True

    return False


def title(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    namespace = event.udm("namespace") or "<UNKNOWN_NAMESPACE>"
    name = event.udm("name") or "<UNKNOWN>"

    return f"[{username}] deployed NodePort service [{namespace}/{name}]"


def alert_context(event):
    return k8s_alert_context(
        event,
        extra_fields={
            "service_name": event.udm("name"),
            "service_type": event.udm("serviceType"),
        },
    )

Analyst notes

  1. Identify all services created by the username in the 48 hours before the alert to understand deployment patterns
  2. Check the NodePort service specification to determine if it exposes cluster-critical services or standard application workloads
  3. Review exposed ports and compare against documented internal services in the past 30 days to verify if this is authorized
Raw source Kubernetes NodePort Service Deployed · Panther Python
Esc
Published by panther-labs/panther-analysis ↗, licensed under Apache 2.0 ↗. Reproduced here unmodified.
AnalysisType: rule
RuleID: "Kubernetes.Service.NodePort.Deployed"
DisplayName: "Kubernetes NodePort Service Deployed"
Enabled: true
Filename: k8s_service_nodeport.py
LogTypes:
  - Amazon.EKS.Audit
  - Azure.MonitorActivity
  - GCP.AuditLog
Severity: High
Description: >
  This detection monitors for any Kubernetes service deployed with type NodePort. A NodePort
  service allows an attacker to expose a set of pods hosting the service to the internet by
  opening their port and redirecting traffic here. This can be used to bypass network controls
  and intercept traffic, creating a direct line to the outside network.
Runbook: |
  1. Identify all services created by the username in the 48 hours before the alert to understand deployment patterns
  2. Check the NodePort service specification to determine if it exposes cluster-critical services or standard application workloads
  3. Review exposed ports and compare against documented internal services in the past 30 days to verify if this is authorized
Reference: https://kubernetes.io/docs/tutorials/kubernetes-basics/expose/expose-intro/
Tags:
  - Kubernetes
  - Exploit Public-Facing Application
  - Initial Access
  - Unified Detection
Reports:
  MITRE ATT&CK:
    - TA0001:T1190 # Exploit Public-Facing Application
Tests:
  - Name: EKS NodePort Service Created
    ExpectedResult: true
    Log:
      {
        "kind": "Event",
        "apiVersion": "audit.k8s.io/v1",
        "verb": "create",
        "user": {"username": "user@example.com"},
        "sourceIPs": ["1.2.3.4"],
        "userAgent": "kubectl/v1.28.2",
        "objectRef": {
          "resource": "services",
          "namespace": "default",
          "name": "test-ns",
          "apiVersion": "v1"
        },
        "responseStatus": {"code": 201},
        "requestObject": {
          "kind": "Service",
          "apiVersion": "v1",
          "spec": {
            "type": "NodePort",
            "ports": [{
              "port": 5678,
              "targetPort": 8080,
              "protocol": "TCP"
            }]
          }
        },
        "p_log_type": "Amazon.EKS.Audit",
        "p_source_label": "eks-cluster"
      }
  - Name: AKS NodePort Service Created
    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\":\"create\",\"user\":{\"username\":\"admin@example.com\"},\"sourceIPs\":[\"10.0.0.1\"],\"objectRef\":{\"resource\":\"services\",\"namespace\":\"default\",\"name\":\"test-svc\"},\"responseStatus\":{\"code\":201},\"requestObject\":{\"kind\":\"Service\",\"spec\":{\"type\":\"NodePort\",\"ports\":[{\"port\":80}]}}}"
        },
        "p_source_label": "aks-cluster"
      }
  - Name: GCP GKE NodePort Service Created
    ExpectedResult: true
    Log:
      {
        "protoPayload": {
          "authenticationInfo": {"principalEmail": "user@company.com"},
          "authorizationInfo": [{
            "granted": true,
            "permission": "io.k8s.core.v1.services.create",
            "resource": "core/v1/namespaces/default/services/test-ns"
          }],
          "methodName": "io.k8s.core.v1.services.create",
          "request": {
            "@type": "core.k8s.io/v1.Service",
            "apiVersion": "v1",
            "kind": "Service",
            "spec": {
              "type": "NodePort",
              "ports": [{"port": 5678, "targetPort": 8080}]
            }
          },
          "requestMetadata": {"callerIP": "1.2.3.4"},
          "resourceName": "core/v1/namespaces/default/services/test-ns",
          "serviceName": "k8s.io",
          "status": {}
        },
        "resource": {
          "labels": {"project_id": "test-project"},
          "type": "k8s_cluster"
        },
        "p_log_type": "GCP.AuditLog",
        "p_source_label": "gke-cluster"
      }
  - Name: ClusterIP Service Created
    ExpectedResult: false
    Log:
      {
        "kind": "Event",
        "verb": "create",
        "objectRef": {"resource": "services", "namespace": "default"},
        "responseStatus": {"code": 201},
        "requestObject": {
          "spec": {"type": "ClusterIP"}
        },
        "p_log_type": "Amazon.EKS.Audit"
      }
  - Name: Service Creation Failed
    ExpectedResult: false
    Log:
      {
        "kind": "Event",
        "verb": "create",
        "objectRef": {"resource": "services", "namespace": "default"},
        "responseStatus": {"code": 409, "status": "Failure"},
        "requestObject": {
          "spec": {"type": "NodePort"}
        },
        "p_log_type": "Amazon.EKS.Audit"
      }
  - Name: Not a Service Creation
    ExpectedResult: false
    Log:
      {
        "kind": "Event",
        "verb": "get",
        "objectRef": {"resource": "services", "namespace": "default"},
        "p_log_type": "Amazon.EKS.Audit"
      }


# ------ paired body: k8s_service_nodeport.py ------

from panther_kubernetes_helpers import is_failed_request, k8s_alert_context


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

    # Only check service creation events
    if verb != "create" or resource != "services":
        return False

    # Skip failed requests
    if is_failed_request(response_status):
        return False

    # Check if service type is NodePort
    service_type = event.udm("serviceType") or ""
    if service_type == "NodePort":
        return True

    return False


def title(event):
    username = event.udm("username") or "<UNKNOWN_USER>"
    namespace = event.udm("namespace") or "<UNKNOWN_NAMESPACE>"
    name = event.udm("name") or "<UNKNOWN>"

    return f"[{username}] deployed NodePort service [{namespace}/{name}]"


def alert_context(event):
    return k8s_alert_context(
        event,
        extra_fields={
            "service_name": event.udm("name"),
            "service_type": event.udm("serviceType"),
        },
    )

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.