Skip to content

Commit

Permalink
Merge pull request #570 from kubescape/feature/support-httproute-in-e…
Browse files Browse the repository at this point in the history
…xposed-to-internet-rule

Support HTTPRoute in Gateway API
  • Loading branch information
YiscahLevySilas1 authored Apr 14, 2024
2 parents e98a0d4 + 35ec9c1 commit d77150c
Show file tree
Hide file tree
Showing 14 changed files with 896 additions and 4 deletions.
33 changes: 33 additions & 0 deletions controls/C-0266-exposuretointernet-gateway.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "Exposure to internet via Gateway API",
"attributes": {
"controlTypeTags": [
"security"
],
"attackTracks": [
{
"attackTrack": "workload-external-track",
"categories": [
"Initial Access"
]
},
{
"attackTrack": "service-destruction",
"categories": [
"Initial Access"
]
}
]
},
"description": "This control detect workloads that are exposed on Internet through a Gateway API (HTTPRoute,TCPRoute, UDPRoute). It fails in case it find workloads connected with these resources.",
"remediation": "The user can evaluate its exposed resources and apply relevant changes wherever needed.",
"rulesNames": ["exposure-to-internet-via-gateway-api"],
"test": "Checks if workloads are exposed through the use of Gateway API (HTTPRoute,TCPRoute, UDPRoute).",
"controlID": "C-0266",
"baseScore": 7.0,
"scanningScope": {
"matches": [
"cluster"
]
}
}
6 changes: 6 additions & 0 deletions frameworks/security.json
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,12 @@
"patch": {
"name": "Outdated Kubernetes version"
}
},
{
"controlID": "C-0266",
"patch": {
"name": "Exposure to internet via Gateway API"
}
}
]
}
78 changes: 78 additions & 0 deletions rules/exposure-to-internet-via-gateway-api/raw.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package armo_builtins
import future.keywords.in


deny[msga] {
httproute := input[_]
httproute.kind in ["HTTPRoute", "TCPRoute", "UDPRoute"]

svc := input[_]
svc.kind == "Service"

# Make sure that they belong to the same namespace
svc.metadata.namespace == httproute.metadata.namespace

# avoid duplicate alerts
# if service is already exposed through NodePort or LoadBalancer workload will fail on that
not is_exposed_service(svc)

wl := input[_]
wl.metadata.namespace == svc.metadata.namespace
spec_template_spec_patterns := {"Deployment", "ReplicaSet", "DaemonSet", "StatefulSet", "Pod", "Job", "CronJob"}
spec_template_spec_patterns[wl.kind]
wl_connected_to_service(wl, svc)

result := svc_connected_to_httproute(svc, httproute)

msga := {
"alertMessage": sprintf("workload '%v' is exposed through httproute '%v'", [wl.metadata.name, httproute.metadata.name]),
"packagename": "armo_builtins",
"failedPaths": [],
"fixPaths": [],
"alertScore": 7,
"alertObject": {
"k8sApiObjects": [wl]
},
"relatedObjects": [
{
"object": httproute,
"reviewPaths": result,
"failedPaths": result,
},
{
"object": svc,
}
]
}
}

# ====================================================================================

is_exposed_service(svc) {
svc.spec.type == "NodePort"
}

is_exposed_service(svc) {
svc.spec.type == "LoadBalancer"
}

wl_connected_to_service(wl, svc) {
count({x | svc.spec.selector[x] == wl.metadata.labels[x]}) == count(svc.spec.selector)
}

wl_connected_to_service(wl, svc) {
wl.spec.selector.matchLabels == svc.spec.selector
}

wl_connected_to_service(wl, svc) {
count({x | svc.spec.selector[x] == wl.spec.template.metadata.labels[x]}) == count(svc.spec.selector)
}

svc_connected_to_httproute(svc, httproute) = result {
rule := httproute.spec.rules[i]
ref := rule.backendRefs[j]
ref.kind == "Service"
svc.metadata.name == ref.name
result := [sprintf("spec.rules[%d].backendRefs[%d].name", [i,j])]
}

