GCP K8S Service Type NodePort Deployed


Description

This detection monitors for any kubernetes service deployed with type node port. A Node Port 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_gcp_helpers import gcp_alert_context


def rule(event):
    if event.deep_get("protoPayload", "response", "status") == "Failure":
        return False

    if event.deep_get("protoPayload", "methodName") != "io.k8s.core.v1.services.create":
        return False

    if event.deep_get("protoPayload", "request", "spec", "type") != "NodePort":
        return False

    authorization_info = event.deep_walk("protoPayload", "authorizationInfo")
    if not authorization_info:
        return False

    for auth in authorization_info:
        if (
            auth.get("permission") == "io.k8s.core.v1.services.create"
            and auth.get("granted") is True
        ):
            return True
    return False


def title(event):
    actor = event.deep_get(
        "protoPayload", "authenticationInfo", "principalEmail", default="<ACTOR_NOT_FOUND>"
    )
    project_id = event.deep_get("resource", "labels", "project_id", default="<PROJECT_NOT_FOUND>")

    return f"[GCP]: [{actor}] created NodePort service in project [{project_id}]"


def alert_context(event):
    context = gcp_alert_context(event)
    request_spec = event.deep_walk("protoPayload", "request", "spec")
    context["request_spec"] = request_spec
    return context

Analyst notes

Investigate the reason of creating NodePort service. Advise that it is discouraged practice. Create ticket if appropriate.

Raw source GCP K8S Service Type NodePort Deployed · Panther Python
Esc
Published by panther-labs/panther-analysis ↗, licensed under Apache 2.0 ↗. Reproduced here unmodified.
AnalysisType: rule
RuleID: "GCP.K8S.Service.Type.NodePort.Deployed"
DisplayName: "GCP K8S Service Type NodePort Deployed"
Enabled: false
Status: Deprecated
Filename: gcp_k8s_service_type_node_port_deployed.py
LogTypes:
  - GCP.AuditLog
Severity: High
Description: >
  This detection monitors for any kubernetes service deployed with type node port. A Node Port 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: |
  Investigate the reason of creating NodePort service. Advise that it is discouraged practice.
  Create ticket if appropriate.
Reference: https://kubernetes.io/docs/tutorials/kubernetes-basics/expose/expose-intro/
Tags:
  - Deprecated
  - Exploit Public-Facing Application
  - Initial Access
Reports:
  MITRE ATT&CK:
    - TA0001:T1190 # Exploit Public-Facing Application
