From 4a60e2d7b645fc1028555ce3a914e143886b5f10 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Tue, 14 Jan 2025 16:05:22 +0600 Subject: [PATCH] fix(checks): respect PodSecurityContext for containers Signed-off-by: Nikita Pivkin --- .../general/runs_with_GID_le_10000_test.rego | 23 ++++++++++++ .../general/runs_with_UID_le_10000_test.rego | 23 ++++++++++++ ..._default_seccomp_profile_not_set_test.rego | 2 +- lib/kubernetes/kubernetes.rego | 17 +++++++-- lib/kubernetes/kubernetes_test.rego | 35 ++++++++++++++----- lib/kubernetes/security_context.rego | 33 +++++++++++++++++ 6 files changed, 120 insertions(+), 13 deletions(-) create mode 100644 lib/kubernetes/security_context.rego diff --git a/checks/kubernetes/general/runs_with_GID_le_10000_test.rego b/checks/kubernetes/general/runs_with_GID_le_10000_test.rego index 4e7ca12a..9b64e3a4 100644 --- a/checks/kubernetes/general/runs_with_GID_le_10000_test.rego +++ b/checks/kubernetes/general/runs_with_GID_le_10000_test.rego @@ -62,3 +62,26 @@ test_low_gid_denied if { count(r) == 1 r[_].msg == "Container 'hello' of Pod 'hello-gid' should set 'securityContext.runAsGroup' > 10000" } + +test_pod_sec_ctx_low_gid_denied if { + r := deny with input as { + "apiVersion": "v1", + "kind": "Pod", + "metadata": {"name": "hello-gid"}, + "spec": { + "securityContext": {"runAsGroup": 100}, + "containers": [{ + "command": [ + "sh", + "-c", + "echo 'Hello' && sleep 1h", + ], + "image": "busybox", + "name": "hello", + }], + }, + } + + count(r) == 1 + r[_].msg == "Container 'hello' of Pod 'hello-gid' should set 'securityContext.runAsGroup' > 10000" +} diff --git a/checks/kubernetes/general/runs_with_UID_le_10000_test.rego b/checks/kubernetes/general/runs_with_UID_le_10000_test.rego index 9cec828c..042621a7 100644 --- a/checks/kubernetes/general/runs_with_UID_le_10000_test.rego +++ b/checks/kubernetes/general/runs_with_UID_le_10000_test.rego @@ -83,3 +83,26 @@ test_zero_uid_denied if { count(r) == 1 r[_].msg == "Container 'hello' of Pod 'hello-uid' should set 'securityContext.runAsUser' > 10000" } + +test_pod_sec_ctx_low_uid_denied if { + r := deny with input as { + "apiVersion": "v1", + "kind": "Pod", + "metadata": {"name": "hello-uid"}, + "spec": { + "securityContext": {"runAsUser": 100}, + "containers": [{ + "command": [ + "sh", + "-c", + "echo 'Hello' && sleep 1h", + ], + "image": "busybox", + "name": "hello", + }], + }, + } + + count(r) == 1 + r[_].msg == "Container 'hello' of Pod 'hello-uid' should set 'securityContext.runAsUser' > 10000" +} diff --git a/checks/kubernetes/pss/restricted/5_runtime_default_seccomp_profile_not_set_test.rego b/checks/kubernetes/pss/restricted/5_runtime_default_seccomp_profile_not_set_test.rego index a9355f6b..24483d03 100644 --- a/checks/kubernetes/pss/restricted/5_runtime_default_seccomp_profile_not_set_test.rego +++ b/checks/kubernetes/pss/restricted/5_runtime_default_seccomp_profile_not_set_test.rego @@ -23,7 +23,7 @@ test_pod_context_custom_profile_denied if { }, } - count(r) == 1 + count(r) == 2 } test_both_undefined_type_denied if { diff --git a/lib/kubernetes/kubernetes.rego b/lib/kubernetes/kubernetes.rego index 8018986c..8a8a8bb0 100644 --- a/lib/kubernetes/kubernetes.rego +++ b/lib/kubernetes/kubernetes.rego @@ -9,6 +9,8 @@ package lib.kubernetes import rego.v1 +import data.lib.k8s_sec_context + default is_gatekeeper := false is_gatekeeper if { @@ -39,8 +41,6 @@ default namespace := "default" namespace := object.metadata.namespace -#annotations = object.metadata.annotations - kind := object.kind is_pod if { @@ -94,7 +94,18 @@ split_image(image) := [image_name, tag] if { pod_containers(pod) := all_containers if { keys = {"containers", "initContainers"} - all_containers = [c | keys[k]; c = pod.spec[k][_]] + all_containers = [c | + keys[k] + some container in pod.spec[k] + c := json.patch( + container, + [{ + "op": "add", + "path": "securityContext", + "value": k8s_sec_context.resolve_container_sec_context(pod, container), + }], + ) + ] } containers contains container if { diff --git a/lib/kubernetes/kubernetes_test.rego b/lib/kubernetes/kubernetes_test.rego index 1104040b..01817476 100644 --- a/lib/kubernetes/kubernetes_test.rego +++ b/lib/kubernetes/kubernetes_test.rego @@ -229,18 +229,35 @@ test_containers if { test_containers := containers with input as { "apiVersion": "v1", "kind": "Pod", - "spec": {"containers": [{ - "command": [ - "sh", - "-c", - "echo 'Hello !' && sleep 1h", - ], - "image": "busybox", - "name": "hello-containers", - }]}, + "spec": { + "securityContext": { + "runAsUser": 1000, + "runAsGroup": 1001, + "fsGroup": 2000, + "supplementalGroups": [4000], + }, + "containers": [{ + "command": [ + "sh", + "-c", + "echo 'Hello !' && sleep 1h", + ], + "image": "busybox", + "name": "hello-containers", + "securityContext": { + "runAsGroup": 3000, + "allowPrivilegeEscalation": false, + }, + }], + }, } test_containers[_].name == "hello-containers" + test_containers[_].securityContext == { + "runAsUser": 1000, + "runAsGroup": 3000, + "allowPrivilegeEscalation": false, + } } test_isapiserver_has_valid_container if { diff --git a/lib/kubernetes/security_context.rego b/lib/kubernetes/security_context.rego new file mode 100644 index 00000000..8271a6e9 --- /dev/null +++ b/lib/kubernetes/security_context.rego @@ -0,0 +1,33 @@ +# METADATA +# custom: +# library: true +# input: +# selector: +# - type: kubernetes +# - type: rbac +package lib.k8s_sec_context + +import rego.v1 + +# Some fields are present in both SecurityContext and PodSecurityContext. +# When both are set, the values in SecurityContext take precedence. +# See https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.32/#securitycontext-v1-core +resolve_container_sec_context(pod, container) := object.union( + _inherited_sec_ctx(pod), + object.get(container, "securityContext", {}), +) + +_inherited_sec_ctx(pod) := {k: v | + ctx := object.get(pod, ["spec", "securityContext"], {}) + some k, v in ctx + k in _inherited_sec_ctx_fields +} + +_inherited_sec_ctx_fields := { + "runAsGroup", + "runAsNonRoot", + "runAsUser", + "seLinuxOptions", + "seccompProfile", + "windowsOptions", +}