From ec0af6fbda4860968c5c122cd52d6d4c04df958c Mon Sep 17 00:00:00 2001 From: Leonard Cohnen Date: Tue, 3 Dec 2024 23:33:01 +0100 Subject: [PATCH] policy: check the linux network namespace Peer pods have a linux namespace of type network. We want to make sure that all container in the same pod use the same namespace. Therefore, we add the first namespace path to the state and check all other requests against that. This commit also adds the corresponding integration test in the policy crate showcasing the benefit of having rust integration tests for the policy. Signed-off-by: Leonard Cohnen --- src/tools/genpolicy/rules.rego | 74 ++- src/tools/genpolicy/tests/main.rs | 5 + .../network_namespace/pod.yaml | 9 + .../network_namespace/testcases.json | 522 ++++++++++++++++++ 4 files changed, 602 insertions(+), 8 deletions(-) create mode 100644 src/tools/genpolicy/tests/testdata/createcontainer/network_namespace/pod.yaml create mode 100644 src/tools/genpolicy/tests/testdata/createcontainer/network_namespace/testcases.json diff --git a/src/tools/genpolicy/rules.rego b/src/tools/genpolicy/rules.rego index 6caba44357..1acfca19c8 100644 --- a/src/tools/genpolicy/rules.rego +++ b/src/tools/genpolicy/rules.rego @@ -86,7 +86,7 @@ CreateContainerRequest:= {"ops": ops, "allowed": true} { i_namespace := i_oci.Annotations[S_NAMESPACE_KEY] print ("CreateContainerRequest: p_namespace =", p_namespace, "i_namespace =", i_namespace) add_namespace_to_state := allow_namespace(p_namespace, i_namespace) - ops := concat_op_if_not_null(ops_builder1, add_namespace_to_state) + ops_builder2 := concat_op_if_not_null(ops_builder1, add_namespace_to_state) print("CreateContainerRequest: p Version =", p_oci.Version, "i Version =", i_oci.Version) p_oci.Version == i_oci.Version @@ -102,7 +102,10 @@ CreateContainerRequest:= {"ops": ops, "allowed": true} { p_devices := p_container.devices allow_devices(p_devices, i_devices) - allow_linux(p_oci, i_oci) + ret := allow_linux(ops_builder2, p_oci, i_oci) + ret.allowed + + ops := ret.ops print("CreateContainerRequest: true") } @@ -150,11 +153,12 @@ allow_namespace(p_namespace, i_namespace) = add_namespace { add_namespace := state_allows("namespace", i_namespace) } -# value hasn't been seen before, save it to state +# key hasn't been seen before, save key, value pair to state state_allows(key, value) = action { state := get_state() + print("state_allows 1: state[key] =", state[key], "value =", value) not state[key] - print("state_allows: saving to state key =", key, "value =", value) + print("state_allows 1: saving to state key =", key, "value =", value) path := get_state_path(key) action := { "op": "add", @@ -165,9 +169,11 @@ state_allows(key, value) = action { # value matches what's in state, allow it state_allows(key, value) = action { + print("state_allows 2: start") state := get_state() + print("state_allows 2: state[key] =", state[key], "value =", value) value == state[key] - print("state_allows: found key =", key, "value =", value, " in state") + print("state_allows 2: found key =", key, "value =", value, " in state") action := null } @@ -181,7 +187,7 @@ get_state_path(key) = path { path := concat("/", ["/pstate", key]) } -# Helper functions to conditionally concatenate if op is not null +# Helper functions to conditionally concatenate op is not null concat_op_if_not_null(ops, op) = result { op == null result := ops @@ -439,22 +445,74 @@ allow_devices(p_devices, i_devices) { print("allow_devices: true") } -allow_linux(p_oci, i_oci) { +allow_linux(state_ops, p_oci, i_oci) := {"ops": ops, "allowed": true} { p_namespaces := p_oci.Linux.Namespaces print("allow_linux: p namespaces =", p_namespaces) i_namespaces := i_oci.Linux.Namespaces print("allow_linux: i namespaces =", i_namespaces) - p_namespaces == i_namespaces + i_namespace_without_network := [obj | obj := i_namespaces[_]; obj.Type != "network"] + + print("allow_linux: i_namespace_without_network =", i_namespace_without_network) + + p_namespaces == i_namespace_without_network allow_masked_paths(p_oci, i_oci) allow_readonly_paths(p_oci, i_oci) allow_linux_devices(p_oci.Linux.Devices, i_oci.Linux.Devices) + ret := allow_network_namespace_start(state_ops, p_oci, i_oci) + ret.allowed + + ops := ret.ops print("allow_linux: true") } +# Retrieve the "network" namespace from the input data and pass it on for the +# network namespace policy checks. +allow_network_namespace_start(state_ops, p_oci, i_oci) := {"ops": ops, "allowed": true} { + print("allow_network_namespace start: start") + + p_namespaces := p_oci.Linux.Namespaces + print("allow_network_namespace start: p namespaces =", p_namespaces) + + i_namespaces := i_oci.Linux.Namespaces + print("allow_network_namespace start: i namespaces =", i_namespaces) + + # Return path of the "network" namespace + network_ns := [obj | obj := i_namespaces[_]; obj.Type == "network"] + + print("allow_network_namespace start: network_ns =", network_ns) + + ret := allow_network_namespace(state_ops, network_ns) + ret.allowed + + ops := ret.ops +} + +# This rule is when there's no network namespace in the input data. +allow_network_namespace(state_ops, network_ns) := {"ops": ops, "allowed": true} { + count(network_ns) == 0 + + network_ns_path = "" + + add_network_namespace_to_state := state_allows("network_namespace", network_ns_path) + ops := concat_op_if_not_null(state_ops, add_network_namespace_to_state) + + print("allow_network_namespace 1: true") +} + +# This rule is when there's exactly one network namespace in the input data. +allow_network_namespace(state_ops, network_ns) := {"ops": ops, "allowed": true} { + count(network_ns) == 1 + + add_network_namespace_to_state := state_allows("network_namespace", network_ns[0].Path) + ops := concat_op_if_not_null(state_ops, add_network_namespace_to_state) + + print("allow_network_namespace 2: true") +} + allow_masked_paths(p_oci, i_oci) { p_paths := p_oci.Linux.MaskedPaths print("allow_masked_paths 1: p_paths =", p_paths) diff --git a/src/tools/genpolicy/tests/main.rs b/src/tools/genpolicy/tests/main.rs index 40247c287a..1c4119bdf4 100644 --- a/src/tools/genpolicy/tests/main.rs +++ b/src/tools/genpolicy/tests/main.rs @@ -134,4 +134,9 @@ mod tests { async fn test_create_sandbox() { runtests::("createsandbox").await; } + + #[tokio::test] + async fn test_create_container_network_namespace() { + runtests::("createcontainer/network_namespace").await; + } } diff --git a/src/tools/genpolicy/tests/testdata/createcontainer/network_namespace/pod.yaml b/src/tools/genpolicy/tests/testdata/createcontainer/network_namespace/pod.yaml new file mode 100644 index 0000000000..7ac6554ed9 --- /dev/null +++ b/src/tools/genpolicy/tests/testdata/createcontainer/network_namespace/pod.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Pod +metadata: + name: dummy +spec: + runtimeClassName: kata-cc-isolation + containers: + - name: dummy + image: registry.k8s.io/pause:3.6@sha256:3d380ca8864549e74af4b29c10f9cb0956236dfb01c40ca076fb6c37253234db diff --git a/src/tools/genpolicy/tests/testdata/createcontainer/network_namespace/testcases.json b/src/tools/genpolicy/tests/testdata/createcontainer/network_namespace/testcases.json new file mode 100644 index 0000000000..5de73c5fcc --- /dev/null +++ b/src/tools/genpolicy/tests/testdata/createcontainer/network_namespace/testcases.json @@ -0,0 +1,522 @@ +[ + { + "description": "one network namespace", + "allowed": true, + "request": { + "OCI": { + "Version": "1.1.0", + "Annotations": { + "io.kubernetes.cri.sandbox-name": "dummy", + "io.kubernetes.cri.sandbox-namespace": "default", + "io.kubernetes.cri.container-type": "sandbox", + "io.katacontainers.pkg.oci.container_type": "pod_sandbox", + "nerdctl/network-namespace": "/var/run/netns/cni-00000000-0000-0000-0000-000000000000", + "io.kubernetes.cri.sandbox-log-directory": "/var/log/pods/default_dummy_00000000-0000-0000-0000-000000000000", + "io.katacontainers.pkg.oci.bundle_path": "/run/containerd/io.containerd.runtime.v2.task/k8s.io/bundle-id", + "io.kubernetes.cri.sandbox-id": "0000000000000000000000000000000000000000000000000000000000000000" + }, + "Linux": { + "GIDMappings": [], + "MountLabel": "", + "Resources": { + "Devices": [] + }, + "RootfsPropagation": "", + "Namespaces": [ + { + "Path": "", + "Type": "ipc" + }, + { + "Path": "", + "Type": "uts" + }, + { + "Path": "", + "Type": "mount" + }, + { + "Path": "/run/netns/podns", + "Type": "network" + } + ], + "MaskedPaths": [ + "/proc/acpi", + "/proc/asound", + "/proc/kcore", + "/proc/keys", + "/proc/latency_stats", + "/proc/timer_list", + "/proc/timer_stats", + "/proc/sched_debug", + "/sys/firmware", + "/proc/scsi" + ], + "ReadonlyPaths": [ + "/proc/bus", + "/proc/fs", + "/proc/irq", + "/proc/sys", + "/proc/sysrq-trigger" + ] + }, + "Process": { + "SelinuxLabel": "", + "User": { + "Username": "", + "UID": 65535 + }, + "Args": [ + "/pause" + ], + "Cwd": "/", + "NoNewPrivileges": true, + "Capabilities": { + "Ambient": [], + "Bounding": [ + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_FSETID", + "CAP_FOWNER", + "CAP_MKNOD", + "CAP_NET_RAW", + "CAP_SETGID", + "CAP_SETUID", + "CAP_SETFCAP", + "CAP_SETPCAP", + "CAP_NET_BIND_SERVICE", + "CAP_SYS_CHROOT", + "CAP_KILL", + "CAP_AUDIT_WRITE" + ], + "Effective": [ + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_FSETID", + "CAP_FOWNER", + "CAP_MKNOD", + "CAP_NET_RAW", + "CAP_SETGID", + "CAP_SETUID", + "CAP_SETFCAP", + "CAP_SETPCAP", + "CAP_NET_BIND_SERVICE", + "CAP_SYS_CHROOT", + "CAP_KILL", + "CAP_AUDIT_WRITE" + ], + "Permitted": [ + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_FSETID", + "CAP_FOWNER", + "CAP_MKNOD", + "CAP_NET_RAW", + "CAP_SETGID", + "CAP_SETUID", + "CAP_SETFCAP", + "CAP_SETPCAP", + "CAP_NET_BIND_SERVICE", + "CAP_SYS_CHROOT", + "CAP_KILL", + "CAP_AUDIT_WRITE" + ] + } + }, + "Root": { + "Readonly": true, + "Path": "/run/kata-containers/shared/containers/bundle-id/rootfs" + } + } + } + }, + { + "description": "same network namespace", + "allowed": true, + "request": { + "OCI": { + "Version": "1.1.0", + "Annotations": { + "io.kubernetes.cri.sandbox-name": "dummy", + "io.kubernetes.cri.sandbox-namespace": "default", + "io.kubernetes.cri.container-type": "sandbox", + "io.katacontainers.pkg.oci.container_type": "pod_sandbox", + "nerdctl/network-namespace": "/var/run/netns/cni-00000000-0000-0000-0000-000000000000", + "io.kubernetes.cri.sandbox-log-directory": "/var/log/pods/default_dummy_00000000-0000-0000-0000-000000000000", + "io.katacontainers.pkg.oci.bundle_path": "/run/containerd/io.containerd.runtime.v2.task/k8s.io/bundle-id", + "io.kubernetes.cri.sandbox-id": "0000000000000000000000000000000000000000000000000000000000000000" + }, + "Linux": { + "GIDMappings": [], + "MountLabel": "", + "Resources": { + "Devices": [] + }, + "RootfsPropagation": "", + "Namespaces": [ + { + "Path": "", + "Type": "ipc" + }, + { + "Path": "", + "Type": "uts" + }, + { + "Path": "", + "Type": "mount" + }, + { + "Path": "/run/netns/podns", + "Type": "network" + } + ], + "MaskedPaths": [ + "/proc/acpi", + "/proc/asound", + "/proc/kcore", + "/proc/keys", + "/proc/latency_stats", + "/proc/timer_list", + "/proc/timer_stats", + "/proc/sched_debug", + "/sys/firmware", + "/proc/scsi" + ], + "ReadonlyPaths": [ + "/proc/bus", + "/proc/fs", + "/proc/irq", + "/proc/sys", + "/proc/sysrq-trigger" + ] + }, + "Process": { + "SelinuxLabel": "", + "User": { + "Username": "", + "UID": 65535 + }, + "Args": [ + "/pause" + ], + "Cwd": "/", + "NoNewPrivileges": true, + "Capabilities": { + "Ambient": [], + "Bounding": [ + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_FSETID", + "CAP_FOWNER", + "CAP_MKNOD", + "CAP_NET_RAW", + "CAP_SETGID", + "CAP_SETUID", + "CAP_SETFCAP", + "CAP_SETPCAP", + "CAP_NET_BIND_SERVICE", + "CAP_SYS_CHROOT", + "CAP_KILL", + "CAP_AUDIT_WRITE" + ], + "Effective": [ + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_FSETID", + "CAP_FOWNER", + "CAP_MKNOD", + "CAP_NET_RAW", + "CAP_SETGID", + "CAP_SETUID", + "CAP_SETFCAP", + "CAP_SETPCAP", + "CAP_NET_BIND_SERVICE", + "CAP_SYS_CHROOT", + "CAP_KILL", + "CAP_AUDIT_WRITE" + ], + "Permitted": [ + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_FSETID", + "CAP_FOWNER", + "CAP_MKNOD", + "CAP_NET_RAW", + "CAP_SETGID", + "CAP_SETUID", + "CAP_SETFCAP", + "CAP_SETPCAP", + "CAP_NET_BIND_SERVICE", + "CAP_SYS_CHROOT", + "CAP_KILL", + "CAP_AUDIT_WRITE" + ] + } + }, + "Root": { + "Readonly": true, + "Path": "/run/kata-containers/shared/containers/bundle-id/rootfs" + } + } + } + }, + { + "description": "no network namespace", + "allowed": false, + "request": { + "OCI": { + "Version": "1.1.0", + "Annotations": { + "io.kubernetes.cri.sandbox-name": "dummy", + "io.kubernetes.cri.sandbox-namespace": "default", + "io.kubernetes.cri.container-type": "sandbox", + "io.katacontainers.pkg.oci.container_type": "pod_sandbox", + "nerdctl/network-namespace": "/var/run/netns/cni-00000000-0000-0000-0000-000000000000", + "io.kubernetes.cri.sandbox-log-directory": "/var/log/pods/default_dummy_00000000-0000-0000-0000-000000000000", + "io.katacontainers.pkg.oci.bundle_path": "/run/containerd/io.containerd.runtime.v2.task/k8s.io/bundle-id", + "io.kubernetes.cri.sandbox-id": "0000000000000000000000000000000000000000000000000000000000000000" + }, + "Linux": { + "GIDMappings": [], + "MountLabel": "", + "Resources": { + "Devices": [] + }, + "RootfsPropagation": "", + "Namespaces": [ + { + "Path": "", + "Type": "ipc" + }, + { + "Path": "", + "Type": "uts" + }, + { + "Path": "", + "Type": "mount" + } + ], + "MaskedPaths": [ + "/proc/acpi", + "/proc/asound", + "/proc/kcore", + "/proc/keys", + "/proc/latency_stats", + "/proc/timer_list", + "/proc/timer_stats", + "/proc/sched_debug", + "/sys/firmware", + "/proc/scsi" + ], + "ReadonlyPaths": [ + "/proc/bus", + "/proc/fs", + "/proc/irq", + "/proc/sys", + "/proc/sysrq-trigger" + ] + }, + "Process": { + "SelinuxLabel": "", + "User": { + "Username": "", + "UID": 65535 + }, + "Args": [ + "/pause" + ], + "Cwd": "/", + "NoNewPrivileges": true, + "Capabilities": { + "Ambient": [], + "Bounding": [ + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_FSETID", + "CAP_FOWNER", + "CAP_MKNOD", + "CAP_NET_RAW", + "CAP_SETGID", + "CAP_SETUID", + "CAP_SETFCAP", + "CAP_SETPCAP", + "CAP_NET_BIND_SERVICE", + "CAP_SYS_CHROOT", + "CAP_KILL", + "CAP_AUDIT_WRITE" + ], + "Effective": [ + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_FSETID", + "CAP_FOWNER", + "CAP_MKNOD", + "CAP_NET_RAW", + "CAP_SETGID", + "CAP_SETUID", + "CAP_SETFCAP", + "CAP_SETPCAP", + "CAP_NET_BIND_SERVICE", + "CAP_SYS_CHROOT", + "CAP_KILL", + "CAP_AUDIT_WRITE" + ], + "Permitted": [ + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_FSETID", + "CAP_FOWNER", + "CAP_MKNOD", + "CAP_NET_RAW", + "CAP_SETGID", + "CAP_SETUID", + "CAP_SETFCAP", + "CAP_SETPCAP", + "CAP_NET_BIND_SERVICE", + "CAP_SYS_CHROOT", + "CAP_KILL", + "CAP_AUDIT_WRITE" + ] + } + }, + "Root": { + "Readonly": true, + "Path": "/run/kata-containers/shared/containers/bundle-id/rootfs" + } + } + } + }, + { + "description": "different network namespace", + "allowed": false, + "request": { + "OCI": { + "Version": "1.1.0", + "Annotations": { + "io.kubernetes.cri.sandbox-name": "dummy", + "io.kubernetes.cri.sandbox-namespace": "default", + "io.kubernetes.cri.container-type": "sandbox", + "io.katacontainers.pkg.oci.container_type": "pod_sandbox", + "nerdctl/network-namespace": "/var/run/netns/cni-00000000-0000-0000-0000-000000000000", + "io.kubernetes.cri.sandbox-log-directory": "/var/log/pods/default_dummy_00000000-0000-0000-0000-000000000000", + "io.katacontainers.pkg.oci.bundle_path": "/run/containerd/io.containerd.runtime.v2.task/k8s.io/bundle-id", + "io.kubernetes.cri.sandbox-id": "0000000000000000000000000000000000000000000000000000000000000000" + }, + "Linux": { + "GIDMappings": [], + "MountLabel": "", + "Resources": { + "Devices": [] + }, + "RootfsPropagation": "", + "Namespaces": [ + { + "Path": "", + "Type": "ipc" + }, + { + "Path": "", + "Type": "uts" + }, + { + "Path": "", + "Type": "mount" + }, + { + "Path": "/run/netns/podns-diff", + "Type": "network" + } + ], + "MaskedPaths": [ + "/proc/acpi", + "/proc/asound", + "/proc/kcore", + "/proc/keys", + "/proc/latency_stats", + "/proc/timer_list", + "/proc/timer_stats", + "/proc/sched_debug", + "/sys/firmware", + "/proc/scsi" + ], + "ReadonlyPaths": [ + "/proc/bus", + "/proc/fs", + "/proc/irq", + "/proc/sys", + "/proc/sysrq-trigger" + ] + }, + "Process": { + "SelinuxLabel": "", + "User": { + "Username": "", + "UID": 65535 + }, + "Args": [ + "/pause" + ], + "Cwd": "/", + "NoNewPrivileges": true, + "Capabilities": { + "Ambient": [], + "Bounding": [ + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_FSETID", + "CAP_FOWNER", + "CAP_MKNOD", + "CAP_NET_RAW", + "CAP_SETGID", + "CAP_SETUID", + "CAP_SETFCAP", + "CAP_SETPCAP", + "CAP_NET_BIND_SERVICE", + "CAP_SYS_CHROOT", + "CAP_KILL", + "CAP_AUDIT_WRITE" + ], + "Effective": [ + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_FSETID", + "CAP_FOWNER", + "CAP_MKNOD", + "CAP_NET_RAW", + "CAP_SETGID", + "CAP_SETUID", + "CAP_SETFCAP", + "CAP_SETPCAP", + "CAP_NET_BIND_SERVICE", + "CAP_SYS_CHROOT", + "CAP_KILL", + "CAP_AUDIT_WRITE" + ], + "Permitted": [ + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_FSETID", + "CAP_FOWNER", + "CAP_MKNOD", + "CAP_NET_RAW", + "CAP_SETGID", + "CAP_SETUID", + "CAP_SETFCAP", + "CAP_SETPCAP", + "CAP_NET_BIND_SERVICE", + "CAP_SYS_CHROOT", + "CAP_KILL", + "CAP_AUDIT_WRITE" + ] + } + }, + "Root": { + "Readonly": true, + "Path": "/run/kata-containers/shared/containers/bundle-id/rootfs" + } + } + } + } +] \ No newline at end of file