Tests:
  - Name: Service Created
    ExpectedResult: true
    Log:
      {
        "logName": "projects/some-project/logs/cloudaudit.googleapis.com%2Factivity",
        "protoPayload":
          {
            "at_sign_type": "type.googleapis.com/google.cloud.audit.AuditLog",
            "authenticationInfo": { "principalEmail": "some.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":
                  {
                    "ports":
                      [
                        {
                          "name": "5678-8080",
                          "port": 5678,
                          "protocol": "TCP",
                          "targetPort": 8080,
                        },
                      ],
                    "type": "NodePort",
                  },
              },
            "requestMetadata":
              {
                "callerIP": "1.2.3.4",
                "callerSuppliedUserAgent": "kubectl/v1.28.2 (darwin/amd64) kubernetes/89a4ea3",
              },
            "resourceName": "core/v1/namespaces/default/services/test-ns",
            "response":
              {
                "@type": "core.k8s.io/v1.Service",
                "apiVersion": "v1",
                "kind": "Service",
                "metadata":
                  {
                    "creationTimestamp": "2024-02-19T12:02:21Z",
                    "name": "test-ns",
                    "namespace": "default",
                    "resourceVersion": "15036073",
                    "uid": "28758fe1-534a-4705-bcc2-12eeac6f11a4",
                  },
                "spec":
                  {
                    "clusterIP": "2.3.4.5",
                    "clusterIPs": ["2.3.4.5"],
                    "ports":
                      [
                        {
                          "name": "5678-8080",
                          "nodePort": 32361,
                          "port": 5678,
                          "protocol": "TCP",
                          "targetPort": 8080,
                        },
                      ],
                    "type": "NodePort",
                  },
              },
            "serviceName": "k8s.io",
            "status": {},
          },
        "receiveTimestamp": "2024-02-19 12:02:39.542633547",
        "resource":
          {
            "labels":
              {
                "cluster_name": "some-project-cluster",
                "location": "us-west1",
                "project_id": "some-project",
              },
            "type": "k8s_cluster",
          },
        "timestamp": "2024-02-19 12:02:22.057586000",
      }
  - Name: Error Creating Service
    ExpectedResult: false
    Log:
      {
        "logName": "projects/some-project/logs/cloudaudit.googleapis.com%2Factivity",
        "protoPayload":
          {
            "at_sign_type": "type.googleapis.com/google.cloud.audit.AuditLog",
            "authenticationInfo": { "principalEmail": "some.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":
                  {
                    "ports":
                      [
                        {
                          "name": "5678-8080",
                          "port": 5678,
                          "protocol": "TCP",
                          "targetPort": 8080,
                        },
                      ],
                    "type": "NodePort",
                  },
              },
            "requestMetadata":
              {
                "callerIP": "1.2.3.4",
                "callerSuppliedUserAgent": "kubectl/v1.28.2 (darwin/amd64) kubernetes/89a4ea3",
              },
            "resourceName": "core/v1/namespaces/default/services/test-ns",
            "response":
              {
                "@type": "core.k8s.io/v1.Status",
                "apiVersion": "v1",
                "code": 409,
                "details": { "kind": "services", "name": "test-ns" },
                "kind": "Status",
                "message": 'services "test-ns" already exists',
                "metadata": {},
                "reason": "AlreadyExists",
                "status": "Failure",
              },
            "serviceName": "k8s.io",
            "status":
              { "code": 10, "message": 'services "test-ns" already exists' },
          },
        "receiveTimestamp": "2024-02-20 13:47:46.955496128",
        "resource":
          {
            "labels":
              {
                "cluster_name": "some-project-cluster",
                "location": "us-west1",
                "project_id": "some-project",
              },
            "type": "k8s_cluster",
          },
        "timestamp": "2024-02-20 13:47:43.126037000",
      }
  - Name: No Permission Granted
    ExpectedResult: false
    Log:
      {
        "logName": "projects/some-project/logs/cloudaudit.googleapis.com%2Factivity",
        "protoPayload":
          {
            "at_sign_type": "type.googleapis.com/google.cloud.audit.AuditLog",
            "authenticationInfo": { "principalEmail": "some.user@company.com" },
            "authorizationInfo":
              [
                {
                  "granted": false,
                  "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":
                  {
                    "ports":
                      [
                        {
                          "name": "5678-8080",
                          "port": 5678,
                          "protocol": "TCP",
                          "targetPort": 8080,
                        },
                      ],
                    "type": "NodePort",
                  },
              },
            "requestMetadata":
              {
                "callerIP": "1.2.3.4",
                "callerSuppliedUserAgent": "kubectl/v1.28.2 (darwin/amd64) kubernetes/89a4ea3",
              },
            "resourceName": "core/v1/namespaces/default/services/test-ns",
            "spec":
              {
                "clusterIP": "2.3.4.5",
                "clusterIPs": ["2.3.4.5"],
                "externalTrafficPolicy": "Cluster",
                "internalTrafficPolicy": "Cluster",
                "ipFamilies": ["IPv4"],
                "ipFamilyPolicy": "SingleStack",
                "ports":
                  [
                    {
                      "name": "5678-8080",
                      "nodePort": 32361,
                      "port": 5678,
                      "protocol": "TCP",
                      "targetPort": 8080,
                    },
                  ],
                "type": "NodePort",
              },
          },
        "serviceName": "k8s.io",
        "status": {},
        "receiveTimestamp": "2024-02-19 12:02:39.542633547",
        "resource":
          {
            "labels":
              {
                "cluster_name": "some-project-cluster",
                "location": "us-west1",
                "project_id": "some-project",
              },
            "type": "k8s_cluster",
          },
        "timestamp": "2024-02-19 12:02:22.057586000",
      }


# ------ paired body: gcp_k8s_service_type_node_port_deployed.py ------

from panther_gcp_helpers import gcp_alert_context


def rule(event):
    if event.deep_get("protoPayload", "response", "status") == "Failure":
        return False

    if event.deep_get("protoPayload", "methodName") != "io.k8s.core.v1.services.create":
        return False

    if event.deep_get("protoPayload", "request", "spec", "type") != "NodePort":
        return False

    authorization_info = event.deep_walk("protoPayload", "authorizationInfo")
    if not authorization_info:
        return False

    for auth in authorization_info:
        if (
            auth.get("permission") == "io.k8s.core.v1.services.create"
            and auth.get("granted") is True
        ):
            return True
    return False


def title(event):
    actor = event.deep_get(
        "protoPayload", "authenticationInfo", "principalEmail", default="<ACTOR_NOT_FOUND>"
    )
    project_id = event.deep_get("resource", "labels", "project_id", default="<PROJECT_NOT_FOUND>")

    return f"[GCP]: [{actor}] created NodePort service in project [{project_id}]"


def alert_context(event):
    context = gcp_alert_context(event)
    request_spec = event.deep_walk("protoPayload", "request", "spec")
    context["request_spec"] = request_spec
    return context

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.