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