62 changes: 62 additions & 0 deletions rules/exposure-to-internet-via-gateway-api/rule.metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{
"name": "exposure-to-internet-via-gateway-api",
"attributes": {
},
"ruleLanguage": "Rego",
"match": [
{
"apiGroups": [
""
],
"apiVersions": [
"v1"
],
"resources": [
"Pod",
"Service"
]
},
{
"apiGroups": [
"apps"
],
"apiVersions": [
"v1"
],
"resources": [
"Deployment",
"ReplicaSet",
"DaemonSet",
"StatefulSet"
]
},
{
"apiGroups": [
"batch"
],
"apiVersions": [
"*"
],
"resources": [
"Job",
"CronJob"
]
},
{
"apiGroups": [
"gateway.networking.k8s.io"
],
"apiVersions": [
"v1", "v1alpha2"
],
"resources": [
"HTTPRoute",
"TCPRoute",
"UDPRoute"
]
}
],
"description": "fails in case the running workload has binded Service and Gateway that are exposing it on Internet.",
"remediation": "",
"ruleQuery": "armo_builtins"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
[
{
"alertMessage": "workload 'httpbin' is exposed through httproute 'http'",
"failedPaths": [],
"fixPaths": [],
"ruleStatus": "",
"packagename": "armo_builtins",
"alertScore": 7,
"alertObject": {
"k8sApiObjects": [
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"name": "httpbin"
}
}
]
},
"relatedObjects": [
{
"object": {
"apiVersion": "gateway.networking.k8s.io/v1",
"kind": "HTTPRoute",
"metadata": {
"annotations": {
"kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"gateway.networking.k8s.io/v1beta1\",\"kind\":\"HTTPRoute\",\"metadata\":{\"annotations\":{},\"name\":\"http\",\"namespace\":\"default\"},\"spec\":{\"hostnames\":[\"httpbin.example.com\"],\"parentRefs\":[{\"name\":\"gateway\",\"namespace\":\"istio-ingress\"}],\"rules\":[{\"backendRefs\":[{\"name\":\"httpbin\",\"port\":8000}],\"matches\":[{\"path\":{\"type\":\"PathPrefix\",\"value\":\"/get\"}}]}]}}\n"
},
"creationTimestamp": "2024-04-14T07:41:31Z",
"generation": 1,
"name": "http",
"namespace": "default",
"resourceVersion": "2647",
"uid": "b7c1d09f-0cf8-4fc6-ada8-ec415b463038"
},
"spec": {
"hostnames": [
"httpbin.example.com"
],
"parentRefs": [
{
"group": "gateway.networking.k8s.io",
"kind": "Gateway",
"name": "gateway",
"namespace": "istio-ingress"
}
],
"rules": [
{
"backendRefs": [
{
"group": "",
"kind": "Service",
"name": "httpbin",
"port": 8000,
"weight": 1
}
],
"matches": [
{
"path": {
"type": "PathPrefix",
"value": "/get"
}
}
]
}
]
},
"status": {
"parents": [
{
"conditions": [
{
"lastTransitionTime": "2024-04-14T07:41:38Z",
"message": "",
"observedGeneration": 1,
"reason": "Accepted",
"status": "True",
"type": "Accepted"
},
{
"lastTransitionTime": "2024-04-14T07:41:38Z",
"message": "",
"observedGeneration": 1,
"reason": "ResolvedRefs",
"status": "True",
"type": "ResolvedRefs"
}
],
"controllerName": "solo.io/gloo-gateway",
"parentRef": {
"group": "gateway.networking.k8s.io",
"kind": "Gateway",
"name": "gateway",
"namespace": "istio-ingress"
}
},
{
"conditions": [
{
"lastTransitionTime": "2024-04-14T07:41:38Z",
"message": "Route was valid",
"observedGeneration": 1,
"reason": "Accepted",
"status": "True",
"type": "Accepted"
},
{
"lastTransitionTime": "2024-04-14T07:41:38Z",
"message": "All references resolved",
"observedGeneration": 1,
"reason": "ResolvedRefs",
"status": "True",
"type": "ResolvedRefs"
}
],
"controllerName": "istio.io/gateway-controller",
"parentRef": {
"group": "gateway.networking.k8s.io",
"kind": "Gateway",
"name": "gateway",
"namespace": "istio-ingress"
}
}
]
}
},
"failedPaths": [
"spec.rules[0].backendRefs[0].name"
],
"reviewPaths": [
"spec.rules[0].backendRefs[0].name"
]
},
{
"object": {
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"annotations": {
"kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"labels\":{\"app\":\"httpbin\",\"service\":\"httpbin\"},\"name\":\"httpbin\",\"namespace\":\"default\"},\"spec\":{\"ports\":[{\"name\":\"http\",\"port\":8000,\"targetPort\":8080}],\"selector\":{\"app\":\"httpbin\"}}}\n"
},
"creationTimestamp": "2024-04-14T07:39:35Z",
"labels": {
"app": "httpbin",
"service": "httpbin"
},
"name": "httpbin",
"namespace": "default",
"resourceVersion": "2328",
"uid": "5b675069-a387-4fa4-83b6-8fd25462f714"
},
"spec": {
"clusterIP": "10.96.126.137",
"clusterIPs": [
"10.96.126.137"
],
"internalTrafficPolicy": "Cluster",
"ipFamilies": [
"IPv4"
],
"ipFamilyPolicy": "SingleStack",
"ports": [
{
"name": "http",
"port": 8000,
"protocol": "TCP",
"targetPort": 8080
}
],
"selector": {
"app": "httpbin"
},
"sessionAffinity": "None",
"type": "ClusterIP"
},
"status": {
"loadBalancer": {}
}
}
}
]
}
]
Loading

0 comments on commit d77150c

Please sign in to comment.