From c6fb8cf044b35bfe8deb4a27176155cf1d62e1a7 Mon Sep 17 00:00:00 2001 From: Anand Krishnamoorthi <35780660+anakrish@users.noreply.github.com> Date: Sat, 27 Apr 2024 06:31:09 -0700 Subject: [PATCH] Add tests for kata containers policies (#221) closes #220 Signed-off-by: Anand Krishnamoorthi --- .github/workflows/pr.yml | 4 + Cargo.toml | 5 + scripts/pre-push | 1 + tests/kata/data/k8s-policy-job/inputs.txt | 34 + tests/kata/data/k8s-policy-job/output.json | 19 + tests/kata/data/k8s-policy-job/policy.rego | 1652 +++++++++++++++++++ tests/kata/data/k8s-policy-pod/inputs.txt | 34 + tests/kata/data/k8s-policy-pod/output.json | 19 + tests/kata/data/k8s-policy-pod/policy.rego | 1653 +++++++++++++++++++ tests/kata/data/k8s-policy-rc/inputs.txt | 33 + tests/kata/data/k8s-policy-rc/output.json | 19 + tests/kata/data/k8s-policy-rc/policy.rego | 1695 ++++++++++++++++++++ tests/{coco => kata}/data/large.rego | 0 tests/kata/main.rs | 115 ++ tests/lexer/mod.rs | 2 +- 15 files changed, 5284 insertions(+), 1 deletion(-) create mode 100644 tests/kata/data/k8s-policy-job/inputs.txt create mode 100644 tests/kata/data/k8s-policy-job/output.json create mode 100644 tests/kata/data/k8s-policy-job/policy.rego create mode 100644 tests/kata/data/k8s-policy-pod/inputs.txt create mode 100644 tests/kata/data/k8s-policy-pod/output.json create mode 100644 tests/kata/data/k8s-policy-pod/policy.rego create mode 100644 tests/kata/data/k8s-policy-rc/inputs.txt create mode 100644 tests/kata/data/k8s-policy-rc/output.json create mode 100644 tests/kata/data/k8s-policy-rc/policy.rego rename tests/{coco => kata}/data/large.rego (100%) create mode 100644 tests/kata/main.rs diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index dcc4257..14ae72f 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -30,6 +30,8 @@ jobs: run: cargo test -r --verbose - name: Run tests (ACI) run: cargo test -r --test aci + - name: Run tests (KATA) + run: cargo test -r --test kata - name: Run tests (OPA Conformance) run: >- cargo test -r --test opa --features opa-testutil,serde_json/arbitrary_precision -- $(tr '\n' ' ' < tests/opa.passing) @@ -39,6 +41,8 @@ jobs: run: cargo test -r --verbose --target x86_64-unknown-linux-musl - name: Run tests (MUSL ACI) run: cargo test -r --test aci --target x86_64-unknown-linux-musl + - name: Run tests (KATA ACI) + run: cargo test -r --test kata --target x86_64-unknown-linux-musl - name: Run tests (MUSL OPA Conformance) run: >- cargo test -r --test opa --features opa-testutil,serde_json/arbitrary_precision --target x86_64-unknown-linux-musl -- $(tr '\n' ' ' < tests/opa.passing) diff --git a/Cargo.toml b/Cargo.toml index 351d79c..05345fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -124,6 +124,11 @@ name="aci" harness=false test=false +[[test]] +name="kata" +harness=false +test=false + [package.metadata.docs.rs] # To build locally: # RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features --no-deps diff --git a/scripts/pre-push b/scripts/pre-push index 5e36118..edb9ef6 100755 --- a/scripts/pre-push +++ b/scripts/pre-push @@ -15,6 +15,7 @@ if [ -f Cargo.toml ]; then # Ensure that all tests pass cargo test -r cargo test -r --test aci + cargo test -r --test kata # Ensure that OPA conformance tests don't regress. cargo test -r --features opa-testutil,serde_json/arbitrary_precision --test opa -- $(tr '\n' ' ' < tests/opa.passing) diff --git a/tests/kata/data/k8s-policy-job/inputs.txt b/tests/kata/data/k8s-policy-job/inputs.txt new file mode 100644 index 0000000..7dbafe4 --- /dev/null +++ b/tests/kata/data/k8s-policy-job/inputs.txt @@ -0,0 +1,34 @@ +["ep":"AllowRequestsFailingPolicy",{}], + +["ep":"UpdateInterfaceRequest",{"interface":{"device":"eth0","name":"eth0","IPAddresses":[{"family":0,"address":"10.244.2.67","mask":"24"},{"family":1,"address":"fe80::e0a5:89ff:feb9:9950","mask":"64"}],"mtu":1500,"hwAddr":"e2:a5:89:b9:99:50","pciPath":"","type_":"","raw_flags":0}}], + +["ep":"UpdateRoutesRequest",{"routes":{"Routes":[{"dest":"","gateway":"10.244.2.1","device":"eth0","source":"","scope":0,"family":0}]}}], + +["ep":"CreateSandboxRequest",{"hostname":"policy-job-dx6cb","dns":["search kata-containers-k8s-tests.svc.cluster.local svc.cluster.local cluster.local iete1orsbdmu5ia1kq2du43hqe.bx.internal.cloudapp.net","nameserver 10.0.0.10","options ndots:5",""],"storages":[{"driver":"virtio-fs","driver_options":[],"source":"kataShared","fstype":"virtiofs","options":[],"mount_point":"/run/kata-containers/shared/containers/","fs_group":null},{"driver":"ephemeral","driver_options":[],"source":"shm","fstype":"tmpfs","options":["noexec","nosuid","nodev","mode=1777","size=67108864"],"mount_point":"/run/kata-containers/sandbox/shm","fs_group":null}],"sandbox_pidns":false,"sandbox_id":"d2bc5590b33176f997388b25f600080d2276cbd08ad134ace2d254260ac864e4","guest_hook_path":"","kernel_modules":[]}], + +["ep":"GuestDetailsRequest",{"mem_block_size":true,"mem_hotplug_probe":true}], + +["ep":"CreateContainerRequest",{"container_id":"d2bc5590b33176f997388b25f600080d2276cbd08ad134ace2d254260ac864e4","exec_id":"d2bc5590b33176f997388b25f600080d2276cbd08ad134ace2d254260ac864e4","string_user":null,"devices":[],"storages":[],"OCI":{"Version":"1.1.0","Process":{"Terminal":false,"ConsoleSize":null,"User":{"UID":65535,"GID":65535,"AdditionalGids":[65535],"Username":""},"Args":["/pause"],"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cwd":"/","Capabilities":{"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"],"Inheritable":[],"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"],"Ambient":[]},"Rlimits":[],"NoNewPrivileges":true,"ApparmorProfile":"","OOMScoreAdj":-998,"SelinuxLabel":""},"Root":{"Path":"/run/kata-containers/shared/containers/d2bc5590b33176f997388b25f600080d2276cbd08ad134ace2d254260ac864e4/rootfs","Readonly":true},"Hostname":"policy-job-dx6cb","Mounts":[{"destination":"/proc","source":"proc","type_":"proc","options":["nosuid","noexec","nodev"]},{"destination":"/dev","source":"tmpfs","type_":"tmpfs","options":["nosuid","strictatime","mode=755","size=65536k"]},{"destination":"/dev/pts","source":"devpts","type_":"devpts","options":["nosuid","noexec","newinstance","ptmxmode=0666","mode=0620","gid=5"]},{"destination":"/dev/mqueue","source":"mqueue","type_":"mqueue","options":["nosuid","noexec","nodev"]},{"destination":"/sys","source":"sysfs","type_":"sysfs","options":["nosuid","noexec","nodev","ro"]},{"destination":"/dev/shm","source":"/run/kata-containers/sandbox/shm","type_":"bind","options":["rbind"]},{"destination":"/etc/resolv.conf","source":"/run/kata-containers/shared/containers/d2bc5590b33176f997388b25f600080d2276cbd08ad134ace2d254260ac864e4-c45e5b9869248a78-resolv.conf","type_":"bind","options":["rbind","ro","nosuid","nodev","noexec"]}],"Hooks":null,"Annotations":{"io.katacontainers.pkg.oci.bundle_path":"/run/containerd/io.containerd.runtime.v2.task/k8s.io/d2bc5590b33176f997388b25f600080d2276cbd08ad134ace2d254260ac864e4","io.kubernetes.cri.container-type":"sandbox","io.kubernetes.cri.sandbox-cpu-shares":"2","nerdctl/network-namespace":"/var/run/netns/cni-92275a86-e0ea-c2c4-9e0c-4ac1ebe428eb","io.katacontainers.pkg.oci.container_type":"pod_sandbox","io.kubernetes.cri.sandbox-uid":"392939d5-72a0-4421-9cd7-1e89c3f86256","io.kubernetes.cri.sandbox-name":"policy-job-dx6cb","io.kubernetes.cri.sandbox-log-directory":"/var/log/pods/kata-containers-k8s-tests_policy-job-dx6cb_392939d5-72a0-4421-9cd7-1e89c3f86256","io.kubernetes.cri.sandbox-cpu-period":"100000","io.kubernetes.cri.sandbox-memory":"0","io.kubernetes.cri.sandbox-namespace":"kata-containers-k8s-tests","io.kubernetes.cri.sandbox-cpu-quota":"0","io.kubernetes.cri.sandbox-id":"d2bc5590b33176f997388b25f600080d2276cbd08ad134ace2d254260ac864e4"},"Linux":{"UIDMappings":[],"GIDMappings":[],"Sysctl":{},"Resources":{"Devices":[],"Memory":null,"CPU":{"Shares":2,"Quota":0,"Period":0,"RealtimeRuntime":0,"RealtimePeriod":0,"Cpus":"","Mems":""},"Pids":null,"BlockIO":null,"HugepageLimits":[],"Network":null},"CgroupsPath":"kubepods-besteffort-pod392939d5_72a0_4421_9cd7_1e89c3f86256.slice:cri-containerd:d2bc5590b33176f997388b25f600080d2276cbd08ad134ace2d254260ac864e4","Namespaces":[{"Type":"ipc","Path":""},{"Type":"uts","Path":""},{"Type":"mount","Path":""}],"Devices":[],"Seccomp":null,"RootfsPropagation":"","MaskedPaths":["/proc/acpi","/proc/asound","/proc/kcore","/proc/keys","/proc/latency_stats","/proc/timer_list","/proc/timer_stats","/proc/sched_debug","/sys/firmware","/sys/devices/virtual/powercap","/proc/scsi"],"ReadonlyPaths":["/proc/bus","/proc/fs","/proc/irq","/proc/sys","/proc/sysrq-trigger"],"MountLabel":"","IntelRdt":null},"Solaris":null,"Windows":null},"sandbox_pidns":false,"shared_mounts":[],"stdin_port":0,"stdout_port":0,"stderr_port":0}], + +["ep":"OnlineCPUMemRequest",{"wait":false,"nb_cpus":0,"cpu_only":false}], + +["ep":"UpdateEphemeralMountsRequest",{"storages":[]}], + +["ep":"StartContainerRequest",{"container_id":"d2bc5590b33176f997388b25f600080d2276cbd08ad134ace2d254260ac864e4"}], + +["ep":"WaitProcessRequest",{"container_id":"d2bc5590b33176f997388b25f600080d2276cbd08ad134ace2d254260ac864e4","exec_id":"d2bc5590b33176f997388b25f600080d2276cbd08ad134ace2d254260ac864e4"}], + +["ep":"GetOOMEventRequest",{}], + +["ep":"CreateContainerRequest",{"container_id":"92fe9b0523a4ef0e39cdd2a509bd6a13639571f50d9d7df74f6dbab37cae23fa","exec_id":"92fe9b0523a4ef0e39cdd2a509bd6a13639571f50d9d7df74f6dbab37cae23fa","string_user":null,"devices":[],"storages":[],"OCI":{"Version":"1.1.0","Process":{"Terminal":false,"ConsoleSize":null,"User":{"UID":0,"GID":0,"AdditionalGids":[0,10],"Username":""},"Args":["/bin/sh","-c","sleep 120"],"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","HOSTNAME=policy-job-dx6cb","var1=val1","KUBERNETES_PORT_443_TCP_PROTO=tcp","KUBERNETES_PORT_443_TCP_PORT=443","KUBERNETES_PORT_443_TCP_ADDR=10.0.0.1","KUBERNETES_SERVICE_HOST=10.0.0.1","KUBERNETES_SERVICE_PORT=443","KUBERNETES_SERVICE_PORT_HTTPS=443","KUBERNETES_PORT=tcp://10.0.0.1:443","KUBERNETES_PORT_443_TCP=tcp://10.0.0.1:443"],"Cwd":"/","Capabilities":{"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"],"Inheritable":[],"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"],"Ambient":[]},"Rlimits":[],"NoNewPrivileges":false,"ApparmorProfile":"cri-containerd.apparmor.d","OOMScoreAdj":1000,"SelinuxLabel":""},"Root":{"Path":"/run/kata-containers/shared/containers/92fe9b0523a4ef0e39cdd2a509bd6a13639571f50d9d7df74f6dbab37cae23fa/rootfs","Readonly":true},"Hostname":"","Mounts":[{"destination":"/proc","source":"proc","type_":"proc","options":["nosuid","noexec","nodev"]},{"destination":"/dev","source":"tmpfs","type_":"tmpfs","options":["nosuid","strictatime","mode=755","size=65536k"]},{"destination":"/dev/pts","source":"devpts","type_":"devpts","options":["nosuid","noexec","newinstance","ptmxmode=0666","mode=0620","gid=5"]},{"destination":"/dev/mqueue","source":"mqueue","type_":"mqueue","options":["nosuid","noexec","nodev"]},{"destination":"/sys","source":"sysfs","type_":"sysfs","options":["nosuid","noexec","nodev","ro"]},{"destination":"/sys/fs/cgroup","source":"cgroup","type_":"cgroup","options":["nosuid","noexec","nodev","relatime","ro"]},{"destination":"/etc/hosts","source":"/run/kata-containers/shared/containers/92fe9b0523a4ef0e39cdd2a509bd6a13639571f50d9d7df74f6dbab37cae23fa-4195c832f3a3ed66-hosts","type_":"bind","options":["rbind","rprivate","rw"]},{"destination":"/dev/termination-log","source":"/run/kata-containers/shared/containers/92fe9b0523a4ef0e39cdd2a509bd6a13639571f50d9d7df74f6dbab37cae23fa-0f5544bf9955d14d-termination-log","type_":"bind","options":["rbind","rprivate","rw"]},{"destination":"/etc/hostname","source":"/run/kata-containers/shared/containers/92fe9b0523a4ef0e39cdd2a509bd6a13639571f50d9d7df74f6dbab37cae23fa-2f1945da6e7bade6-hostname","type_":"bind","options":["rbind","rprivate","ro"]},{"destination":"/etc/resolv.conf","source":"/run/kata-containers/shared/containers/92fe9b0523a4ef0e39cdd2a509bd6a13639571f50d9d7df74f6dbab37cae23fa-22d6d391f26cb161-resolv.conf","type_":"bind","options":["rbind","rprivate","ro"]},{"destination":"/dev/shm","source":"/run/kata-containers/sandbox/shm","type_":"bind","options":["rbind"]},{"destination":"/var/run/secrets/kubernetes.io/serviceaccount","source":"/run/kata-containers/shared/containers/92fe9b0523a4ef0e39cdd2a509bd6a13639571f50d9d7df74f6dbab37cae23fa-aa0738a33eb59dbf-serviceaccount","type_":"bind","options":["rbind","rprivate","ro"]}],"Hooks":null,"Annotations":{"io.kubernetes.cri.container-name":"hello","io.katacontainers.pkg.oci.bundle_path":"/run/containerd/io.containerd.runtime.v2.task/k8s.io/92fe9b0523a4ef0e39cdd2a509bd6a13639571f50d9d7df74f6dbab37cae23fa","io.kubernetes.cri.container-type":"container","io.kubernetes.cri.sandbox-name":"policy-job-dx6cb","io.kubernetes.cri.image-name":"quay.io/prometheus/busybox:latest","io.kubernetes.cri.sandbox-uid":"392939d5-72a0-4421-9cd7-1e89c3f86256","io.katacontainers.pkg.oci.container_type":"pod_container","io.kubernetes.cri.sandbox-namespace":"kata-containers-k8s-tests","io.kubernetes.cri.sandbox-id":"d2bc5590b33176f997388b25f600080d2276cbd08ad134ace2d254260ac864e4"},"Linux":{"UIDMappings":[],"GIDMappings":[],"Sysctl":{},"Resources":{"Devices":[],"Memory":{"Limit":0,"Reservation":0,"Swap":0,"Kernel":0,"KernelTCP":0,"Swappiness":0,"DisableOOMKiller":false},"CPU":{"Shares":2,"Quota":0,"Period":100000,"RealtimeRuntime":0,"RealtimePeriod":0,"Cpus":"","Mems":""},"Pids":null,"BlockIO":null,"HugepageLimits":[],"Network":null},"CgroupsPath":"kubepods-besteffort-pod392939d5_72a0_4421_9cd7_1e89c3f86256.slice:cri-containerd:92fe9b0523a4ef0e39cdd2a509bd6a13639571f50d9d7df74f6dbab37cae23fa","Namespaces":[{"Type":"ipc","Path":""},{"Type":"uts","Path":""},{"Type":"mount","Path":""}],"Devices":[],"Seccomp":null,"RootfsPropagation":"","MaskedPaths":["/proc/asound","/proc/acpi","/proc/kcore","/proc/keys","/proc/latency_stats","/proc/timer_list","/proc/timer_stats","/proc/sched_debug","/proc/scsi","/sys/firmware"],"ReadonlyPaths":["/proc/bus","/proc/fs","/proc/irq","/proc/sys","/proc/sysrq-trigger"],"MountLabel":"","IntelRdt":null},"Solaris":null,"Windows":null},"sandbox_pidns":false,"shared_mounts":[],"stdin_port":0,"stdout_port":0,"stderr_port":0}], + +["ep":"OnlineCPUMemRequest",{"wait":false,"nb_cpus":0,"cpu_only":false}], + +["ep":"UpdateEphemeralMountsRequest",{"storages":[]}], + +["ep":"StartContainerRequest",{"container_id":"92fe9b0523a4ef0e39cdd2a509bd6a13639571f50d9d7df74f6dbab37cae23fa"}], + +["ep":"OnlineCPUMemRequest",{"wait":false,"nb_cpus":0,"cpu_only":false}], + +["ep":"UpdateEphemeralMountsRequest",{"storages":[]}], + diff --git a/tests/kata/data/k8s-policy-job/output.json b/tests/kata/data/k8s-policy-job/output.json new file mode 100644 index 0000000..c0f5c42 --- /dev/null +++ b/tests/kata/data/k8s-policy-job/output.json @@ -0,0 +1,19 @@ +[ + false, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true +] \ No newline at end of file diff --git a/tests/kata/data/k8s-policy-job/policy.rego b/tests/kata/data/k8s-policy-job/policy.rego new file mode 100644 index 0000000..b131baa --- /dev/null +++ b/tests/kata/data/k8s-policy-job/policy.rego @@ -0,0 +1,1652 @@ +# Copyright (c) 2023 Microsoft Corporation +# +# SPDX-License-Identifier: Apache-2.0 +# +package agent_policy + +import future.keywords.in +import future.keywords.every + +# Default values, returned by OPA when rules cannot be evaluated to true. +default AddARPNeighborsRequest := false +default AddSwapRequest := false +default CloseStdinRequest := false +default CopyFileRequest := false +default CreateContainerRequest := false +default CreateSandboxRequest := true +default DestroySandboxRequest := true +default ExecProcessRequest := false +default GetOOMEventRequest := true +default GuestDetailsRequest := true +default ListInterfacesRequest := false +default ListRoutesRequest := false +default MemHotplugByProbeRequest := false +default OnlineCPUMemRequest := true +default PauseContainerRequest := false +default ReadStreamRequest := false +default RemoveContainerRequest := true +default RemoveStaleVirtiofsShareMountsRequest := true +default ReseedRandomDevRequest := false +default ResumeContainerRequest := false +default SetGuestDateTimeRequest := false +default SetPolicyRequest := false +default SignalProcessRequest := true +default StartContainerRequest := true +default StartTracingRequest := false +default StatsContainerRequest := true +default StopTracingRequest := false +default TtyWinResizeRequest := true +default UpdateContainerRequest := false +default UpdateEphemeralMountsRequest := true +default UpdateInterfaceRequest := true +default UpdateRoutesRequest := true +default WaitProcessRequest := true +default WriteStreamRequest := false + +# AllowRequestsFailingPolicy := true configures the Agent to *allow any +# requests causing a policy failure*. This is an unsecure configuration +# but is useful for allowing unsecure pods to start, then connect to +# them and inspect OPA logs for the root cause of a failure. +default AllowRequestsFailingPolicy := false + +CreateContainerRequest { + i_oci := input.OCI + i_storages := input.storages + + some p_container in policy_data.containers + print("======== CreateContainerRequest: trying next policy container") + + p_pidns := p_container.sandbox_pidns + i_pidns := input.sandbox_pidns + print("CreateContainerRequest: p_pidns =", p_pidns, "i_pidns =", i_pidns) + p_pidns == i_pidns + + p_oci := p_container.OCI + + print("CreateContainerRequest: p Version =", p_oci.Version, "i Version =", i_oci.Version) + p_oci.Version == i_oci.Version + + print("CreateContainerRequest: p Readonly =", p_oci.Root.Readonly, "i Readonly =", i_oci.Root.Readonly) + p_oci.Root.Readonly == i_oci.Root.Readonly + + allow_anno(p_oci, i_oci) + + p_storages := p_container.storages + allow_by_anno(p_oci, i_oci, p_storages, i_storages) + + allow_linux(p_oci, i_oci) + + print("CreateContainerRequest: true") +} + +# Reject unexpected annotations. +allow_anno(p_oci, i_oci) { + print("allow_anno 1: start") + + not i_oci.Annotations + + print("allow_anno 1: true") +} +allow_anno(p_oci, i_oci) { + print("allow_anno 2: p Annotations =", p_oci.Annotations) + print("allow_anno 2: i Annotations =", i_oci.Annotations) + + i_keys := object.keys(i_oci.Annotations) + print("allow_anno 2: i keys =", i_keys) + + every i_key in i_keys { + allow_anno_key(i_key, p_oci) + } + + print("allow_anno 2: true") +} + +allow_anno_key(i_key, p_oci) { + print("allow_anno_key 1: i key =", i_key) + + startswith(i_key, "io.kubernetes.cri.") + + print("allow_anno_key 1: true") +} +allow_anno_key(i_key, p_oci) { + print("allow_anno_key 2: i key =", i_key) + + some p_key, _ in p_oci.Annotations + p_key == i_key + + print("allow_anno_key 2: true") +} + +# Get the value of the "io.kubernetes.cri.sandbox-name" annotation and +# correlate it with other annotations and process fields. +allow_by_anno(p_oci, i_oci, p_storages, i_storages) { + print("allow_by_anno 1: start") + + s_name := "io.kubernetes.cri.sandbox-name" + + not p_oci.Annotations[s_name] + + i_s_name := i_oci.Annotations[s_name] + print("allow_by_anno 1: i_s_name =", i_s_name) + + allow_by_sandbox_name(p_oci, i_oci, p_storages, i_storages, i_s_name) + + print("allow_by_anno 1: true") +} +allow_by_anno(p_oci, i_oci, p_storages, i_storages) { + print("allow_by_anno 2: start") + + s_name := "io.kubernetes.cri.sandbox-name" + + p_s_name := p_oci.Annotations[s_name] + i_s_name := i_oci.Annotations[s_name] + print("allow_by_anno 2: i_s_name =", i_s_name, "p_s_name =", p_s_name) + + allow_sandbox_name(p_s_name, i_s_name) + allow_by_sandbox_name(p_oci, i_oci, p_storages, i_storages, i_s_name) + + print("allow_by_anno 2: true") +} + +allow_by_sandbox_name(p_oci, i_oci, p_storages, i_storages, s_name) { + print("allow_by_sandbox_name: start") + + s_namespace := "io.kubernetes.cri.sandbox-namespace" + + p_namespace := p_oci.Annotations[s_namespace] + i_namespace := i_oci.Annotations[s_namespace] + print("allow_by_sandbox_name: p_namespace =", p_namespace, "i_namespace =", i_namespace) + p_namespace == i_namespace + + allow_by_container_types(p_oci, i_oci, s_name, p_namespace) + allow_by_bundle_or_sandbox_id(p_oci, i_oci, p_storages, i_storages) + allow_process(p_oci, i_oci, s_name) + + print("allow_by_sandbox_name: true") +} + +allow_sandbox_name(p_s_name, i_s_name) { + print("allow_sandbox_name 1: start") + + p_s_name == i_s_name + + print("allow_sandbox_name 1: true") +} +allow_sandbox_name(p_s_name, i_s_name) { + print("allow_sandbox_name 2: start") + + # TODO: should generated names be handled differently? + contains(p_s_name, "$(generated-name)") + + print("allow_sandbox_name 2: true") +} + +# Check that the "io.kubernetes.cri.container-type" and +# "io.katacontainers.pkg.oci.container_type" annotations designate the +# expected type - either a "sandbox" or a "container". Then, validate +# other annotations based on the actual "sandbox" or "container" value +# from the input container. +allow_by_container_types(p_oci, i_oci, s_name, s_namespace) { + print("allow_by_container_types: checking io.kubernetes.cri.container-type") + + c_type := "io.kubernetes.cri.container-type" + + p_cri_type := p_oci.Annotations[c_type] + i_cri_type := i_oci.Annotations[c_type] + print("allow_by_container_types: p_cri_type =", p_cri_type, "i_cri_type =", i_cri_type) + p_cri_type == i_cri_type + + allow_by_container_type(i_cri_type, p_oci, i_oci, s_name, s_namespace) + + print("allow_by_container_types: true") +} + +allow_by_container_type(i_cri_type, p_oci, i_oci, s_name, s_namespace) { + print("allow_by_container_type 1: i_cri_type =", i_cri_type) + i_cri_type == "sandbox" + + i_kata_type := i_oci.Annotations["io.katacontainers.pkg.oci.container_type"] + print("allow_by_container_type 1: i_kata_type =", i_kata_type) + i_kata_type == "pod_sandbox" + + allow_sandbox_container_name(p_oci, i_oci) + allow_sandbox_net_namespace(p_oci, i_oci) + allow_sandbox_log_directory(p_oci, i_oci, s_name, s_namespace) + + print("allow_by_container_type 1: true") +} + +allow_by_container_type(i_cri_type, p_oci, i_oci, s_name, s_namespace) { + print("allow_by_container_type 2: i_cri_type =", i_cri_type) + i_cri_type == "container" + + i_kata_type := i_oci.Annotations["io.katacontainers.pkg.oci.container_type"] + print("allow_by_container_type 2: i_kata_type =", i_kata_type) + i_kata_type == "pod_container" + + allow_container_name(p_oci, i_oci) + allow_net_namespace(p_oci, i_oci) + allow_log_directory(p_oci, i_oci) + + print("allow_by_container_type 2: true") +} + +# "io.kubernetes.cri.container-name" annotation +allow_sandbox_container_name(p_oci, i_oci) { + print("allow_sandbox_container_name: start") + + container_annotation_missing(p_oci, i_oci, "io.kubernetes.cri.container-name") + + print("allow_sandbox_container_name: true") +} + +allow_container_name(p_oci, i_oci) { + print("allow_container_name: start") + + allow_container_annotation(p_oci, i_oci, "io.kubernetes.cri.container-name") + + print("allow_container_name: true") +} + +container_annotation_missing(p_oci, i_oci, key) { + print("container_annotation_missing:", key) + + not p_oci.Annotations[key] + not i_oci.Annotations[key] + + print("container_annotation_missing: true") +} + +allow_container_annotation(p_oci, i_oci, key) { + print("allow_container_annotation: key =", key) + + p_value := p_oci.Annotations[key] + i_value := i_oci.Annotations[key] + print("allow_container_annotation: p_value =", p_value, "i_value =", i_value) + + p_value == i_value + + print("allow_container_annotation: true") +} + +# "nerdctl/network-namespace" annotation +allow_sandbox_net_namespace(p_oci, i_oci) { + print("allow_sandbox_net_namespace: start") + + key := "nerdctl/network-namespace" + + p_namespace := p_oci.Annotations[key] + i_namespace := i_oci.Annotations[key] + print("allow_sandbox_net_namespace: p_namespace =", p_namespace, "i_namespace =", i_namespace) + + regex.match(p_namespace, i_namespace) + + print("allow_sandbox_net_namespace: true") +} + +allow_net_namespace(p_oci, i_oci) { + print("allow_net_namespace: start") + + key := "nerdctl/network-namespace" + + not p_oci.Annotations[key] + not i_oci.Annotations[key] + + print("allow_net_namespace: true") +} + +# "io.kubernetes.cri.sandbox-log-directory" annotation +allow_sandbox_log_directory(p_oci, i_oci, s_name, s_namespace) { + print("allow_sandbox_log_directory: start") + + key := "io.kubernetes.cri.sandbox-log-directory" + + p_dir := p_oci.Annotations[key] + regex1 := replace(p_dir, "$(sandbox-name)", s_name) + regex2 := replace(regex1, "$(sandbox-namespace)", s_namespace) + print("allow_sandbox_log_directory: regex2 =", regex2) + + i_dir := i_oci.Annotations[key] + print("allow_sandbox_log_directory: i_dir =", i_dir) + + regex.match(regex2, i_dir) + + print("allow_sandbox_log_directory: true") +} + +allow_log_directory(p_oci, i_oci) { + print("allow_log_directory: start") + + key := "io.kubernetes.cri.sandbox-log-directory" + + not p_oci.Annotations[key] + not i_oci.Annotations[key] + + print("allow_log_directory: true") +} + +allow_linux(p_oci, i_oci) { + 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 + + allow_masked_paths(p_oci, i_oci) + allow_readonly_paths(p_oci, i_oci) + + print("allow_linux: true") +} + +allow_masked_paths(p_oci, i_oci) { + p_paths := p_oci.Linux.MaskedPaths + print("allow_masked_paths 1: p_paths =", p_paths) + + i_paths := i_oci.Linux.MaskedPaths + print("allow_masked_paths 1: i_paths =", i_paths) + + allow_masked_paths_array(p_paths, i_paths) + + print("allow_masked_paths 1: true") +} +allow_masked_paths(p_oci, i_oci) { + print("allow_masked_paths 2: start") + + not p_oci.Linux.MaskedPaths + not i_oci.Linux.MaskedPaths + + print("allow_masked_paths 2: true") +} + +# All the policy masked paths must be masked in the input data too. +# Input is allowed to have more masked paths than the policy. +allow_masked_paths_array(p_array, i_array) { + every p_elem in p_array { + allow_masked_path(p_elem, i_array) + } +} + +allow_masked_path(p_elem, i_array) { + print("allow_masked_path: p_elem =", p_elem) + + some i_elem in i_array + p_elem == i_elem + + print("allow_masked_path: true") +} + +allow_readonly_paths(p_oci, i_oci) { + p_paths := p_oci.Linux.ReadonlyPaths + print("allow_readonly_paths 1: p_paths =", p_paths) + + i_paths := i_oci.Linux.ReadonlyPaths + print("allow_readonly_paths 1: i_paths =", i_paths) + + allow_readonly_paths_array(p_paths, i_paths, i_oci.Linux.MaskedPaths) + + print("allow_readonly_paths 1: true") +} +allow_readonly_paths(p_oci, i_oci) { + print("allow_readonly_paths 2: start") + + not p_oci.Linux.ReadonlyPaths + not i_oci.Linux.ReadonlyPaths + + print("allow_readonly_paths 2: true") +} + +# All the policy readonly paths must be either: +# - Present in the input readonly paths, or +# - Present in the input masked paths. +# Input is allowed to have more readonly paths than the policy. +allow_readonly_paths_array(p_array, i_array, masked_paths) { + every p_elem in p_array { + allow_readonly_path(p_elem, i_array, masked_paths) + } +} + +allow_readonly_path(p_elem, i_array, masked_paths) { + print("allow_readonly_path 1: p_elem =", p_elem) + + some i_elem in i_array + p_elem == i_elem + + print("allow_readonly_path 1: true") +} +allow_readonly_path(p_elem, i_array, masked_paths) { + print("allow_readonly_path 2: p_elem =", p_elem) + + some i_masked in masked_paths + p_elem == i_masked + + print("allow_readonly_path 2: true") +} + +# Check the consistency of the input "io.katacontainers.pkg.oci.bundle_path" +# and io.kubernetes.cri.sandbox-id" values with other fields. +allow_by_bundle_or_sandbox_id(p_oci, i_oci, p_storages, i_storages) { + print("allow_by_bundle_or_sandbox_id: start") + + bundle_path := i_oci.Annotations["io.katacontainers.pkg.oci.bundle_path"] + bundle_id := replace(bundle_path, "/run/containerd/io.containerd.runtime.v2.task/k8s.io/", "") + + key := "io.kubernetes.cri.sandbox-id" + + p_regex := p_oci.Annotations[key] + sandbox_id := i_oci.Annotations[key] + + print("allow_by_bundle_or_sandbox_id: sandbox_id =", sandbox_id, "regex =", p_regex) + regex.match(p_regex, sandbox_id) + + allow_root_path(p_oci, i_oci, bundle_id) + + every i_mount in input.OCI.Mounts { + allow_mount(p_oci, i_mount, bundle_id, sandbox_id) + } + + # TODO: enable allow_storages() after fixing https://github.com/kata-containers/kata-containers/issues/8833 + # allow_storages(p_storages, i_storages, bundle_id, sandbox_id) + + print("allow_by_bundle_or_sandbox_id: true") +} + +allow_process(p_oci, i_oci, s_name) { + p_process := p_oci.Process + i_process := i_oci.Process + + print("allow_process: i terminal =", i_process.Terminal, "p terminal =", p_process.Terminal) + p_process.Terminal == i_process.Terminal + + print("allow_process: i cwd =", i_process.Cwd, "i cwd =", p_process.Cwd) + p_process.Cwd == i_process.Cwd + + print("allow_process: i noNewPrivileges =", i_process.NoNewPrivileges, "p noNewPrivileges =", p_process.NoNewPrivileges) + p_process.NoNewPrivileges == i_process.NoNewPrivileges + + allow_caps(p_process.Capabilities, i_process.Capabilities) + allow_user(p_process, i_process) + allow_args(p_process, i_process, s_name) + allow_env(p_process, i_process, s_name) + + print("allow_process: true") +} + +allow_user(p_process, i_process) { + p_user := p_process.User + i_user := i_process.User + + # TODO: track down the reason for mcr.microsoft.com/oss/bitnami/redis:6.0.8 being + # executed with uid = 0 despite having "User": "1001" in its container image + # config. + #print("allow_user: input uid =", i_user.UID, "policy uid =", p_user.UID) + #p_user.UID == i_user.UID + + # TODO: track down the reason for registry.k8s.io/pause:3.9 being + # executed with gid = 0 despite having "65535:65535" in its container image + # config. + #print("allow_user: input gid =", i_user.GID, "policy gid =", p_user.GID) + #p_user.GID == i_user.GID + + # TODO: compare the additionalGids field too after computing its value + # based on /etc/passwd and /etc/group from the container image. +} + +allow_args(p_process, i_process, s_name) { + print("allow_args 1: no args") + + not p_process.Args + not i_process.Args + + print("allow_args 1: true") +} +allow_args(p_process, i_process, s_name) { + print("allow_args 2: policy args =", p_process.Args) + print("allow_args 2: input args =", i_process.Args) + + count(p_process.Args) == count(i_process.Args) + + every i, i_arg in i_process.Args { + allow_arg(i, i_arg, p_process, s_name) + } + + print("allow_args 2: true") +} +allow_arg(i, i_arg, p_process, s_name) { + p_arg := p_process.Args[i] + print("allow_arg 1: i =", i, "i_arg =", i_arg, "p_arg =", p_arg) + + p_arg2 := replace(p_arg, "$$", "$") + p_arg2 == i_arg + + print("allow_arg 1: true") +} +allow_arg(i, i_arg, p_process, s_name) { + p_arg := p_process.Args[i] + print("allow_arg 2: i =", i, "i_arg =", i_arg, "p_arg =", p_arg) + + # TODO: can $(node-name) be handled better? + contains(p_arg, "$(node-name)") + + print("allow_arg 2: true") +} +allow_arg(i, i_arg, p_process, s_name) { + p_arg := p_process.Args[i] + print("allow_arg 3: i =", i, "i_arg =", i_arg, "p_arg =", p_arg) + + p_arg2 := replace(p_arg, "$$", "$") + p_arg3 := replace(p_arg2, "$(sandbox-name)", s_name) + print("allow_arg 3: p_arg3 =", p_arg3) + p_arg3 == i_arg + + print("allow_arg 3: true") +} + +# OCI process.Env field +allow_env(p_process, i_process, s_name) { + print("allow_env: p env =", p_process.Env) + print("allow_env: i env =", i_process.Env) + + every i_var in i_process.Env { + print("allow_env: i_var =", i_var) + allow_var(p_process, i_process, i_var, s_name) + } + + print("allow_env: true") +} + +# Allow input env variables that are present in the policy data too. +allow_var(p_process, i_process, i_var, s_name) { + some p_var in p_process.Env + p_var == i_var + print("allow_var 1: true") +} + +# Match input with one of the policy variables, after substituting $(sandbox-name). +allow_var(p_process, i_process, i_var, s_name) { + some p_var in p_process.Env + p_var2 := replace(p_var, "$(sandbox-name)", s_name) + + print("allow_var 2: p_var2 =", p_var2) + p_var2 == i_var + + print("allow_var 2: true") +} + +# Allow input env variables that match with a request_defaults regex. +allow_var(p_process, i_process, i_var, s_name) { + some p_regex1 in policy_data.request_defaults.CreateContainerRequest.allow_env_regex + p_regex2 := replace(p_regex1, "$(ipv4_a)", policy_data.common.ipv4_a) + p_regex3 := replace(p_regex2, "$(ip_p)", policy_data.common.ip_p) + p_regex4 := replace(p_regex3, "$(svc_name)", policy_data.common.svc_name) + p_regex5 := replace(p_regex4, "$(dns_label)", policy_data.common.dns_label) + + print("allow_var 3: p_regex5 =", p_regex5) + regex.match(p_regex5, i_var) + + print("allow_var 3: true") +} + +# Allow fieldRef "fieldPath: status.podIP" values. +allow_var(p_process, i_process, i_var, s_name) { + name_value := split(i_var, "=") + count(name_value) == 2 + is_ip(name_value[1]) + + some p_var in p_process.Env + allow_pod_ip_var(name_value[0], p_var) + + print("allow_var 4: true") +} + +# Allow common fieldRef variables. +allow_var(p_process, i_process, i_var, s_name) { + name_value := split(i_var, "=") + count(name_value) == 2 + + some p_var in p_process.Env + p_name_value := split(p_var, "=") + count(p_name_value) == 2 + + p_name_value[0] == name_value[0] + + # TODO: should these be handled in a different way? + always_allowed := ["$(host-name)", "$(node-name)", "$(pod-uid)"] + some allowed in always_allowed + contains(p_name_value[1], allowed) + + print("allow_var 5: true") +} + +# Allow fieldRef "fieldPath: status.hostIP" values. +allow_var(p_process, i_process, i_var, s_name) { + name_value := split(i_var, "=") + count(name_value) == 2 + is_ip(name_value[1]) + + some p_var in p_process.Env + allow_host_ip_var(name_value[0], p_var) + + print("allow_var 6: true") +} + +# Allow resourceFieldRef values (e.g., "limits.cpu"). +allow_var(p_process, i_process, i_var, s_name) { + name_value := split(i_var, "=") + count(name_value) == 2 + + some p_var in p_process.Env + p_name_value := split(p_var, "=") + count(p_name_value) == 2 + + p_name_value[0] == name_value[0] + + # TODO: should these be handled in a different way? + always_allowed = ["$(resource-field)", "$(todo-annotation)"] + some allowed in always_allowed + contains(p_name_value[1], allowed) + + print("allow_var 7: true") +} + +allow_pod_ip_var(var_name, p_var) { + print("allow_pod_ip_var: var_name =", var_name, "p_var =", p_var) + + p_name_value := split(p_var, "=") + count(p_name_value) == 2 + + p_name_value[0] == var_name + p_name_value[1] == "$(pod-ip)" + + print("allow_pod_ip_var: true") +} + +allow_host_ip_var(var_name, p_var) { + print("allow_host_ip_var: var_name =", var_name, "p_var =", p_var) + + p_name_value := split(p_var, "=") + count(p_name_value) == 2 + + p_name_value[0] == var_name + p_name_value[1] == "$(host-ip)" + + print("allow_host_ip_var: true") +} + +is_ip(value) { + bytes = split(value, ".") + count(bytes) == 4 + + is_ip_first_byte(bytes[0]) + is_ip_other_byte(bytes[1]) + is_ip_other_byte(bytes[2]) + is_ip_other_byte(bytes[3]) +} +is_ip_first_byte(component) { + number = to_number(component) + number >= 1 + number <= 255 +} +is_ip_other_byte(component) { + number = to_number(component) + number >= 0 + number <= 255 +} + +# OCI root.Path +allow_root_path(p_oci, i_oci, bundle_id) { + i_path := i_oci.Root.Path + p_path1 := p_oci.Root.Path + print("allow_root_path: i_path =", i_path, "p_path1 =", p_path1) + + p_path2 := replace(p_path1, "$(cpath)", policy_data.common.cpath) + print("allow_root_path: p_path2 =", p_path2) + + p_path3 := replace(p_path2, "$(bundle-id)", bundle_id) + print("allow_root_path: p_path3 =", p_path3) + + p_path3 == i_path + + print("allow_root_path: true") +} + +# device mounts +allow_mount(p_oci, i_mount, bundle_id, sandbox_id) { + print("allow_mount: i_mount =", i_mount) + + some p_mount in p_oci.Mounts + print("allow_mount: p_mount =", p_mount) + check_mount(p_mount, i_mount, bundle_id, sandbox_id) + + # TODO: are there any other required policy checks for mounts - e.g., + # multiple mounts with same source or destination? + + print("allow_mount: true") +} + +check_mount(p_mount, i_mount, bundle_id, sandbox_id) { + p_mount == i_mount + print("check_mount 1: true") +} +check_mount(p_mount, i_mount, bundle_id, sandbox_id) { + p_mount.destination == i_mount.destination + p_mount.type_ == i_mount.type_ + p_mount.options == i_mount.options + + mount_source_allows(p_mount, i_mount, bundle_id, sandbox_id) + + print("check_mount 2: true") +} + +mount_source_allows(p_mount, i_mount, bundle_id, sandbox_id) { + regex1 := p_mount.source + regex2 := replace(regex1, "$(sfprefix)", policy_data.common.sfprefix) + regex3 := replace(regex2, "$(cpath)", policy_data.common.cpath) + regex4 := replace(regex3, "$(bundle-id)", bundle_id) + + print("mount_source_allows 1: regex4 =", regex4) + regex.match(regex4, i_mount.source) + + print("mount_source_allows 1: true") +} +mount_source_allows(p_mount, i_mount, bundle_id, sandbox_id) { + regex1 := p_mount.source + regex2 := replace(regex1, "$(sfprefix)", policy_data.common.sfprefix) + regex3 := replace(regex2, "$(cpath)", policy_data.common.cpath) + regex4 := replace(regex3, "$(sandbox-id)", sandbox_id) + + print("mount_source_allows 2: regex4 =", regex4) + regex.match(regex4, i_mount.source) + + print("mount_source_allows 2: true") +} + +###################################################################### +# Storages + +allow_storages(p_storages, i_storages, bundle_id, sandbox_id) { + p_count := count(p_storages) + i_count := count(i_storages) + print("allow_storages: p_count =", p_count, "i_count =", i_count) + + p_count == i_count + + # Get the container image layer IDs and verity root hashes, from the "overlayfs" storage. + some overlay_storage in p_storages + overlay_storage.driver == "overlayfs" + print("allow_storages: overlay_storage =", overlay_storage) + count(overlay_storage.options) == 2 + + layer_ids := split(overlay_storage.options[0], ":") + print("allow_storages: layer_ids =", layer_ids) + + root_hashes := split(overlay_storage.options[1], ":") + print("allow_storages: root_hashes =", root_hashes) + + every i_storage in i_storages { + allow_storage(p_storages, i_storage, bundle_id, sandbox_id, layer_ids, root_hashes) + } + + print("allow_storages: true") +} + +allow_storage(p_storages, i_storage, bundle_id, sandbox_id, layer_ids, root_hashes) { + some p_storage in p_storages + + print("allow_storage: p_storage =", p_storage) + print("allow_storage: i_storage =", i_storage) + + p_storage.driver == i_storage.driver + p_storage.driver_options == i_storage.driver_options + p_storage.fs_group == i_storage.fs_group + + allow_storage_options(p_storage, i_storage, layer_ids, root_hashes) + allow_mount_point(p_storage, i_storage, bundle_id, sandbox_id, layer_ids) + + # TODO: validate the source field too. + + print("allow_storage: true") +} + +allow_storage_options(p_storage, i_storage, layer_ids, root_hashes) { + print("allow_storage_options 1: start") + + p_storage.driver != "blk" + p_storage.driver != "overlayfs" + p_storage.options == i_storage.options + + print("allow_storage_options 1: true") +} +allow_storage_options(p_storage, i_storage, layer_ids, root_hashes) { + print("allow_storage_options 2: start") + + p_storage.driver == "overlayfs" + count(p_storage.options) == 2 + + policy_ids := split(p_storage.options[0], ":") + print("allow_storage_options 2: policy_ids =", policy_ids) + policy_ids == layer_ids + + policy_hashes := split(p_storage.options[1], ":") + print("allow_storage_options 2: policy_hashes =", policy_hashes) + + p_count := count(policy_ids) + print("allow_storage_options 2: p_count =", p_count) + p_count >= 1 + p_count == count(policy_hashes) + + i_count := count(i_storage.options) + print("allow_storage_options 2: i_count =", i_count) + i_count == p_count + 3 + + print("allow_storage_options 2: i_storage.options[0] =", i_storage.options[0]) + i_storage.options[0] == "io.katacontainers.fs-opt.layer-src-prefix=/var/lib/containerd/io.containerd.snapshotter.v1.tardev/layers" + + print("allow_storage_options 2: i_storage.options[i_count - 2] =", i_storage.options[i_count - 2]) + i_storage.options[i_count - 2] == "io.katacontainers.fs-opt.overlay-rw" + + lowerdir := concat("=", ["lowerdir", p_storage.options[0]]) + print("allow_storage_options 2: lowerdir =", lowerdir) + + i_storage.options[i_count - 1] == lowerdir + print("allow_storage_options 2: i_storage.options[i_count - 1] =", i_storage.options[i_count - 1]) + + every i, policy_id in policy_ids { + allow_overlay_layer(policy_id, policy_hashes[i], i_storage.options[i + 1]) + } + + print("allow_storage_options 2: true") +} +allow_storage_options(p_storage, i_storage, layer_ids, root_hashes) { + print("allow_storage_options 3: start") + + p_storage.driver == "blk" + count(p_storage.options) == 1 + + startswith(p_storage.options[0], "$(hash") + hash_suffix := trim_left(p_storage.options[0], "$(hash") + + endswith(hash_suffix, ")") + hash_index := trim_right(hash_suffix, ")") + i := to_number(hash_index) + print("allow_storage_options 3: i =", i) + + hash_option := concat("=", ["io.katacontainers.fs-opt.root-hash", root_hashes[i]]) + print("allow_storage_options 3: hash_option =", hash_option) + + count(i_storage.options) == 4 + i_storage.options[0] == "ro" + i_storage.options[1] == "io.katacontainers.fs-opt.block_device=file" + i_storage.options[2] == "io.katacontainers.fs-opt.is-layer" + i_storage.options[3] == hash_option + + print("allow_storage_options 3: true") +} + +allow_overlay_layer(policy_id, policy_hash, i_option) { + print("allow_overlay_layer: policy_id =", policy_id, "policy_hash =", policy_hash) + print("allow_overlay_layer: i_option =", i_option) + + startswith(i_option, "io.katacontainers.fs-opt.layer=") + i_value := replace(i_option, "io.katacontainers.fs-opt.layer=", "") + i_value_decoded := base64.decode(i_value) + print("allow_overlay_layer: i_value_decoded =", i_value_decoded) + + policy_suffix := concat("=", ["tar,ro,io.katacontainers.fs-opt.block_device=file,io.katacontainers.fs-opt.is-layer,io.katacontainers.fs-opt.root-hash", policy_hash]) + p_value := concat(",", [policy_id, policy_suffix]) + print("allow_overlay_layer: p_value =", p_value) + + p_value == i_value_decoded + + print("allow_overlay_layer: true") +} + +allow_mount_point(p_storage, i_storage, bundle_id, sandbox_id, layer_ids) { + p_storage.fstype == "tar" + + startswith(p_storage.mount_point, "$(layer") + mount_suffix := trim_left(p_storage.mount_point, "$(layer") + + endswith(mount_suffix, ")") + layer_index := trim_right(mount_suffix, ")") + i := to_number(layer_index) + print("allow_mount_point 1: i =", i) + + layer_id := layer_ids[i] + print("allow_mount_point 1: layer_id =", layer_id) + + p_mount := concat("/", ["/run/kata-containers/sandbox/layers", layer_id]) + print("allow_mount_point 1: p_mount =", p_mount) + + p_mount == i_storage.mount_point + + print("allow_mount_point 1: true") +} +allow_mount_point(p_storage, i_storage, bundle_id, sandbox_id, layer_ids) { + p_storage.fstype == "fuse3.kata-overlay" + + mount1 := replace(p_storage.mount_point, "$(cpath)", policy_data.common.cpath) + mount2 := replace(mount1, "$(bundle-id)", bundle_id) + print("allow_mount_point 2: mount2 =", mount2) + + mount2 == i_storage.mount_point + + print("allow_mount_point 2: true") +} +allow_mount_point(p_storage, i_storage, bundle_id, sandbox_id, layer_ids) { + p_storage.fstype == "local" + + mount1 := p_storage.mount_point + print("allow_mount_point 3: mount1 =", mount1) + + mount2 := replace(mount1, "$(cpath)", policy_data.common.cpath) + print("allow_mount_point 3: mount2 =", mount2) + + mount3 := replace(mount2, "$(sandbox-id)", sandbox_id) + print("allow_mount_point 3: mount3 =", mount3) + + regex.match(mount3, i_storage.mount_point) + + print("allow_mount_point 3: true") +} +allow_mount_point(p_storage, i_storage, bundle_id, sandbox_id, layer_ids) { + p_storage.fstype == "bind" + + mount1 := p_storage.mount_point + print("allow_mount_point 4: mount1 =", mount1) + + mount2 := replace(mount1, "$(cpath)", policy_data.common.cpath) + print("allow_mount_point 4: mount2 =", mount2) + + mount3 := replace(mount2, "$(bundle-id)", bundle_id) + print("allow_mount_point 4: mount3 =", mount3) + + regex.match(mount3, i_storage.mount_point) + + print("allow_mount_point 4: true") +} +allow_mount_point(p_storage, i_storage, bundle_id, sandbox_id, layer_ids) { + p_storage.fstype == "tmpfs" + + mount1 := p_storage.mount_point + print("allow_mount_point 5: mount1 =", mount1) + + regex.match(mount1, i_storage.mount_point) + + print("allow_mount_point 5: true") +} + +# process.Capabilities +allow_caps(p_caps, i_caps) { + print("allow_caps: policy Ambient =", p_caps.Ambient) + print("allow_caps: input Ambient =", i_caps.Ambient) + match_caps(p_caps.Ambient, i_caps.Ambient) + + print("allow_caps: policy Bounding =", p_caps.Bounding) + print("allow_caps: input Bounding =", i_caps.Bounding) + match_caps(p_caps.Bounding, i_caps.Bounding) + + print("allow_caps: policy Effective =", p_caps.Effective) + print("allow_caps: input Effective =", i_caps.Effective) + match_caps(p_caps.Effective, i_caps.Effective) + + print("allow_caps: policy Inheritable =", p_caps.Inheritable) + print("allow_caps: input Inheritable =", i_caps.Inheritable) + match_caps(p_caps.Inheritable, i_caps.Inheritable) + + print("allow_caps: policy Permitted =", p_caps.Permitted) + print("allow_caps: input Permitted =", i_caps.Permitted) + match_caps(p_caps.Permitted, i_caps.Permitted) +} + +match_caps(p_caps, i_caps) { + print("match_caps 1: start") + + p_caps == i_caps + + print("match_caps 1: true") +} +match_caps(p_caps, i_caps) { + print("match_caps 2: start") + + count(p_caps) == 1 + p_caps[0] == "$(default_caps)" + + print("match_caps 2: default_caps =", policy_data.common.default_caps) + policy_data.common.default_caps == i_caps + + print("match_caps 2: true") +} +match_caps(p_caps, i_caps) { + print("match_caps 3: start") + + count(p_caps) == 1 + p_caps[0] == "$(privileged_caps)" + + print("match_caps 3: privileged_caps =", policy_data.common.privileged_caps) + policy_data.common.privileged_caps == i_caps + + print("match_caps 3: true") +} + +###################################################################### +CopyFileRequest { + print("CopyFileRequest: input.path =", input.path) + + some regex1 in policy_data.request_defaults.CopyFileRequest + regex2 := replace(regex1, "$(cpath)", policy_data.common.cpath) + regex.match(regex2, input.path) + + print("CopyFileRequest: true") +} + +CreateSandboxRequest { + i_pidns := input.sandbox_pidns + print("CreateSandboxRequest: i_pidns =", i_pidns) + i_pidns == false +} + +ExecProcessRequest { + print("ExecProcessRequest 1: input =", input) + + i_command = concat(" ", input.process.Args) + print("ExecProcessRequest 1: i_command =", i_command) + + some p_command in policy_data.request_defaults.ExecProcessRequest.commands + print("ExecProcessRequest 1: p_command =", p_command) + p_command == i_command + + print("ExecProcessRequest 1: true") +} +ExecProcessRequest { + print("ExecProcessRequest 2: input =", input) + + # TODO: match input container ID with its corresponding container.exec_commands. + i_command = concat(" ", input.process.Args) + print("ExecProcessRequest 3: i_command =", i_command) + + some container in policy_data.containers + some p_command in container.exec_commands + print("ExecProcessRequest 2: p_command =", p_command) + + # TODO: should other input data fields be validated as well? + p_command == i_command + + print("ExecProcessRequest 2: true") +} +ExecProcessRequest { + print("ExecProcessRequest 3: input =", input) + + i_command = concat(" ", input.process.Args) + print("ExecProcessRequest 3: i_command =", i_command) + + some p_regex in policy_data.request_defaults.ExecProcessRequest.regex + print("ExecProcessRequest 3: p_regex =", p_regex) + + regex.match(p_regex, i_command) + + print("ExecProcessRequest 3: true") +} + +CloseStdinRequest { + policy_data.request_defaults.CloseStdinRequest == true +} + +ReadStreamRequest { + policy_data.request_defaults.ReadStreamRequest == true +} + +WriteStreamRequest { + policy_data.request_defaults.WriteStreamRequest == true +} + +policy_data := { + "containers": [ + { + "OCI": { + "Version": "1.1.0", + "Process": { + "Terminal": false, + "User": { + "UID": 65535, + "GID": 65535, + "AdditionalGids": [], + "Username": "" + }, + "Args": [ + "/pause" + ], + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "Cwd": "/", + "Capabilities": { + "Ambient": [], + "Bounding": [ + "$(default_caps)" + ], + "Effective": [ + "$(default_caps)" + ], + "Inheritable": [], + "Permitted": [ + "$(default_caps)" + ] + }, + "NoNewPrivileges": true + }, + "Root": { + "Path": "$(cpath)/$(bundle-id)/rootfs", + "Readonly": true + }, + "Mounts": [ + { + "destination": "/proc", + "source": "proc", + "type_": "proc", + "options": [ + "nosuid", + "noexec", + "nodev" + ] + }, + { + "destination": "/dev", + "source": "tmpfs", + "type_": "tmpfs", + "options": [ + "nosuid", + "strictatime", + "mode=755", + "size=65536k" + ] + }, + { + "destination": "/dev/pts", + "source": "devpts", + "type_": "devpts", + "options": [ + "nosuid", + "noexec", + "newinstance", + "ptmxmode=0666", + "mode=0620", + "gid=5" + ] + }, + { + "destination": "/dev/shm", + "source": "/run/kata-containers/sandbox/shm", + "type_": "bind", + "options": [ + "rbind" + ] + }, + { + "destination": "/dev/mqueue", + "source": "mqueue", + "type_": "mqueue", + "options": [ + "nosuid", + "noexec", + "nodev" + ] + }, + { + "destination": "/sys", + "source": "sysfs", + "type_": "sysfs", + "options": [ + "nosuid", + "noexec", + "nodev", + "ro" + ] + }, + { + "destination": "/etc/resolv.conf", + "source": "$(sfprefix)resolv.conf$", + "type_": "bind", + "options": [ + "rbind", + "ro", + "nosuid", + "nodev", + "noexec" + ] + } + ], + "Annotations": { + "io.katacontainers.pkg.oci.bundle_path": "/run/containerd/io.containerd.runtime.v2.task/k8s.io/$(bundle-id)", + "io.katacontainers.pkg.oci.container_type": "pod_sandbox", + "io.kubernetes.cri.container-type": "sandbox", + "io.kubernetes.cri.sandbox-id": "^[a-z0-9]{64}$", + "io.kubernetes.cri.sandbox-log-directory": "^/var/log/pods/$(sandbox-namespace)_$(sandbox-name)_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", + "io.kubernetes.cri.sandbox-namespace": "kata-containers-k8s-tests", + "nerdctl/network-namespace": "^/var/run/netns/cni-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" + }, + "Linux": { + "Namespaces": [ + { + "Type": "ipc", + "Path": "" + }, + { + "Type": "uts", + "Path": "" + }, + { + "Type": "mount", + "Path": "" + } + ], + "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" + ] + } + }, + "storages": [ + { + "driver": "blk", + "driver_options": [], + "source": "", + "fstype": "tar", + "options": [ + "$(hash0)" + ], + "mount_point": "$(layer0)", + "fs_group": null + }, + { + "driver": "overlayfs", + "driver_options": [], + "source": "", + "fstype": "fuse3.kata-overlay", + "options": [ + "5a5aad80055ff20012a50dc25f8df7a29924474324d65f7d5306ee8ee27ff71d", + "817250f1a3e336da76f5bd3fa784e1b26d959b9c131876815ba2604048b70c18" + ], + "mount_point": "$(cpath)/$(bundle-id)/rootfs", + "fs_group": null + } + ], + "sandbox_pidns": false, + "exec_commands": [] + }, + { + "OCI": { + "Version": "1.1.0", + "Process": { + "Terminal": false, + "User": { + "UID": 0, + "GID": 0, + "AdditionalGids": [], + "Username": "" + }, + "Args": [ + "/bin/sh", + "-c", + "sleep 120" + ], + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "HOSTNAME=$(host-name)", + "var1=val1" + ], + "Cwd": "/", + "Capabilities": { + "Ambient": [], + "Bounding": [ + "$(default_caps)" + ], + "Effective": [ + "$(default_caps)" + ], + "Inheritable": [], + "Permitted": [ + "$(default_caps)" + ] + }, + "NoNewPrivileges": false + }, + "Root": { + "Path": "$(cpath)/$(bundle-id)/rootfs", + "Readonly": true + }, + "Mounts": [ + { + "destination": "/proc", + "source": "proc", + "type_": "proc", + "options": [ + "nosuid", + "noexec", + "nodev" + ] + }, + { + "destination": "/dev", + "source": "tmpfs", + "type_": "tmpfs", + "options": [ + "nosuid", + "strictatime", + "mode=755", + "size=65536k" + ] + }, + { + "destination": "/dev/pts", + "source": "devpts", + "type_": "devpts", + "options": [ + "nosuid", + "noexec", + "newinstance", + "ptmxmode=0666", + "mode=0620", + "gid=5" + ] + }, + { + "destination": "/dev/shm", + "source": "/run/kata-containers/sandbox/shm", + "type_": "bind", + "options": [ + "rbind" + ] + }, + { + "destination": "/dev/mqueue", + "source": "mqueue", + "type_": "mqueue", + "options": [ + "nosuid", + "noexec", + "nodev" + ] + }, + { + "destination": "/sys", + "source": "sysfs", + "type_": "sysfs", + "options": [ + "nosuid", + "noexec", + "nodev", + "ro" + ] + }, + { + "destination": "/sys/fs/cgroup", + "source": "cgroup", + "type_": "cgroup", + "options": [ + "nosuid", + "noexec", + "nodev", + "relatime", + "ro" + ] + }, + { + "destination": "/etc/hosts", + "source": "$(sfprefix)hosts$", + "type_": "bind", + "options": [ + "rbind", + "rprivate", + "rw" + ] + }, + { + "destination": "/dev/termination-log", + "source": "$(sfprefix)termination-log$", + "type_": "bind", + "options": [ + "rbind", + "rprivate", + "rw" + ] + }, + { + "destination": "/etc/hostname", + "source": "$(sfprefix)hostname$", + "type_": "bind", + "options": [ + "rbind", + "rprivate", + "ro" + ] + }, + { + "destination": "/etc/resolv.conf", + "source": "$(sfprefix)resolv.conf$", + "type_": "bind", + "options": [ + "rbind", + "rprivate", + "ro" + ] + }, + { + "destination": "/var/run/secrets/kubernetes.io/serviceaccount", + "source": "$(sfprefix)serviceaccount$", + "type_": "bind", + "options": [ + "rbind", + "rprivate", + "ro" + ] + }, + { + "destination": "/var/run/secrets/azure/tokens", + "source": "$(sfprefix)tokens$", + "type_": "bind", + "options": [ + "rbind", + "rprivate", + "ro" + ] + } + ], + "Annotations": { + "io.katacontainers.pkg.oci.bundle_path": "/run/containerd/io.containerd.runtime.v2.task/k8s.io/$(bundle-id)", + "io.katacontainers.pkg.oci.container_type": "pod_container", + "io.kubernetes.cri.container-name": "hello", + "io.kubernetes.cri.container-type": "container", + "io.kubernetes.cri.image-name": "quay.io/prometheus/busybox:latest", + "io.kubernetes.cri.sandbox-id": "^[a-z0-9]{64}$", + "io.kubernetes.cri.sandbox-namespace": "kata-containers-k8s-tests" + }, + "Linux": { + "Namespaces": [ + { + "Type": "ipc", + "Path": "" + }, + { + "Type": "uts", + "Path": "" + }, + { + "Type": "mount", + "Path": "" + } + ], + "MaskedPaths": [ + "/proc/acpi", + "/proc/kcore", + "/proc/keys", + "/proc/latency_stats", + "/proc/timer_list", + "/proc/timer_stats", + "/proc/sched_debug", + "/proc/scsi", + "/sys/firmware" + ], + "ReadonlyPaths": [ + "/proc/asound", + "/proc/bus", + "/proc/fs", + "/proc/irq", + "/proc/sys", + "/proc/sysrq-trigger" + ] + } + }, + "storages": [ + { + "driver": "blk", + "driver_options": [], + "source": "", + "fstype": "tar", + "options": [ + "$(hash0)" + ], + "mount_point": "$(layer0)", + "fs_group": null + }, + { + "driver": "blk", + "driver_options": [], + "source": "", + "fstype": "tar", + "options": [ + "$(hash1)" + ], + "mount_point": "$(layer1)", + "fs_group": null + }, + { + "driver": "overlayfs", + "driver_options": [], + "source": "", + "fstype": "fuse3.kata-overlay", + "options": [ + "b1ef3d18661ad727a8ad01d462fffc63ec90420a938056bc0422822328f83ff7:a8650d4a279d4537d7fc43c582a264528a96f041b1327d41b503f368c301594f", + "75ed5e78d30374b9557d32c17ae6d2e1b4afdc5efd128812e6c99a0f60708dba:2e52d86498030b4b99318650826d3f121bdfac5fe7bbbea9d18c546d48003aa0" + ], + "mount_point": "$(cpath)/$(bundle-id)/rootfs", + "fs_group": null + } + ], + "sandbox_pidns": false, + "exec_commands": [] + } + ], + "common": { + "cpath": "/run/kata-containers/shared/containers", + "sfprefix": "^$(cpath)/$(bundle-id)-[a-z0-9]{16}-", + "ipv4_a": "(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])", + "ip_p": "[0-9]{1,5}", + "svc_name": "[A-Z_\\.\\-]+", + "dns_label": "[a-zA-Z0-9_\\.\\-]+", + "default_caps": [ + "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" + ], + "privileged_caps": [ + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_DAC_READ_SEARCH", + "CAP_FOWNER", + "CAP_FSETID", + "CAP_KILL", + "CAP_SETGID", + "CAP_SETUID", + "CAP_SETPCAP", + "CAP_LINUX_IMMUTABLE", + "CAP_NET_BIND_SERVICE", + "CAP_NET_BROADCAST", + "CAP_NET_ADMIN", + "CAP_NET_RAW", + "CAP_IPC_LOCK", + "CAP_IPC_OWNER", + "CAP_SYS_MODULE", + "CAP_SYS_RAWIO", + "CAP_SYS_CHROOT", + "CAP_SYS_PTRACE", + "CAP_SYS_PACCT", + "CAP_SYS_ADMIN", + "CAP_SYS_BOOT", + "CAP_SYS_NICE", + "CAP_SYS_RESOURCE", + "CAP_SYS_TIME", + "CAP_SYS_TTY_CONFIG", + "CAP_MKNOD", + "CAP_LEASE", + "CAP_AUDIT_WRITE", + "CAP_AUDIT_CONTROL", + "CAP_SETFCAP", + "CAP_MAC_OVERRIDE", + "CAP_MAC_ADMIN", + "CAP_SYSLOG", + "CAP_WAKE_ALARM", + "CAP_BLOCK_SUSPEND", + "CAP_AUDIT_READ", + "CAP_PERFMON", + "CAP_BPF", + "CAP_CHECKPOINT_RESTORE" + ] + }, + "request_defaults": { + "CreateContainerRequest": { + "allow_env_regex": [ + "^HOSTNAME=$(dns_label)$", + "^$(svc_name)_PORT_$(ip_p)_TCP=tcp://$(ipv4_a):$(ip_p)$", + "^$(svc_name)_PORT_$(ip_p)_TCP_PROTO=tcp$", + "^$(svc_name)_PORT_$(ip_p)_TCP_PORT=$(ip_p)$", + "^$(svc_name)_PORT_$(ip_p)_TCP_ADDR=$(ipv4_a)$", + "^$(svc_name)_SERVICE_HOST=$(ipv4_a)$", + "^$(svc_name)_SERVICE_PORT=$(ip_p)$", + "^$(svc_name)_SERVICE_PORT_$(dns_label)=$(ip_p)$", + "^$(svc_name)_PORT=tcp://$(ipv4_a):$(ip_p)$", + "^AZURE_CLIENT_ID=[A-Fa-f0-9-]+$", + "^AZURE_TENANT_ID=[A-Fa-f0-9-]+$", + "^AZURE_FEDERATED_TOKEN_FILE=/var/run/secrets/azure/tokens/azure-identity-token$", + "^AZURE_AUTHORITY_HOST=https://login\\.microsoftonline\\.com/$" + ] + }, + "CopyFileRequest": [ + "^$(cpath)/" + ], + "ExecProcessRequest": { + "commands": [], + "regex": [] + }, + "CloseStdinRequest": false, + "ReadStreamRequest": true, + "WriteStreamRequest": false + } +} diff --git a/tests/kata/data/k8s-policy-pod/inputs.txt b/tests/kata/data/k8s-policy-pod/inputs.txt new file mode 100644 index 0000000..ad4332e --- /dev/null +++ b/tests/kata/data/k8s-policy-pod/inputs.txt @@ -0,0 +1,34 @@ +["ep":"AllowRequestsFailingPolicy",{}], + +["ep":"UpdateInterfaceRequest",{"interface":{"device":"eth0","name":"eth0","IPAddresses":[{"family":0,"address":"10.244.2.63","mask":"24"},{"family":1,"address":"fe80::b076:e0ff:fee4:4483","mask":"64"}],"mtu":1500,"hwAddr":"b2:76:e0:e4:44:83","pciPath":"","type_":"","raw_flags":0}}], + +["ep":"UpdateRoutesRequest",{"routes":{"Routes":[{"dest":"","gateway":"10.244.2.1","device":"eth0","source":"","scope":0,"family":0}]}}], + +["ep":"CreateSandboxRequest",{"hostname":"policy-pod","dns":["search kata-containers-k8s-tests.svc.cluster.local svc.cluster.local cluster.local iete1orsbdmu5ia1kq2du43hqe.bx.internal.cloudapp.net","nameserver 10.0.0.10","options ndots:5",""],"storages":[{"driver":"virtio-fs","driver_options":[],"source":"kataShared","fstype":"virtiofs","options":[],"mount_point":"/run/kata-containers/shared/containers/","fs_group":null},{"driver":"ephemeral","driver_options":[],"source":"shm","fstype":"tmpfs","options":["noexec","nosuid","nodev","mode=1777","size=67108864"],"mount_point":"/run/kata-containers/sandbox/shm","fs_group":null}],"sandbox_pidns":false,"sandbox_id":"e4bd2a97acb81f9ea31d49e21ef2675bcd8bf6883d3cd4273d6d4fb2f6fcbba2","guest_hook_path":"","kernel_modules":[]}], + +["ep":"GuestDetailsRequest",{"mem_block_size":true,"mem_hotplug_probe":true}], + +["ep":"CreateContainerRequest",{"container_id":"e4bd2a97acb81f9ea31d49e21ef2675bcd8bf6883d3cd4273d6d4fb2f6fcbba2","exec_id":"e4bd2a97acb81f9ea31d49e21ef2675bcd8bf6883d3cd4273d6d4fb2f6fcbba2","string_user":null,"devices":[],"storages":[],"OCI":{"Version":"1.1.0","Process":{"Terminal":false,"ConsoleSize":null,"User":{"UID":65535,"GID":65535,"AdditionalGids":[65535],"Username":""},"Args":["/pause"],"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cwd":"/","Capabilities":{"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"],"Inheritable":[],"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"],"Ambient":[]},"Rlimits":[],"NoNewPrivileges":true,"ApparmorProfile":"","OOMScoreAdj":-998,"SelinuxLabel":""},"Root":{"Path":"/run/kata-containers/shared/containers/e4bd2a97acb81f9ea31d49e21ef2675bcd8bf6883d3cd4273d6d4fb2f6fcbba2/rootfs","Readonly":true},"Hostname":"policy-pod","Mounts":[{"destination":"/proc","source":"proc","type_":"proc","options":["nosuid","noexec","nodev"]},{"destination":"/dev","source":"tmpfs","type_":"tmpfs","options":["nosuid","strictatime","mode=755","size=65536k"]},{"destination":"/dev/pts","source":"devpts","type_":"devpts","options":["nosuid","noexec","newinstance","ptmxmode=0666","mode=0620","gid=5"]},{"destination":"/dev/mqueue","source":"mqueue","type_":"mqueue","options":["nosuid","noexec","nodev"]},{"destination":"/sys","source":"sysfs","type_":"sysfs","options":["nosuid","noexec","nodev","ro"]},{"destination":"/dev/shm","source":"/run/kata-containers/sandbox/shm","type_":"bind","options":["rbind"]},{"destination":"/etc/resolv.conf","source":"/run/kata-containers/shared/containers/e4bd2a97acb81f9ea31d49e21ef2675bcd8bf6883d3cd4273d6d4fb2f6fcbba2-c9c9b9dc84433468-resolv.conf","type_":"bind","options":["rbind","ro","nosuid","nodev","noexec"]}],"Hooks":null,"Annotations":{"io.kubernetes.cri.sandbox-name":"policy-pod","io.katacontainers.pkg.oci.bundle_path":"/run/containerd/io.containerd.runtime.v2.task/k8s.io/e4bd2a97acb81f9ea31d49e21ef2675bcd8bf6883d3cd4273d6d4fb2f6fcbba2","io.kubernetes.cri.container-type":"sandbox","io.kubernetes.cri.sandbox-namespace":"kata-containers-k8s-tests","nerdctl/network-namespace":"/var/run/netns/cni-3e413870-ed7b-d970-7440-cc55c7795613","io.kubernetes.cri.sandbox-memory":"0","io.kubernetes.cri.sandbox-uid":"f7641c61-8a63-4bc6-98b9-6a4a5b0d860a","io.kubernetes.cri.sandbox-cpu-period":"100000","io.kubernetes.cri.sandbox-log-directory":"/var/log/pods/kata-containers-k8s-tests_policy-pod_f7641c61-8a63-4bc6-98b9-6a4a5b0d860a","io.kubernetes.cri.sandbox-cpu-quota":"0","io.kubernetes.cri.sandbox-id":"e4bd2a97acb81f9ea31d49e21ef2675bcd8bf6883d3cd4273d6d4fb2f6fcbba2","io.katacontainers.pkg.oci.container_type":"pod_sandbox","io.kubernetes.cri.sandbox-cpu-shares":"2"},"Linux":{"UIDMappings":[],"GIDMappings":[],"Sysctl":{},"Resources":{"Devices":[],"Memory":null,"CPU":{"Shares":2,"Quota":0,"Period":0,"RealtimeRuntime":0,"RealtimePeriod":0,"Cpus":"","Mems":""},"Pids":null,"BlockIO":null,"HugepageLimits":[],"Network":null},"CgroupsPath":"kubepods-besteffort-podf7641c61_8a63_4bc6_98b9_6a4a5b0d860a.slice:cri-containerd:e4bd2a97acb81f9ea31d49e21ef2675bcd8bf6883d3cd4273d6d4fb2f6fcbba2","Namespaces":[{"Type":"ipc","Path":""},{"Type":"uts","Path":""},{"Type":"mount","Path":""}],"Devices":[],"Seccomp":null,"RootfsPropagation":"","MaskedPaths":["/proc/acpi","/proc/asound","/proc/kcore","/proc/keys","/proc/latency_stats","/proc/timer_list","/proc/timer_stats","/proc/sched_debug","/sys/firmware","/sys/devices/virtual/powercap","/proc/scsi"],"ReadonlyPaths":["/proc/bus","/proc/fs","/proc/irq","/proc/sys","/proc/sysrq-trigger"],"MountLabel":"","IntelRdt":null},"Solaris":null,"Windows":null},"sandbox_pidns":false,"shared_mounts":[],"stdin_port":0,"stdout_port":0,"stderr_port":0}], + +["ep":"OnlineCPUMemRequest",{"wait":false,"nb_cpus":0,"cpu_only":false}], + +["ep":"UpdateEphemeralMountsRequest",{"storages":[]}], + +["ep":"StartContainerRequest",{"container_id":"e4bd2a97acb81f9ea31d49e21ef2675bcd8bf6883d3cd4273d6d4fb2f6fcbba2"}], + +["ep":"WaitProcessRequest",{"container_id":"e4bd2a97acb81f9ea31d49e21ef2675bcd8bf6883d3cd4273d6d4fb2f6fcbba2","exec_id":"e4bd2a97acb81f9ea31d49e21ef2675bcd8bf6883d3cd4273d6d4fb2f6fcbba2"}], + +["ep":"GetOOMEventRequest",{}], + +["ep":"CreateContainerRequest",{"container_id":"151c95eb53118154e0574814181406187332a6569c6c82bcf29e3e89392282c9","exec_id":"151c95eb53118154e0574814181406187332a6569c6c82bcf29e3e89392282c9","string_user":null,"devices":[],"storages":[],"OCI":{"Version":"1.1.0","Process":{"Terminal":false,"ConsoleSize":null,"User":{"UID":0,"GID":0,"AdditionalGids":[0,10],"Username":""},"Args":["sh"],"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","HOSTNAME=policy-pod","KUBE_CONFIG_1=value-1","KUBE_CONFIG_2=value-2","KUBERNETES_PORT_443_TCP_PROTO=tcp","KUBERNETES_PORT_443_TCP_PORT=443","KUBERNETES_PORT_443_TCP_ADDR=10.0.0.1","KUBERNETES_SERVICE_HOST=10.0.0.1","KUBERNETES_SERVICE_PORT=443","KUBERNETES_SERVICE_PORT_HTTPS=443","KUBERNETES_PORT=tcp://10.0.0.1:443","KUBERNETES_PORT_443_TCP=tcp://10.0.0.1:443"],"Cwd":"/","Capabilities":{"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"],"Inheritable":[],"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"],"Ambient":[]},"Rlimits":[],"NoNewPrivileges":false,"ApparmorProfile":"cri-containerd.apparmor.d","OOMScoreAdj":1000,"SelinuxLabel":""},"Root":{"Path":"/run/kata-containers/shared/containers/151c95eb53118154e0574814181406187332a6569c6c82bcf29e3e89392282c9/rootfs","Readonly":false},"Hostname":"","Mounts":[{"destination":"/proc","source":"proc","type_":"proc","options":["nosuid","noexec","nodev"]},{"destination":"/dev","source":"tmpfs","type_":"tmpfs","options":["nosuid","strictatime","mode=755","size=65536k"]},{"destination":"/dev/pts","source":"devpts","type_":"devpts","options":["nosuid","noexec","newinstance","ptmxmode=0666","mode=0620","gid=5"]},{"destination":"/dev/mqueue","source":"mqueue","type_":"mqueue","options":["nosuid","noexec","nodev"]},{"destination":"/sys","source":"sysfs","type_":"sysfs","options":["nosuid","noexec","nodev","ro"]},{"destination":"/sys/fs/cgroup","source":"cgroup","type_":"cgroup","options":["nosuid","noexec","nodev","relatime","ro"]},{"destination":"/etc/hosts","source":"/run/kata-containers/shared/containers/151c95eb53118154e0574814181406187332a6569c6c82bcf29e3e89392282c9-b0241970194aa0f1-hosts","type_":"bind","options":["rbind","rprivate","rw"]},{"destination":"/dev/termination-log","source":"/run/kata-containers/shared/containers/151c95eb53118154e0574814181406187332a6569c6c82bcf29e3e89392282c9-70afdd32aae261c9-termination-log","type_":"bind","options":["rbind","rprivate","rw"]},{"destination":"/etc/hostname","source":"/run/kata-containers/shared/containers/151c95eb53118154e0574814181406187332a6569c6c82bcf29e3e89392282c9-cdfc9069abab6aa0-hostname","type_":"bind","options":["rbind","rprivate","rw"]},{"destination":"/etc/resolv.conf","source":"/run/kata-containers/shared/containers/151c95eb53118154e0574814181406187332a6569c6c82bcf29e3e89392282c9-a9ad9599a85707b3-resolv.conf","type_":"bind","options":["rbind","rprivate","rw"]},{"destination":"/dev/shm","source":"/run/kata-containers/sandbox/shm","type_":"bind","options":["rbind"]},{"destination":"/var/run/secrets/kubernetes.io/serviceaccount","source":"/run/kata-containers/shared/containers/151c95eb53118154e0574814181406187332a6569c6c82bcf29e3e89392282c9-5f0e5e3718c35792-serviceaccount","type_":"bind","options":["rbind","rprivate","ro"]}],"Hooks":null,"Annotations":{"io.katacontainers.pkg.oci.bundle_path":"/run/containerd/io.containerd.runtime.v2.task/k8s.io/151c95eb53118154e0574814181406187332a6569c6c82bcf29e3e89392282c9","io.kubernetes.cri.sandbox-id":"e4bd2a97acb81f9ea31d49e21ef2675bcd8bf6883d3cd4273d6d4fb2f6fcbba2","io.kubernetes.cri.container-type":"container","io.katacontainers.pkg.oci.container_type":"pod_container","io.kubernetes.cri.sandbox-name":"policy-pod","io.kubernetes.cri.container-name":"busybox","io.kubernetes.cri.sandbox-uid":"f7641c61-8a63-4bc6-98b9-6a4a5b0d860a","io.kubernetes.cri.sandbox-namespace":"kata-containers-k8s-tests","io.kubernetes.cri.image-name":"quay.io/prometheus/busybox:latest"},"Linux":{"UIDMappings":[],"GIDMappings":[],"Sysctl":{},"Resources":{"Devices":[],"Memory":{"Limit":0,"Reservation":0,"Swap":0,"Kernel":0,"KernelTCP":0,"Swappiness":0,"DisableOOMKiller":false},"CPU":{"Shares":2,"Quota":0,"Period":100000,"RealtimeRuntime":0,"RealtimePeriod":0,"Cpus":"","Mems":""},"Pids":null,"BlockIO":null,"HugepageLimits":[],"Network":null},"CgroupsPath":"kubepods-besteffort-podf7641c61_8a63_4bc6_98b9_6a4a5b0d860a.slice:cri-containerd:151c95eb53118154e0574814181406187332a6569c6c82bcf29e3e89392282c9","Namespaces":[{"Type":"ipc","Path":""},{"Type":"uts","Path":""},{"Type":"mount","Path":""}],"Devices":[],"Seccomp":null,"RootfsPropagation":"","MaskedPaths":["/proc/asound","/proc/acpi","/proc/kcore","/proc/keys","/proc/latency_stats","/proc/timer_list","/proc/timer_stats","/proc/sched_debug","/proc/scsi","/sys/firmware"],"ReadonlyPaths":["/proc/bus","/proc/fs","/proc/irq","/proc/sys","/proc/sysrq-trigger"],"MountLabel":"","IntelRdt":null},"Solaris":null,"Windows":null},"sandbox_pidns":false,"shared_mounts":[],"stdin_port":0,"stdout_port":0,"stderr_port":0}], + +["ep":"OnlineCPUMemRequest",{"wait":false,"nb_cpus":0,"cpu_only":false}], + +["ep":"UpdateEphemeralMountsRequest",{"storages":[]}], + +["ep":"StartContainerRequest",{"container_id":"151c95eb53118154e0574814181406187332a6569c6c82bcf29e3e89392282c9"}], + +["ep":"OnlineCPUMemRequest",{"wait":false,"nb_cpus":0,"cpu_only":false}], + +["ep":"UpdateEphemeralMountsRequest",{"storages":[]}], + diff --git a/tests/kata/data/k8s-policy-pod/output.json b/tests/kata/data/k8s-policy-pod/output.json new file mode 100644 index 0000000..c0f5c42 --- /dev/null +++ b/tests/kata/data/k8s-policy-pod/output.json @@ -0,0 +1,19 @@ +[ + false, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true +] \ No newline at end of file diff --git a/tests/kata/data/k8s-policy-pod/policy.rego b/tests/kata/data/k8s-policy-pod/policy.rego new file mode 100644 index 0000000..9d503f7 --- /dev/null +++ b/tests/kata/data/k8s-policy-pod/policy.rego @@ -0,0 +1,1653 @@ +# Copyright (c) 2023 Microsoft Corporation +# +# SPDX-License-Identifier: Apache-2.0 +# +package agent_policy + +import future.keywords.in +import future.keywords.every + +# Default values, returned by OPA when rules cannot be evaluated to true. +default AddARPNeighborsRequest := false +default AddSwapRequest := false +default CloseStdinRequest := false +default CopyFileRequest := false +default CreateContainerRequest := false +default CreateSandboxRequest := true +default DestroySandboxRequest := true +default ExecProcessRequest := false +default GetOOMEventRequest := true +default GuestDetailsRequest := true +default ListInterfacesRequest := false +default ListRoutesRequest := false +default MemHotplugByProbeRequest := false +default OnlineCPUMemRequest := true +default PauseContainerRequest := false +default ReadStreamRequest := false +default RemoveContainerRequest := true +default RemoveStaleVirtiofsShareMountsRequest := true +default ReseedRandomDevRequest := false +default ResumeContainerRequest := false +default SetGuestDateTimeRequest := false +default SetPolicyRequest := false +default SignalProcessRequest := true +default StartContainerRequest := true +default StartTracingRequest := false +default StatsContainerRequest := true +default StopTracingRequest := false +default TtyWinResizeRequest := true +default UpdateContainerRequest := false +default UpdateEphemeralMountsRequest := true +default UpdateInterfaceRequest := true +default UpdateRoutesRequest := true +default WaitProcessRequest := true +default WriteStreamRequest := false + +# AllowRequestsFailingPolicy := true configures the Agent to *allow any +# requests causing a policy failure*. This is an unsecure configuration +# but is useful for allowing unsecure pods to start, then connect to +# them and inspect OPA logs for the root cause of a failure. +default AllowRequestsFailingPolicy := false + +CreateContainerRequest { + i_oci := input.OCI + i_storages := input.storages + + some p_container in policy_data.containers + print("======== CreateContainerRequest: trying next policy container") + + p_pidns := p_container.sandbox_pidns + i_pidns := input.sandbox_pidns + print("CreateContainerRequest: p_pidns =", p_pidns, "i_pidns =", i_pidns) + p_pidns == i_pidns + + p_oci := p_container.OCI + + print("CreateContainerRequest: p Version =", p_oci.Version, "i Version =", i_oci.Version) + p_oci.Version == i_oci.Version + + print("CreateContainerRequest: p Readonly =", p_oci.Root.Readonly, "i Readonly =", i_oci.Root.Readonly) + p_oci.Root.Readonly == i_oci.Root.Readonly + + allow_anno(p_oci, i_oci) + + p_storages := p_container.storages + allow_by_anno(p_oci, i_oci, p_storages, i_storages) + + allow_linux(p_oci, i_oci) + + print("CreateContainerRequest: true") +} + +# Reject unexpected annotations. +allow_anno(p_oci, i_oci) { + print("allow_anno 1: start") + + not i_oci.Annotations + + print("allow_anno 1: true") +} +allow_anno(p_oci, i_oci) { + print("allow_anno 2: p Annotations =", p_oci.Annotations) + print("allow_anno 2: i Annotations =", i_oci.Annotations) + + i_keys := object.keys(i_oci.Annotations) + print("allow_anno 2: i keys =", i_keys) + + every i_key in i_keys { + allow_anno_key(i_key, p_oci) + } + + print("allow_anno 2: true") +} + +allow_anno_key(i_key, p_oci) { + print("allow_anno_key 1: i key =", i_key) + + startswith(i_key, "io.kubernetes.cri.") + + print("allow_anno_key 1: true") +} +allow_anno_key(i_key, p_oci) { + print("allow_anno_key 2: i key =", i_key) + + some p_key, _ in p_oci.Annotations + p_key == i_key + + print("allow_anno_key 2: true") +} + +# Get the value of the "io.kubernetes.cri.sandbox-name" annotation and +# correlate it with other annotations and process fields. +allow_by_anno(p_oci, i_oci, p_storages, i_storages) { + print("allow_by_anno 1: start") + + s_name := "io.kubernetes.cri.sandbox-name" + + not p_oci.Annotations[s_name] + + i_s_name := i_oci.Annotations[s_name] + print("allow_by_anno 1: i_s_name =", i_s_name) + + allow_by_sandbox_name(p_oci, i_oci, p_storages, i_storages, i_s_name) + + print("allow_by_anno 1: true") +} +allow_by_anno(p_oci, i_oci, p_storages, i_storages) { + print("allow_by_anno 2: start") + + s_name := "io.kubernetes.cri.sandbox-name" + + p_s_name := p_oci.Annotations[s_name] + i_s_name := i_oci.Annotations[s_name] + print("allow_by_anno 2: i_s_name =", i_s_name, "p_s_name =", p_s_name) + + allow_sandbox_name(p_s_name, i_s_name) + allow_by_sandbox_name(p_oci, i_oci, p_storages, i_storages, i_s_name) + + print("allow_by_anno 2: true") +} + +allow_by_sandbox_name(p_oci, i_oci, p_storages, i_storages, s_name) { + print("allow_by_sandbox_name: start") + + s_namespace := "io.kubernetes.cri.sandbox-namespace" + + p_namespace := p_oci.Annotations[s_namespace] + i_namespace := i_oci.Annotations[s_namespace] + print("allow_by_sandbox_name: p_namespace =", p_namespace, "i_namespace =", i_namespace) + p_namespace == i_namespace + + allow_by_container_types(p_oci, i_oci, s_name, p_namespace) + allow_by_bundle_or_sandbox_id(p_oci, i_oci, p_storages, i_storages) + allow_process(p_oci, i_oci, s_name) + + print("allow_by_sandbox_name: true") +} + +allow_sandbox_name(p_s_name, i_s_name) { + print("allow_sandbox_name 1: start") + + p_s_name == i_s_name + + print("allow_sandbox_name 1: true") +} +allow_sandbox_name(p_s_name, i_s_name) { + print("allow_sandbox_name 2: start") + + # TODO: should generated names be handled differently? + contains(p_s_name, "$(generated-name)") + + print("allow_sandbox_name 2: true") +} + +# Check that the "io.kubernetes.cri.container-type" and +# "io.katacontainers.pkg.oci.container_type" annotations designate the +# expected type - either a "sandbox" or a "container". Then, validate +# other annotations based on the actual "sandbox" or "container" value +# from the input container. +allow_by_container_types(p_oci, i_oci, s_name, s_namespace) { + print("allow_by_container_types: checking io.kubernetes.cri.container-type") + + c_type := "io.kubernetes.cri.container-type" + + p_cri_type := p_oci.Annotations[c_type] + i_cri_type := i_oci.Annotations[c_type] + print("allow_by_container_types: p_cri_type =", p_cri_type, "i_cri_type =", i_cri_type) + p_cri_type == i_cri_type + + allow_by_container_type(i_cri_type, p_oci, i_oci, s_name, s_namespace) + + print("allow_by_container_types: true") +} + +allow_by_container_type(i_cri_type, p_oci, i_oci, s_name, s_namespace) { + print("allow_by_container_type 1: i_cri_type =", i_cri_type) + i_cri_type == "sandbox" + + i_kata_type := i_oci.Annotations["io.katacontainers.pkg.oci.container_type"] + print("allow_by_container_type 1: i_kata_type =", i_kata_type) + i_kata_type == "pod_sandbox" + + allow_sandbox_container_name(p_oci, i_oci) + allow_sandbox_net_namespace(p_oci, i_oci) + allow_sandbox_log_directory(p_oci, i_oci, s_name, s_namespace) + + print("allow_by_container_type 1: true") +} + +allow_by_container_type(i_cri_type, p_oci, i_oci, s_name, s_namespace) { + print("allow_by_container_type 2: i_cri_type =", i_cri_type) + i_cri_type == "container" + + i_kata_type := i_oci.Annotations["io.katacontainers.pkg.oci.container_type"] + print("allow_by_container_type 2: i_kata_type =", i_kata_type) + i_kata_type == "pod_container" + + allow_container_name(p_oci, i_oci) + allow_net_namespace(p_oci, i_oci) + allow_log_directory(p_oci, i_oci) + + print("allow_by_container_type 2: true") +} + +# "io.kubernetes.cri.container-name" annotation +allow_sandbox_container_name(p_oci, i_oci) { + print("allow_sandbox_container_name: start") + + container_annotation_missing(p_oci, i_oci, "io.kubernetes.cri.container-name") + + print("allow_sandbox_container_name: true") +} + +allow_container_name(p_oci, i_oci) { + print("allow_container_name: start") + + allow_container_annotation(p_oci, i_oci, "io.kubernetes.cri.container-name") + + print("allow_container_name: true") +} + +container_annotation_missing(p_oci, i_oci, key) { + print("container_annotation_missing:", key) + + not p_oci.Annotations[key] + not i_oci.Annotations[key] + + print("container_annotation_missing: true") +} + +allow_container_annotation(p_oci, i_oci, key) { + print("allow_container_annotation: key =", key) + + p_value := p_oci.Annotations[key] + i_value := i_oci.Annotations[key] + print("allow_container_annotation: p_value =", p_value, "i_value =", i_value) + + p_value == i_value + + print("allow_container_annotation: true") +} + +# "nerdctl/network-namespace" annotation +allow_sandbox_net_namespace(p_oci, i_oci) { + print("allow_sandbox_net_namespace: start") + + key := "nerdctl/network-namespace" + + p_namespace := p_oci.Annotations[key] + i_namespace := i_oci.Annotations[key] + print("allow_sandbox_net_namespace: p_namespace =", p_namespace, "i_namespace =", i_namespace) + + regex.match(p_namespace, i_namespace) + + print("allow_sandbox_net_namespace: true") +} + +allow_net_namespace(p_oci, i_oci) { + print("allow_net_namespace: start") + + key := "nerdctl/network-namespace" + + not p_oci.Annotations[key] + not i_oci.Annotations[key] + + print("allow_net_namespace: true") +} + +# "io.kubernetes.cri.sandbox-log-directory" annotation +allow_sandbox_log_directory(p_oci, i_oci, s_name, s_namespace) { + print("allow_sandbox_log_directory: start") + + key := "io.kubernetes.cri.sandbox-log-directory" + + p_dir := p_oci.Annotations[key] + regex1 := replace(p_dir, "$(sandbox-name)", s_name) + regex2 := replace(regex1, "$(sandbox-namespace)", s_namespace) + print("allow_sandbox_log_directory: regex2 =", regex2) + + i_dir := i_oci.Annotations[key] + print("allow_sandbox_log_directory: i_dir =", i_dir) + + regex.match(regex2, i_dir) + + print("allow_sandbox_log_directory: true") +} + +allow_log_directory(p_oci, i_oci) { + print("allow_log_directory: start") + + key := "io.kubernetes.cri.sandbox-log-directory" + + not p_oci.Annotations[key] + not i_oci.Annotations[key] + + print("allow_log_directory: true") +} + +allow_linux(p_oci, i_oci) { + 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 + + allow_masked_paths(p_oci, i_oci) + allow_readonly_paths(p_oci, i_oci) + + print("allow_linux: true") +} + +allow_masked_paths(p_oci, i_oci) { + p_paths := p_oci.Linux.MaskedPaths + print("allow_masked_paths 1: p_paths =", p_paths) + + i_paths := i_oci.Linux.MaskedPaths + print("allow_masked_paths 1: i_paths =", i_paths) + + allow_masked_paths_array(p_paths, i_paths) + + print("allow_masked_paths 1: true") +} +allow_masked_paths(p_oci, i_oci) { + print("allow_masked_paths 2: start") + + not p_oci.Linux.MaskedPaths + not i_oci.Linux.MaskedPaths + + print("allow_masked_paths 2: true") +} + +# All the policy masked paths must be masked in the input data too. +# Input is allowed to have more masked paths than the policy. +allow_masked_paths_array(p_array, i_array) { + every p_elem in p_array { + allow_masked_path(p_elem, i_array) + } +} + +allow_masked_path(p_elem, i_array) { + print("allow_masked_path: p_elem =", p_elem) + + some i_elem in i_array + p_elem == i_elem + + print("allow_masked_path: true") +} + +allow_readonly_paths(p_oci, i_oci) { + p_paths := p_oci.Linux.ReadonlyPaths + print("allow_readonly_paths 1: p_paths =", p_paths) + + i_paths := i_oci.Linux.ReadonlyPaths + print("allow_readonly_paths 1: i_paths =", i_paths) + + allow_readonly_paths_array(p_paths, i_paths, i_oci.Linux.MaskedPaths) + + print("allow_readonly_paths 1: true") +} +allow_readonly_paths(p_oci, i_oci) { + print("allow_readonly_paths 2: start") + + not p_oci.Linux.ReadonlyPaths + not i_oci.Linux.ReadonlyPaths + + print("allow_readonly_paths 2: true") +} + +# All the policy readonly paths must be either: +# - Present in the input readonly paths, or +# - Present in the input masked paths. +# Input is allowed to have more readonly paths than the policy. +allow_readonly_paths_array(p_array, i_array, masked_paths) { + every p_elem in p_array { + allow_readonly_path(p_elem, i_array, masked_paths) + } +} + +allow_readonly_path(p_elem, i_array, masked_paths) { + print("allow_readonly_path 1: p_elem =", p_elem) + + some i_elem in i_array + p_elem == i_elem + + print("allow_readonly_path 1: true") +} +allow_readonly_path(p_elem, i_array, masked_paths) { + print("allow_readonly_path 2: p_elem =", p_elem) + + some i_masked in masked_paths + p_elem == i_masked + + print("allow_readonly_path 2: true") +} + +# Check the consistency of the input "io.katacontainers.pkg.oci.bundle_path" +# and io.kubernetes.cri.sandbox-id" values with other fields. +allow_by_bundle_or_sandbox_id(p_oci, i_oci, p_storages, i_storages) { + print("allow_by_bundle_or_sandbox_id: start") + + bundle_path := i_oci.Annotations["io.katacontainers.pkg.oci.bundle_path"] + bundle_id := replace(bundle_path, "/run/containerd/io.containerd.runtime.v2.task/k8s.io/", "") + + key := "io.kubernetes.cri.sandbox-id" + + p_regex := p_oci.Annotations[key] + sandbox_id := i_oci.Annotations[key] + + print("allow_by_bundle_or_sandbox_id: sandbox_id =", sandbox_id, "regex =", p_regex) + regex.match(p_regex, sandbox_id) + + allow_root_path(p_oci, i_oci, bundle_id) + + every i_mount in input.OCI.Mounts { + allow_mount(p_oci, i_mount, bundle_id, sandbox_id) + } + + # TODO: enable allow_storages() after fixing https://github.com/kata-containers/kata-containers/issues/8833 + # allow_storages(p_storages, i_storages, bundle_id, sandbox_id) + + print("allow_by_bundle_or_sandbox_id: true") +} + +allow_process(p_oci, i_oci, s_name) { + p_process := p_oci.Process + i_process := i_oci.Process + + print("allow_process: i terminal =", i_process.Terminal, "p terminal =", p_process.Terminal) + p_process.Terminal == i_process.Terminal + + print("allow_process: i cwd =", i_process.Cwd, "i cwd =", p_process.Cwd) + p_process.Cwd == i_process.Cwd + + print("allow_process: i noNewPrivileges =", i_process.NoNewPrivileges, "p noNewPrivileges =", p_process.NoNewPrivileges) + p_process.NoNewPrivileges == i_process.NoNewPrivileges + + allow_caps(p_process.Capabilities, i_process.Capabilities) + allow_user(p_process, i_process) + allow_args(p_process, i_process, s_name) + allow_env(p_process, i_process, s_name) + + print("allow_process: true") +} + +allow_user(p_process, i_process) { + p_user := p_process.User + i_user := i_process.User + + # TODO: track down the reason for mcr.microsoft.com/oss/bitnami/redis:6.0.8 being + # executed with uid = 0 despite having "User": "1001" in its container image + # config. + #print("allow_user: input uid =", i_user.UID, "policy uid =", p_user.UID) + #p_user.UID == i_user.UID + + # TODO: track down the reason for registry.k8s.io/pause:3.9 being + # executed with gid = 0 despite having "65535:65535" in its container image + # config. + #print("allow_user: input gid =", i_user.GID, "policy gid =", p_user.GID) + #p_user.GID == i_user.GID + + # TODO: compare the additionalGids field too after computing its value + # based on /etc/passwd and /etc/group from the container image. +} + +allow_args(p_process, i_process, s_name) { + print("allow_args 1: no args") + + not p_process.Args + not i_process.Args + + print("allow_args 1: true") +} +allow_args(p_process, i_process, s_name) { + print("allow_args 2: policy args =", p_process.Args) + print("allow_args 2: input args =", i_process.Args) + + count(p_process.Args) == count(i_process.Args) + + every i, i_arg in i_process.Args { + allow_arg(i, i_arg, p_process, s_name) + } + + print("allow_args 2: true") +} +allow_arg(i, i_arg, p_process, s_name) { + p_arg := p_process.Args[i] + print("allow_arg 1: i =", i, "i_arg =", i_arg, "p_arg =", p_arg) + + p_arg2 := replace(p_arg, "$$", "$") + p_arg2 == i_arg + + print("allow_arg 1: true") +} +allow_arg(i, i_arg, p_process, s_name) { + p_arg := p_process.Args[i] + print("allow_arg 2: i =", i, "i_arg =", i_arg, "p_arg =", p_arg) + + # TODO: can $(node-name) be handled better? + contains(p_arg, "$(node-name)") + + print("allow_arg 2: true") +} +allow_arg(i, i_arg, p_process, s_name) { + p_arg := p_process.Args[i] + print("allow_arg 3: i =", i, "i_arg =", i_arg, "p_arg =", p_arg) + + p_arg2 := replace(p_arg, "$$", "$") + p_arg3 := replace(p_arg2, "$(sandbox-name)", s_name) + print("allow_arg 3: p_arg3 =", p_arg3) + p_arg3 == i_arg + + print("allow_arg 3: true") +} + +# OCI process.Env field +allow_env(p_process, i_process, s_name) { + print("allow_env: p env =", p_process.Env) + print("allow_env: i env =", i_process.Env) + + every i_var in i_process.Env { + print("allow_env: i_var =", i_var) + allow_var(p_process, i_process, i_var, s_name) + } + + print("allow_env: true") +} + +# Allow input env variables that are present in the policy data too. +allow_var(p_process, i_process, i_var, s_name) { + some p_var in p_process.Env + p_var == i_var + print("allow_var 1: true") +} + +# Match input with one of the policy variables, after substituting $(sandbox-name). +allow_var(p_process, i_process, i_var, s_name) { + some p_var in p_process.Env + p_var2 := replace(p_var, "$(sandbox-name)", s_name) + + print("allow_var 2: p_var2 =", p_var2) + p_var2 == i_var + + print("allow_var 2: true") +} + +# Allow input env variables that match with a request_defaults regex. +allow_var(p_process, i_process, i_var, s_name) { + some p_regex1 in policy_data.request_defaults.CreateContainerRequest.allow_env_regex + p_regex2 := replace(p_regex1, "$(ipv4_a)", policy_data.common.ipv4_a) + p_regex3 := replace(p_regex2, "$(ip_p)", policy_data.common.ip_p) + p_regex4 := replace(p_regex3, "$(svc_name)", policy_data.common.svc_name) + p_regex5 := replace(p_regex4, "$(dns_label)", policy_data.common.dns_label) + + print("allow_var 3: p_regex5 =", p_regex5) + regex.match(p_regex5, i_var) + + print("allow_var 3: true") +} + +# Allow fieldRef "fieldPath: status.podIP" values. +allow_var(p_process, i_process, i_var, s_name) { + name_value := split(i_var, "=") + count(name_value) == 2 + is_ip(name_value[1]) + + some p_var in p_process.Env + allow_pod_ip_var(name_value[0], p_var) + + print("allow_var 4: true") +} + +# Allow common fieldRef variables. +allow_var(p_process, i_process, i_var, s_name) { + name_value := split(i_var, "=") + count(name_value) == 2 + + some p_var in p_process.Env + p_name_value := split(p_var, "=") + count(p_name_value) == 2 + + p_name_value[0] == name_value[0] + + # TODO: should these be handled in a different way? + always_allowed := ["$(host-name)", "$(node-name)", "$(pod-uid)"] + some allowed in always_allowed + contains(p_name_value[1], allowed) + + print("allow_var 5: true") +} + +# Allow fieldRef "fieldPath: status.hostIP" values. +allow_var(p_process, i_process, i_var, s_name) { + name_value := split(i_var, "=") + count(name_value) == 2 + is_ip(name_value[1]) + + some p_var in p_process.Env + allow_host_ip_var(name_value[0], p_var) + + print("allow_var 6: true") +} + +# Allow resourceFieldRef values (e.g., "limits.cpu"). +allow_var(p_process, i_process, i_var, s_name) { + name_value := split(i_var, "=") + count(name_value) == 2 + + some p_var in p_process.Env + p_name_value := split(p_var, "=") + count(p_name_value) == 2 + + p_name_value[0] == name_value[0] + + # TODO: should these be handled in a different way? + always_allowed = ["$(resource-field)", "$(todo-annotation)"] + some allowed in always_allowed + contains(p_name_value[1], allowed) + + print("allow_var 7: true") +} + +allow_pod_ip_var(var_name, p_var) { + print("allow_pod_ip_var: var_name =", var_name, "p_var =", p_var) + + p_name_value := split(p_var, "=") + count(p_name_value) == 2 + + p_name_value[0] == var_name + p_name_value[1] == "$(pod-ip)" + + print("allow_pod_ip_var: true") +} + +allow_host_ip_var(var_name, p_var) { + print("allow_host_ip_var: var_name =", var_name, "p_var =", p_var) + + p_name_value := split(p_var, "=") + count(p_name_value) == 2 + + p_name_value[0] == var_name + p_name_value[1] == "$(host-ip)" + + print("allow_host_ip_var: true") +} + +is_ip(value) { + bytes = split(value, ".") + count(bytes) == 4 + + is_ip_first_byte(bytes[0]) + is_ip_other_byte(bytes[1]) + is_ip_other_byte(bytes[2]) + is_ip_other_byte(bytes[3]) +} +is_ip_first_byte(component) { + number = to_number(component) + number >= 1 + number <= 255 +} +is_ip_other_byte(component) { + number = to_number(component) + number >= 0 + number <= 255 +} + +# OCI root.Path +allow_root_path(p_oci, i_oci, bundle_id) { + i_path := i_oci.Root.Path + p_path1 := p_oci.Root.Path + print("allow_root_path: i_path =", i_path, "p_path1 =", p_path1) + + p_path2 := replace(p_path1, "$(cpath)", policy_data.common.cpath) + print("allow_root_path: p_path2 =", p_path2) + + p_path3 := replace(p_path2, "$(bundle-id)", bundle_id) + print("allow_root_path: p_path3 =", p_path3) + + p_path3 == i_path + + print("allow_root_path: true") +} + +# device mounts +allow_mount(p_oci, i_mount, bundle_id, sandbox_id) { + print("allow_mount: i_mount =", i_mount) + + some p_mount in p_oci.Mounts + print("allow_mount: p_mount =", p_mount) + check_mount(p_mount, i_mount, bundle_id, sandbox_id) + + # TODO: are there any other required policy checks for mounts - e.g., + # multiple mounts with same source or destination? + + print("allow_mount: true") +} + +check_mount(p_mount, i_mount, bundle_id, sandbox_id) { + p_mount == i_mount + print("check_mount 1: true") +} +check_mount(p_mount, i_mount, bundle_id, sandbox_id) { + p_mount.destination == i_mount.destination + p_mount.type_ == i_mount.type_ + p_mount.options == i_mount.options + + mount_source_allows(p_mount, i_mount, bundle_id, sandbox_id) + + print("check_mount 2: true") +} + +mount_source_allows(p_mount, i_mount, bundle_id, sandbox_id) { + regex1 := p_mount.source + regex2 := replace(regex1, "$(sfprefix)", policy_data.common.sfprefix) + regex3 := replace(regex2, "$(cpath)", policy_data.common.cpath) + regex4 := replace(regex3, "$(bundle-id)", bundle_id) + + print("mount_source_allows 1: regex4 =", regex4) + regex.match(regex4, i_mount.source) + + print("mount_source_allows 1: true") +} +mount_source_allows(p_mount, i_mount, bundle_id, sandbox_id) { + regex1 := p_mount.source + regex2 := replace(regex1, "$(sfprefix)", policy_data.common.sfprefix) + regex3 := replace(regex2, "$(cpath)", policy_data.common.cpath) + regex4 := replace(regex3, "$(sandbox-id)", sandbox_id) + + print("mount_source_allows 2: regex4 =", regex4) + regex.match(regex4, i_mount.source) + + print("mount_source_allows 2: true") +} + +###################################################################### +# Storages + +allow_storages(p_storages, i_storages, bundle_id, sandbox_id) { + p_count := count(p_storages) + i_count := count(i_storages) + print("allow_storages: p_count =", p_count, "i_count =", i_count) + + p_count == i_count + + # Get the container image layer IDs and verity root hashes, from the "overlayfs" storage. + some overlay_storage in p_storages + overlay_storage.driver == "overlayfs" + print("allow_storages: overlay_storage =", overlay_storage) + count(overlay_storage.options) == 2 + + layer_ids := split(overlay_storage.options[0], ":") + print("allow_storages: layer_ids =", layer_ids) + + root_hashes := split(overlay_storage.options[1], ":") + print("allow_storages: root_hashes =", root_hashes) + + every i_storage in i_storages { + allow_storage(p_storages, i_storage, bundle_id, sandbox_id, layer_ids, root_hashes) + } + + print("allow_storages: true") +} + +allow_storage(p_storages, i_storage, bundle_id, sandbox_id, layer_ids, root_hashes) { + some p_storage in p_storages + + print("allow_storage: p_storage =", p_storage) + print("allow_storage: i_storage =", i_storage) + + p_storage.driver == i_storage.driver + p_storage.driver_options == i_storage.driver_options + p_storage.fs_group == i_storage.fs_group + + allow_storage_options(p_storage, i_storage, layer_ids, root_hashes) + allow_mount_point(p_storage, i_storage, bundle_id, sandbox_id, layer_ids) + + # TODO: validate the source field too. + + print("allow_storage: true") +} + +allow_storage_options(p_storage, i_storage, layer_ids, root_hashes) { + print("allow_storage_options 1: start") + + p_storage.driver != "blk" + p_storage.driver != "overlayfs" + p_storage.options == i_storage.options + + print("allow_storage_options 1: true") +} +allow_storage_options(p_storage, i_storage, layer_ids, root_hashes) { + print("allow_storage_options 2: start") + + p_storage.driver == "overlayfs" + count(p_storage.options) == 2 + + policy_ids := split(p_storage.options[0], ":") + print("allow_storage_options 2: policy_ids =", policy_ids) + policy_ids == layer_ids + + policy_hashes := split(p_storage.options[1], ":") + print("allow_storage_options 2: policy_hashes =", policy_hashes) + + p_count := count(policy_ids) + print("allow_storage_options 2: p_count =", p_count) + p_count >= 1 + p_count == count(policy_hashes) + + i_count := count(i_storage.options) + print("allow_storage_options 2: i_count =", i_count) + i_count == p_count + 3 + + print("allow_storage_options 2: i_storage.options[0] =", i_storage.options[0]) + i_storage.options[0] == "io.katacontainers.fs-opt.layer-src-prefix=/var/lib/containerd/io.containerd.snapshotter.v1.tardev/layers" + + print("allow_storage_options 2: i_storage.options[i_count - 2] =", i_storage.options[i_count - 2]) + i_storage.options[i_count - 2] == "io.katacontainers.fs-opt.overlay-rw" + + lowerdir := concat("=", ["lowerdir", p_storage.options[0]]) + print("allow_storage_options 2: lowerdir =", lowerdir) + + i_storage.options[i_count - 1] == lowerdir + print("allow_storage_options 2: i_storage.options[i_count - 1] =", i_storage.options[i_count - 1]) + + every i, policy_id in policy_ids { + allow_overlay_layer(policy_id, policy_hashes[i], i_storage.options[i + 1]) + } + + print("allow_storage_options 2: true") +} +allow_storage_options(p_storage, i_storage, layer_ids, root_hashes) { + print("allow_storage_options 3: start") + + p_storage.driver == "blk" + count(p_storage.options) == 1 + + startswith(p_storage.options[0], "$(hash") + hash_suffix := trim_left(p_storage.options[0], "$(hash") + + endswith(hash_suffix, ")") + hash_index := trim_right(hash_suffix, ")") + i := to_number(hash_index) + print("allow_storage_options 3: i =", i) + + hash_option := concat("=", ["io.katacontainers.fs-opt.root-hash", root_hashes[i]]) + print("allow_storage_options 3: hash_option =", hash_option) + + count(i_storage.options) == 4 + i_storage.options[0] == "ro" + i_storage.options[1] == "io.katacontainers.fs-opt.block_device=file" + i_storage.options[2] == "io.katacontainers.fs-opt.is-layer" + i_storage.options[3] == hash_option + + print("allow_storage_options 3: true") +} + +allow_overlay_layer(policy_id, policy_hash, i_option) { + print("allow_overlay_layer: policy_id =", policy_id, "policy_hash =", policy_hash) + print("allow_overlay_layer: i_option =", i_option) + + startswith(i_option, "io.katacontainers.fs-opt.layer=") + i_value := replace(i_option, "io.katacontainers.fs-opt.layer=", "") + i_value_decoded := base64.decode(i_value) + print("allow_overlay_layer: i_value_decoded =", i_value_decoded) + + policy_suffix := concat("=", ["tar,ro,io.katacontainers.fs-opt.block_device=file,io.katacontainers.fs-opt.is-layer,io.katacontainers.fs-opt.root-hash", policy_hash]) + p_value := concat(",", [policy_id, policy_suffix]) + print("allow_overlay_layer: p_value =", p_value) + + p_value == i_value_decoded + + print("allow_overlay_layer: true") +} + +allow_mount_point(p_storage, i_storage, bundle_id, sandbox_id, layer_ids) { + p_storage.fstype == "tar" + + startswith(p_storage.mount_point, "$(layer") + mount_suffix := trim_left(p_storage.mount_point, "$(layer") + + endswith(mount_suffix, ")") + layer_index := trim_right(mount_suffix, ")") + i := to_number(layer_index) + print("allow_mount_point 1: i =", i) + + layer_id := layer_ids[i] + print("allow_mount_point 1: layer_id =", layer_id) + + p_mount := concat("/", ["/run/kata-containers/sandbox/layers", layer_id]) + print("allow_mount_point 1: p_mount =", p_mount) + + p_mount == i_storage.mount_point + + print("allow_mount_point 1: true") +} +allow_mount_point(p_storage, i_storage, bundle_id, sandbox_id, layer_ids) { + p_storage.fstype == "fuse3.kata-overlay" + + mount1 := replace(p_storage.mount_point, "$(cpath)", policy_data.common.cpath) + mount2 := replace(mount1, "$(bundle-id)", bundle_id) + print("allow_mount_point 2: mount2 =", mount2) + + mount2 == i_storage.mount_point + + print("allow_mount_point 2: true") +} +allow_mount_point(p_storage, i_storage, bundle_id, sandbox_id, layer_ids) { + p_storage.fstype == "local" + + mount1 := p_storage.mount_point + print("allow_mount_point 3: mount1 =", mount1) + + mount2 := replace(mount1, "$(cpath)", policy_data.common.cpath) + print("allow_mount_point 3: mount2 =", mount2) + + mount3 := replace(mount2, "$(sandbox-id)", sandbox_id) + print("allow_mount_point 3: mount3 =", mount3) + + regex.match(mount3, i_storage.mount_point) + + print("allow_mount_point 3: true") +} +allow_mount_point(p_storage, i_storage, bundle_id, sandbox_id, layer_ids) { + p_storage.fstype == "bind" + + mount1 := p_storage.mount_point + print("allow_mount_point 4: mount1 =", mount1) + + mount2 := replace(mount1, "$(cpath)", policy_data.common.cpath) + print("allow_mount_point 4: mount2 =", mount2) + + mount3 := replace(mount2, "$(bundle-id)", bundle_id) + print("allow_mount_point 4: mount3 =", mount3) + + regex.match(mount3, i_storage.mount_point) + + print("allow_mount_point 4: true") +} +allow_mount_point(p_storage, i_storage, bundle_id, sandbox_id, layer_ids) { + p_storage.fstype == "tmpfs" + + mount1 := p_storage.mount_point + print("allow_mount_point 5: mount1 =", mount1) + + regex.match(mount1, i_storage.mount_point) + + print("allow_mount_point 5: true") +} + +# process.Capabilities +allow_caps(p_caps, i_caps) { + print("allow_caps: policy Ambient =", p_caps.Ambient) + print("allow_caps: input Ambient =", i_caps.Ambient) + match_caps(p_caps.Ambient, i_caps.Ambient) + + print("allow_caps: policy Bounding =", p_caps.Bounding) + print("allow_caps: input Bounding =", i_caps.Bounding) + match_caps(p_caps.Bounding, i_caps.Bounding) + + print("allow_caps: policy Effective =", p_caps.Effective) + print("allow_caps: input Effective =", i_caps.Effective) + match_caps(p_caps.Effective, i_caps.Effective) + + print("allow_caps: policy Inheritable =", p_caps.Inheritable) + print("allow_caps: input Inheritable =", i_caps.Inheritable) + match_caps(p_caps.Inheritable, i_caps.Inheritable) + + print("allow_caps: policy Permitted =", p_caps.Permitted) + print("allow_caps: input Permitted =", i_caps.Permitted) + match_caps(p_caps.Permitted, i_caps.Permitted) +} + +match_caps(p_caps, i_caps) { + print("match_caps 1: start") + + p_caps == i_caps + + print("match_caps 1: true") +} +match_caps(p_caps, i_caps) { + print("match_caps 2: start") + + count(p_caps) == 1 + p_caps[0] == "$(default_caps)" + + print("match_caps 2: default_caps =", policy_data.common.default_caps) + policy_data.common.default_caps == i_caps + + print("match_caps 2: true") +} +match_caps(p_caps, i_caps) { + print("match_caps 3: start") + + count(p_caps) == 1 + p_caps[0] == "$(privileged_caps)" + + print("match_caps 3: privileged_caps =", policy_data.common.privileged_caps) + policy_data.common.privileged_caps == i_caps + + print("match_caps 3: true") +} + +###################################################################### +CopyFileRequest { + print("CopyFileRequest: input.path =", input.path) + + some regex1 in policy_data.request_defaults.CopyFileRequest + regex2 := replace(regex1, "$(cpath)", policy_data.common.cpath) + regex.match(regex2, input.path) + + print("CopyFileRequest: true") +} + +CreateSandboxRequest { + i_pidns := input.sandbox_pidns + print("CreateSandboxRequest: i_pidns =", i_pidns) + i_pidns == false +} + +ExecProcessRequest { + print("ExecProcessRequest 1: input =", input) + + i_command = concat(" ", input.process.Args) + print("ExecProcessRequest 1: i_command =", i_command) + + some p_command in policy_data.request_defaults.ExecProcessRequest.commands + print("ExecProcessRequest 1: p_command =", p_command) + p_command == i_command + + print("ExecProcessRequest 1: true") +} +ExecProcessRequest { + print("ExecProcessRequest 2: input =", input) + + # TODO: match input container ID with its corresponding container.exec_commands. + i_command = concat(" ", input.process.Args) + print("ExecProcessRequest 3: i_command =", i_command) + + some container in policy_data.containers + some p_command in container.exec_commands + print("ExecProcessRequest 2: p_command =", p_command) + + # TODO: should other input data fields be validated as well? + p_command == i_command + + print("ExecProcessRequest 2: true") +} +ExecProcessRequest { + print("ExecProcessRequest 3: input =", input) + + i_command = concat(" ", input.process.Args) + print("ExecProcessRequest 3: i_command =", i_command) + + some p_regex in policy_data.request_defaults.ExecProcessRequest.regex + print("ExecProcessRequest 3: p_regex =", p_regex) + + regex.match(p_regex, i_command) + + print("ExecProcessRequest 3: true") +} + +CloseStdinRequest { + policy_data.request_defaults.CloseStdinRequest == true +} + +ReadStreamRequest { + policy_data.request_defaults.ReadStreamRequest == true +} + +WriteStreamRequest { + policy_data.request_defaults.WriteStreamRequest == true +} + +policy_data := { + "containers": [ + { + "OCI": { + "Version": "1.1.0", + "Process": { + "Terminal": false, + "User": { + "UID": 65535, + "GID": 65535, + "AdditionalGids": [], + "Username": "" + }, + "Args": [ + "/pause" + ], + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "Cwd": "/", + "Capabilities": { + "Ambient": [], + "Bounding": [ + "$(default_caps)" + ], + "Effective": [ + "$(default_caps)" + ], + "Inheritable": [], + "Permitted": [ + "$(default_caps)" + ] + }, + "NoNewPrivileges": true + }, + "Root": { + "Path": "$(cpath)/$(bundle-id)/rootfs", + "Readonly": true + }, + "Mounts": [ + { + "destination": "/proc", + "source": "proc", + "type_": "proc", + "options": [ + "nosuid", + "noexec", + "nodev" + ] + }, + { + "destination": "/dev", + "source": "tmpfs", + "type_": "tmpfs", + "options": [ + "nosuid", + "strictatime", + "mode=755", + "size=65536k" + ] + }, + { + "destination": "/dev/pts", + "source": "devpts", + "type_": "devpts", + "options": [ + "nosuid", + "noexec", + "newinstance", + "ptmxmode=0666", + "mode=0620", + "gid=5" + ] + }, + { + "destination": "/dev/shm", + "source": "/run/kata-containers/sandbox/shm", + "type_": "bind", + "options": [ + "rbind" + ] + }, + { + "destination": "/dev/mqueue", + "source": "mqueue", + "type_": "mqueue", + "options": [ + "nosuid", + "noexec", + "nodev" + ] + }, + { + "destination": "/sys", + "source": "sysfs", + "type_": "sysfs", + "options": [ + "nosuid", + "noexec", + "nodev", + "ro" + ] + }, + { + "destination": "/etc/resolv.conf", + "source": "$(sfprefix)resolv.conf$", + "type_": "bind", + "options": [ + "rbind", + "ro", + "nosuid", + "nodev", + "noexec" + ] + } + ], + "Annotations": { + "io.katacontainers.pkg.oci.bundle_path": "/run/containerd/io.containerd.runtime.v2.task/k8s.io/$(bundle-id)", + "io.katacontainers.pkg.oci.container_type": "pod_sandbox", + "io.kubernetes.cri.container-type": "sandbox", + "io.kubernetes.cri.sandbox-id": "^[a-z0-9]{64}$", + "io.kubernetes.cri.sandbox-log-directory": "^/var/log/pods/$(sandbox-namespace)_$(sandbox-name)_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", + "io.kubernetes.cri.sandbox-name": "policy-pod", + "io.kubernetes.cri.sandbox-namespace": "kata-containers-k8s-tests", + "nerdctl/network-namespace": "^/var/run/netns/cni-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" + }, + "Linux": { + "Namespaces": [ + { + "Type": "ipc", + "Path": "" + }, + { + "Type": "uts", + "Path": "" + }, + { + "Type": "mount", + "Path": "" + } + ], + "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" + ] + } + }, + "storages": [ + { + "driver": "blk", + "driver_options": [], + "source": "", + "fstype": "tar", + "options": [ + "$(hash0)" + ], + "mount_point": "$(layer0)", + "fs_group": null + }, + { + "driver": "overlayfs", + "driver_options": [], + "source": "", + "fstype": "fuse3.kata-overlay", + "options": [ + "5a5aad80055ff20012a50dc25f8df7a29924474324d65f7d5306ee8ee27ff71d", + "817250f1a3e336da76f5bd3fa784e1b26d959b9c131876815ba2604048b70c18" + ], + "mount_point": "$(cpath)/$(bundle-id)/rootfs", + "fs_group": null + } + ], + "sandbox_pidns": false, + "exec_commands": [] + }, + { + "OCI": { + "Version": "1.1.0", + "Process": { + "Terminal": false, + "User": { + "UID": 0, + "GID": 0, + "AdditionalGids": [], + "Username": "" + }, + "Args": [ + "sh" + ], + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "HOSTNAME=$(host-name)", + "KUBE_CONFIG_1=value-1", + "KUBE_CONFIG_2=value-2" + ], + "Cwd": "/", + "Capabilities": { + "Ambient": [], + "Bounding": [ + "$(default_caps)" + ], + "Effective": [ + "$(default_caps)" + ], + "Inheritable": [], + "Permitted": [ + "$(default_caps)" + ] + }, + "NoNewPrivileges": false + }, + "Root": { + "Path": "$(cpath)/$(bundle-id)/rootfs", + "Readonly": false + }, + "Mounts": [ + { + "destination": "/proc", + "source": "proc", + "type_": "proc", + "options": [ + "nosuid", + "noexec", + "nodev" + ] + }, + { + "destination": "/dev", + "source": "tmpfs", + "type_": "tmpfs", + "options": [ + "nosuid", + "strictatime", + "mode=755", + "size=65536k" + ] + }, + { + "destination": "/dev/pts", + "source": "devpts", + "type_": "devpts", + "options": [ + "nosuid", + "noexec", + "newinstance", + "ptmxmode=0666", + "mode=0620", + "gid=5" + ] + }, + { + "destination": "/dev/shm", + "source": "/run/kata-containers/sandbox/shm", + "type_": "bind", + "options": [ + "rbind" + ] + }, + { + "destination": "/dev/mqueue", + "source": "mqueue", + "type_": "mqueue", + "options": [ + "nosuid", + "noexec", + "nodev" + ] + }, + { + "destination": "/sys", + "source": "sysfs", + "type_": "sysfs", + "options": [ + "nosuid", + "noexec", + "nodev", + "ro" + ] + }, + { + "destination": "/sys/fs/cgroup", + "source": "cgroup", + "type_": "cgroup", + "options": [ + "nosuid", + "noexec", + "nodev", + "relatime", + "ro" + ] + }, + { + "destination": "/etc/hosts", + "source": "$(sfprefix)hosts$", + "type_": "bind", + "options": [ + "rbind", + "rprivate", + "rw" + ] + }, + { + "destination": "/dev/termination-log", + "source": "$(sfprefix)termination-log$", + "type_": "bind", + "options": [ + "rbind", + "rprivate", + "rw" + ] + }, + { + "destination": "/etc/hostname", + "source": "$(sfprefix)hostname$", + "type_": "bind", + "options": [ + "rbind", + "rprivate", + "rw" + ] + }, + { + "destination": "/etc/resolv.conf", + "source": "$(sfprefix)resolv.conf$", + "type_": "bind", + "options": [ + "rbind", + "rprivate", + "rw" + ] + }, + { + "destination": "/var/run/secrets/kubernetes.io/serviceaccount", + "source": "$(sfprefix)serviceaccount$", + "type_": "bind", + "options": [ + "rbind", + "rprivate", + "ro" + ] + }, + { + "destination": "/var/run/secrets/azure/tokens", + "source": "$(sfprefix)tokens$", + "type_": "bind", + "options": [ + "rbind", + "rprivate", + "ro" + ] + } + ], + "Annotations": { + "io.katacontainers.pkg.oci.bundle_path": "/run/containerd/io.containerd.runtime.v2.task/k8s.io/$(bundle-id)", + "io.katacontainers.pkg.oci.container_type": "pod_container", + "io.kubernetes.cri.container-name": "busybox", + "io.kubernetes.cri.container-type": "container", + "io.kubernetes.cri.image-name": "quay.io/prometheus/busybox:latest", + "io.kubernetes.cri.sandbox-id": "^[a-z0-9]{64}$", + "io.kubernetes.cri.sandbox-name": "policy-pod", + "io.kubernetes.cri.sandbox-namespace": "kata-containers-k8s-tests" + }, + "Linux": { + "Namespaces": [ + { + "Type": "ipc", + "Path": "" + }, + { + "Type": "uts", + "Path": "" + }, + { + "Type": "mount", + "Path": "" + } + ], + "MaskedPaths": [ + "/proc/acpi", + "/proc/kcore", + "/proc/keys", + "/proc/latency_stats", + "/proc/timer_list", + "/proc/timer_stats", + "/proc/sched_debug", + "/proc/scsi", + "/sys/firmware" + ], + "ReadonlyPaths": [ + "/proc/asound", + "/proc/bus", + "/proc/fs", + "/proc/irq", + "/proc/sys", + "/proc/sysrq-trigger" + ] + } + }, + "storages": [ + { + "driver": "blk", + "driver_options": [], + "source": "", + "fstype": "tar", + "options": [ + "$(hash0)" + ], + "mount_point": "$(layer0)", + "fs_group": null + }, + { + "driver": "blk", + "driver_options": [], + "source": "", + "fstype": "tar", + "options": [ + "$(hash1)" + ], + "mount_point": "$(layer1)", + "fs_group": null + }, + { + "driver": "overlayfs", + "driver_options": [], + "source": "", + "fstype": "fuse3.kata-overlay", + "options": [ + "b1ef3d18661ad727a8ad01d462fffc63ec90420a938056bc0422822328f83ff7:a8650d4a279d4537d7fc43c582a264528a96f041b1327d41b503f368c301594f", + "75ed5e78d30374b9557d32c17ae6d2e1b4afdc5efd128812e6c99a0f60708dba:2e52d86498030b4b99318650826d3f121bdfac5fe7bbbea9d18c546d48003aa0" + ], + "mount_point": "$(cpath)/$(bundle-id)/rootfs", + "fs_group": null + } + ], + "sandbox_pidns": false, + "exec_commands": [] + } + ], + "common": { + "cpath": "/run/kata-containers/shared/containers", + "sfprefix": "^$(cpath)/$(bundle-id)-[a-z0-9]{16}-", + "ipv4_a": "(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])", + "ip_p": "[0-9]{1,5}", + "svc_name": "[A-Z_\\.\\-]+", + "dns_label": "[a-zA-Z0-9_\\.\\-]+", + "default_caps": [ + "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" + ], + "privileged_caps": [ + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_DAC_READ_SEARCH", + "CAP_FOWNER", + "CAP_FSETID", + "CAP_KILL", + "CAP_SETGID", + "CAP_SETUID", + "CAP_SETPCAP", + "CAP_LINUX_IMMUTABLE", + "CAP_NET_BIND_SERVICE", + "CAP_NET_BROADCAST", + "CAP_NET_ADMIN", + "CAP_NET_RAW", + "CAP_IPC_LOCK", + "CAP_IPC_OWNER", + "CAP_SYS_MODULE", + "CAP_SYS_RAWIO", + "CAP_SYS_CHROOT", + "CAP_SYS_PTRACE", + "CAP_SYS_PACCT", + "CAP_SYS_ADMIN", + "CAP_SYS_BOOT", + "CAP_SYS_NICE", + "CAP_SYS_RESOURCE", + "CAP_SYS_TIME", + "CAP_SYS_TTY_CONFIG", + "CAP_MKNOD", + "CAP_LEASE", + "CAP_AUDIT_WRITE", + "CAP_AUDIT_CONTROL", + "CAP_SETFCAP", + "CAP_MAC_OVERRIDE", + "CAP_MAC_ADMIN", + "CAP_SYSLOG", + "CAP_WAKE_ALARM", + "CAP_BLOCK_SUSPEND", + "CAP_AUDIT_READ", + "CAP_PERFMON", + "CAP_BPF", + "CAP_CHECKPOINT_RESTORE" + ] + }, + "request_defaults": { + "CreateContainerRequest": { + "allow_env_regex": [ + "^HOSTNAME=$(dns_label)$", + "^$(svc_name)_PORT_$(ip_p)_TCP=tcp://$(ipv4_a):$(ip_p)$", + "^$(svc_name)_PORT_$(ip_p)_TCP_PROTO=tcp$", + "^$(svc_name)_PORT_$(ip_p)_TCP_PORT=$(ip_p)$", + "^$(svc_name)_PORT_$(ip_p)_TCP_ADDR=$(ipv4_a)$", + "^$(svc_name)_SERVICE_HOST=$(ipv4_a)$", + "^$(svc_name)_SERVICE_PORT=$(ip_p)$", + "^$(svc_name)_SERVICE_PORT_$(dns_label)=$(ip_p)$", + "^$(svc_name)_PORT=tcp://$(ipv4_a):$(ip_p)$", + "^AZURE_CLIENT_ID=[A-Fa-f0-9-]+$", + "^AZURE_TENANT_ID=[A-Fa-f0-9-]+$", + "^AZURE_FEDERATED_TOKEN_FILE=/var/run/secrets/azure/tokens/azure-identity-token$", + "^AZURE_AUTHORITY_HOST=https://login\\.microsoftonline\\.com/$" + ] + }, + "CopyFileRequest": [ + "^$(cpath)/" + ], + "ExecProcessRequest": { + "commands": [], + "regex": [] + }, + "CloseStdinRequest": false, + "ReadStreamRequest": false, + "WriteStreamRequest": false + } +} diff --git a/tests/kata/data/k8s-policy-rc/inputs.txt b/tests/kata/data/k8s-policy-rc/inputs.txt new file mode 100644 index 0000000..101e789 --- /dev/null +++ b/tests/kata/data/k8s-policy-rc/inputs.txt @@ -0,0 +1,33 @@ +["ep":"AllowRequestsFailingPolicy",{}], + +["ep":"UpdateInterfaceRequest",{"interface":{"device":"eth0","name":"eth0","IPAddresses":[{"family":0,"address":"10.244.2.68","mask":"24"},{"family":1,"address":"fe80::ec1f:81ff:fe86:255b","mask":"64"}],"mtu":1500,"hwAddr":"ee:1f:81:86:25:5b","pciPath":"","type_":"","raw_flags":0}}], + +["ep":"UpdateRoutesRequest",{"routes":{"Routes":[{"dest":"","gateway":"10.244.2.1","device":"eth0","source":"","scope":0,"family":0}]}}], + +["ep":"CreateSandboxRequest",{"hostname":"policy-rc-test-674df","dns":["search kata-containers-k8s-tests.svc.cluster.local svc.cluster.local cluster.local iete1orsbdmu5ia1kq2du43hqe.bx.internal.cloudapp.net","nameserver 10.0.0.10","options ndots:5",""],"storages":[{"driver":"virtio-fs","driver_options":[],"source":"kataShared","fstype":"virtiofs","options":[],"mount_point":"/run/kata-containers/shared/containers/","fs_group":null},{"driver":"ephemeral","driver_options":[],"source":"shm","fstype":"tmpfs","options":["noexec","nosuid","nodev","mode=1777","size=67108864"],"mount_point":"/run/kata-containers/sandbox/shm","fs_group":null}],"sandbox_pidns":false,"sandbox_id":"c469f18bb1c58dab6be747450b77c3321e43ba5dd26d62d19a599b82bdb0ddc5","guest_hook_path":"","kernel_modules":[]}], + +["ep":"GuestDetailsRequest",{"mem_block_size":true,"mem_hotplug_probe":true}], + +["ep":"CreateContainerRequest",{"container_id":"c469f18bb1c58dab6be747450b77c3321e43ba5dd26d62d19a599b82bdb0ddc5","exec_id":"c469f18bb1c58dab6be747450b77c3321e43ba5dd26d62d19a599b82bdb0ddc5","string_user":null,"devices":[],"storages":[],"OCI":{"Version":"1.1.0","Process":{"Terminal":false,"ConsoleSize":null,"User":{"UID":65535,"GID":65535,"AdditionalGids":[65535],"Username":""},"Args":["/pause"],"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cwd":"/","Capabilities":{"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"],"Inheritable":[],"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"],"Ambient":[]},"Rlimits":[],"NoNewPrivileges":true,"ApparmorProfile":"","OOMScoreAdj":-998,"SelinuxLabel":""},"Root":{"Path":"/run/kata-containers/shared/containers/c469f18bb1c58dab6be747450b77c3321e43ba5dd26d62d19a599b82bdb0ddc5/rootfs","Readonly":true},"Hostname":"policy-rc-test-674df","Mounts":[{"destination":"/proc","source":"proc","type_":"proc","options":["nosuid","noexec","nodev"]},{"destination":"/dev","source":"tmpfs","type_":"tmpfs","options":["nosuid","strictatime","mode=755","size=65536k"]},{"destination":"/dev/pts","source":"devpts","type_":"devpts","options":["nosuid","noexec","newinstance","ptmxmode=0666","mode=0620","gid=5"]},{"destination":"/dev/mqueue","source":"mqueue","type_":"mqueue","options":["nosuid","noexec","nodev"]},{"destination":"/sys","source":"sysfs","type_":"sysfs","options":["nosuid","noexec","nodev","ro"]},{"destination":"/dev/shm","source":"/run/kata-containers/sandbox/shm","type_":"bind","options":["rbind"]},{"destination":"/etc/resolv.conf","source":"/run/kata-containers/shared/containers/c469f18bb1c58dab6be747450b77c3321e43ba5dd26d62d19a599b82bdb0ddc5-e1c85415dcc483c7-resolv.conf","type_":"bind","options":["rbind","ro","nosuid","nodev","noexec"]}],"Hooks":null,"Annotations":{"io.kubernetes.cri.sandbox-uid":"eeb60852-0dcd-4387-b0f9-eebd63ddb7a8","io.katacontainers.pkg.oci.bundle_path":"/run/containerd/io.containerd.runtime.v2.task/k8s.io/c469f18bb1c58dab6be747450b77c3321e43ba5dd26d62d19a599b82bdb0ddc5","io.kubernetes.cri.sandbox-cpu-period":"100000","io.kubernetes.cri.sandbox-id":"c469f18bb1c58dab6be747450b77c3321e43ba5dd26d62d19a599b82bdb0ddc5","io.kubernetes.cri.sandbox-name":"policy-rc-test-674df","io.katacontainers.pkg.oci.container_type":"pod_sandbox","io.kubernetes.cri.container-type":"sandbox","nerdctl/network-namespace":"/var/run/netns/cni-939f1f21-5cec-1599-6804-471950c9c843","io.kubernetes.cri.sandbox-log-directory":"/var/log/pods/kata-containers-k8s-tests_policy-rc-test-674df_eeb60852-0dcd-4387-b0f9-eebd63ddb7a8","io.kubernetes.cri.sandbox-cpu-quota":"0","io.kubernetes.cri.sandbox-cpu-shares":"2","io.kubernetes.cri.sandbox-namespace":"kata-containers-k8s-tests","io.kubernetes.cri.sandbox-memory":"0"},"Linux":{"UIDMappings":[],"GIDMappings":[],"Sysctl":{},"Resources":{"Devices":[],"Memory":null,"CPU":{"Shares":2,"Quota":0,"Period":0,"RealtimeRuntime":0,"RealtimePeriod":0,"Cpus":"","Mems":""},"Pids":null,"BlockIO":null,"HugepageLimits":[],"Network":null},"CgroupsPath":"kubepods-besteffort-podeeb60852_0dcd_4387_b0f9_eebd63ddb7a8.slice:cri-containerd:c469f18bb1c58dab6be747450b77c3321e43ba5dd26d62d19a599b82bdb0ddc5","Namespaces":[{"Type":"ipc","Path":""},{"Type":"uts","Path":""},{"Type":"mount","Path":""}],"Devices":[],"Seccomp":null,"RootfsPropagation":"","MaskedPaths":["/proc/acpi","/proc/asound","/proc/kcore","/proc/keys","/proc/latency_stats","/proc/timer_list","/proc/timer_stats","/proc/sched_debug","/sys/firmware","/sys/devices/virtual/powercap","/proc/scsi"],"ReadonlyPaths":["/proc/bus","/proc/fs","/proc/irq","/proc/sys","/proc/sysrq-trigger"],"MountLabel":"","IntelRdt":null},"Solaris":null,"Windows":null},"sandbox_pidns":false,"shared_mounts":[],"stdin_port":0,"stdout_port":0,"stderr_port":0}], + +["ep":"OnlineCPUMemRequest",{"wait":false,"nb_cpus":0,"cpu_only":false}], + +["ep":"UpdateEphemeralMountsRequest",{"storages":[]}], + +["ep":"StartContainerRequest",{"container_id":"c469f18bb1c58dab6be747450b77c3321e43ba5dd26d62d19a599b82bdb0ddc5"}], + +["ep":"WaitProcessRequest",{"container_id":"c469f18bb1c58dab6be747450b77c3321e43ba5dd26d62d19a599b82bdb0ddc5","exec_id":"c469f18bb1c58dab6be747450b77c3321e43ba5dd26d62d19a599b82bdb0ddc5"}], + +["ep":"GetOOMEventRequest",{}], + +["ep":"CreateContainerRequest",{"container_id":"432a988cdb7fd4798e112d1a27a272d86e665dbfbdc4fc6937af127120b3a03e","exec_id":"432a988cdb7fd4798e112d1a27a272d86e665dbfbdc4fc6937af127120b3a03e","string_user":null,"devices":[],"storages":[{"driver":"local","driver_options":[],"source":"local","fstype":"local","options":["mode=0777"],"mount_point":"/run/kata-containers/shared/containers/c469f18bb1c58dab6be747450b77c3321e43ba5dd26d62d19a599b82bdb0ddc5/rootfs/local/host-empty-vol","fs_group":null}],"OCI":{"Version":"1.1.0","Process":{"Terminal":false,"ConsoleSize":null,"User":{"UID":0,"GID":0,"AdditionalGids":[0,1,2,3,4,6,10,11,20,26,27],"Username":""},"Args":["nginx","-g","daemon off;"],"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","HOSTNAME=policy-rc-test-674df","NGINX_VERSION=1.15.12","KUBERNETES_PORT_443_TCP=tcp://10.0.0.1:443","KUBERNETES_PORT_443_TCP_PROTO=tcp","KUBERNETES_PORT_443_TCP_PORT=443","KUBERNETES_PORT_443_TCP_ADDR=10.0.0.1","KUBERNETES_SERVICE_HOST=10.0.0.1","KUBERNETES_SERVICE_PORT=443","KUBERNETES_SERVICE_PORT_HTTPS=443","KUBERNETES_PORT=tcp://10.0.0.1:443"],"Cwd":"/","Capabilities":{"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"],"Inheritable":[],"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"],"Ambient":[]},"Rlimits":[],"NoNewPrivileges":false,"ApparmorProfile":"cri-containerd.apparmor.d","OOMScoreAdj":1000,"SelinuxLabel":""},"Root":{"Path":"/run/kata-containers/shared/containers/432a988cdb7fd4798e112d1a27a272d86e665dbfbdc4fc6937af127120b3a03e/rootfs","Readonly":false},"Hostname":"","Mounts":[{"destination":"/proc","source":"proc","type_":"proc","options":["nosuid","noexec","nodev"]},{"destination":"/dev","source":"tmpfs","type_":"tmpfs","options":["nosuid","strictatime","mode=755","size=65536k"]},{"destination":"/dev/pts","source":"devpts","type_":"devpts","options":["nosuid","noexec","newinstance","ptmxmode=0666","mode=0620","gid=5"]},{"destination":"/dev/mqueue","source":"mqueue","type_":"mqueue","options":["nosuid","noexec","nodev"]},{"destination":"/sys","source":"sysfs","type_":"sysfs","options":["nosuid","noexec","nodev","ro"]},{"destination":"/sys/fs/cgroup","source":"cgroup","type_":"cgroup","options":["nosuid","noexec","nodev","relatime","ro"]},{"destination":"/host/cache","source":"/run/kata-containers/shared/containers/c469f18bb1c58dab6be747450b77c3321e43ba5dd26d62d19a599b82bdb0ddc5/rootfs/local/host-empty-vol","type_":"local","options":["rbind","rprivate","rw"]},{"destination":"/etc/hosts","source":"/run/kata-containers/shared/containers/432a988cdb7fd4798e112d1a27a272d86e665dbfbdc4fc6937af127120b3a03e-01336f00e960a39f-hosts","type_":"bind","options":["rbind","rprivate","rw"]},{"destination":"/dev/termination-log","source":"/run/kata-containers/shared/containers/432a988cdb7fd4798e112d1a27a272d86e665dbfbdc4fc6937af127120b3a03e-90afc9ee3516c39f-termination-log","type_":"bind","options":["rbind","rprivate","rw"]},{"destination":"/etc/hostname","source":"/run/kata-containers/shared/containers/432a988cdb7fd4798e112d1a27a272d86e665dbfbdc4fc6937af127120b3a03e-094a24e380ead97f-hostname","type_":"bind","options":["rbind","rprivate","rw"]},{"destination":"/etc/resolv.conf","source":"/run/kata-containers/shared/containers/432a988cdb7fd4798e112d1a27a272d86e665dbfbdc4fc6937af127120b3a03e-530431b992f6d296-resolv.conf","type_":"bind","options":["rbind","rprivate","rw"]},{"destination":"/dev/shm","source":"/run/kata-containers/sandbox/shm","type_":"bind","options":["rbind"]},{"destination":"/var/run/secrets/kubernetes.io/serviceaccount","source":"/run/kata-containers/shared/containers/432a988cdb7fd4798e112d1a27a272d86e665dbfbdc4fc6937af127120b3a03e-cb7632a83c5a9dc3-serviceaccount","type_":"bind","options":["rbind","rprivate","ro"]}],"Hooks":null,"Annotations":{"io.kubernetes.cri.sandbox-namespace":"kata-containers-k8s-tests","io.kubernetes.cri.container-name":"nginxtest","io.kubernetes.cri.sandbox-name":"policy-rc-test-674df","io.katacontainers.pkg.oci.container_type":"pod_container","io.katacontainers.pkg.oci.bundle_path":"/run/containerd/io.containerd.runtime.v2.task/k8s.io/432a988cdb7fd4798e112d1a27a272d86e665dbfbdc4fc6937af127120b3a03e","io.kubernetes.cri.container-type":"container","io.kubernetes.cri.sandbox-id":"c469f18bb1c58dab6be747450b77c3321e43ba5dd26d62d19a599b82bdb0ddc5","io.kubernetes.cri.sandbox-uid":"eeb60852-0dcd-4387-b0f9-eebd63ddb7a8","io.kubernetes.cri.image-name":"quay.io/sjenning/nginx:1.15-alpine"},"Linux":{"UIDMappings":[],"GIDMappings":[],"Sysctl":{},"Resources":{"Devices":[],"Memory":{"Limit":0,"Reservation":0,"Swap":0,"Kernel":0,"KernelTCP":0,"Swappiness":0,"DisableOOMKiller":false},"CPU":{"Shares":2,"Quota":0,"Period":100000,"RealtimeRuntime":0,"RealtimePeriod":0,"Cpus":"","Mems":""},"Pids":null,"BlockIO":null,"HugepageLimits":[],"Network":null},"CgroupsPath":"kubepods-besteffort-podeeb60852_0dcd_4387_b0f9_eebd63ddb7a8.slice:cri-containerd:432a988cdb7fd4798e112d1a27a272d86e665dbfbdc4fc6937af127120b3a03e","Namespaces":[{"Type":"ipc","Path":""},{"Type":"uts","Path":""},{"Type":"mount","Path":""}],"Devices":[],"Seccomp":null,"RootfsPropagation":"","MaskedPaths":["/proc/asound","/proc/acpi","/proc/kcore","/proc/keys","/proc/latency_stats","/proc/timer_list","/proc/timer_stats","/proc/sched_debug","/proc/scsi","/sys/firmware"],"ReadonlyPaths":["/proc/bus","/proc/fs","/proc/irq","/proc/sys","/proc/sysrq-trigger"],"MountLabel":"","IntelRdt":null},"Solaris":null,"Windows":null},"sandbox_pidns":false,"shared_mounts":[],"stdin_port":0,"stdout_port":0,"stderr_port":0}], + +["ep":"OnlineCPUMemRequest",{"wait":false,"nb_cpus":0,"cpu_only":false}], + +["ep":"UpdateEphemeralMountsRequest",{"storages":[]}], + +["ep":"StartContainerRequest",{"container_id":"432a988cdb7fd4798e112d1a27a272d86e665dbfbdc4fc6937af127120b3a03e"}], + +["ep":"OnlineCPUMemRequest",{"wait":false,"nb_cpus":0,"cpu_only":false}], + +["ep":"UpdateEphemeralMountsRequest",{"storages":[]}], diff --git a/tests/kata/data/k8s-policy-rc/output.json b/tests/kata/data/k8s-policy-rc/output.json new file mode 100644 index 0000000..c0f5c42 --- /dev/null +++ b/tests/kata/data/k8s-policy-rc/output.json @@ -0,0 +1,19 @@ +[ + false, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true +] \ No newline at end of file diff --git a/tests/kata/data/k8s-policy-rc/policy.rego b/tests/kata/data/k8s-policy-rc/policy.rego new file mode 100644 index 0000000..757c88d --- /dev/null +++ b/tests/kata/data/k8s-policy-rc/policy.rego @@ -0,0 +1,1695 @@ +# Copyright (c) 2023 Microsoft Corporation +# +# SPDX-License-Identifier: Apache-2.0 +# +package agent_policy + +import future.keywords.in +import future.keywords.every + +# Default values, returned by OPA when rules cannot be evaluated to true. +default AddARPNeighborsRequest := false +default AddSwapRequest := false +default CloseStdinRequest := false +default CopyFileRequest := false +default CreateContainerRequest := false +default CreateSandboxRequest := true +default DestroySandboxRequest := true +default ExecProcessRequest := false +default GetOOMEventRequest := true +default GuestDetailsRequest := true +default ListInterfacesRequest := false +default ListRoutesRequest := false +default MemHotplugByProbeRequest := false +default OnlineCPUMemRequest := true +default PauseContainerRequest := false +default ReadStreamRequest := false +default RemoveContainerRequest := true +default RemoveStaleVirtiofsShareMountsRequest := true +default ReseedRandomDevRequest := false +default ResumeContainerRequest := false +default SetGuestDateTimeRequest := false +default SetPolicyRequest := false +default SignalProcessRequest := true +default StartContainerRequest := true +default StartTracingRequest := false +default StatsContainerRequest := true +default StopTracingRequest := false +default TtyWinResizeRequest := true +default UpdateContainerRequest := false +default UpdateEphemeralMountsRequest := true +default UpdateInterfaceRequest := true +default UpdateRoutesRequest := true +default WaitProcessRequest := true +default WriteStreamRequest := false + +# AllowRequestsFailingPolicy := true configures the Agent to *allow any +# requests causing a policy failure*. This is an unsecure configuration +# but is useful for allowing unsecure pods to start, then connect to +# them and inspect OPA logs for the root cause of a failure. +default AllowRequestsFailingPolicy := false + +CreateContainerRequest { + i_oci := input.OCI + i_storages := input.storages + + some p_container in policy_data.containers + print("======== CreateContainerRequest: trying next policy container") + + p_pidns := p_container.sandbox_pidns + i_pidns := input.sandbox_pidns + print("CreateContainerRequest: p_pidns =", p_pidns, "i_pidns =", i_pidns) + p_pidns == i_pidns + + p_oci := p_container.OCI + + print("CreateContainerRequest: p Version =", p_oci.Version, "i Version =", i_oci.Version) + p_oci.Version == i_oci.Version + + print("CreateContainerRequest: p Readonly =", p_oci.Root.Readonly, "i Readonly =", i_oci.Root.Readonly) + p_oci.Root.Readonly == i_oci.Root.Readonly + + allow_anno(p_oci, i_oci) + + p_storages := p_container.storages + allow_by_anno(p_oci, i_oci, p_storages, i_storages) + + allow_linux(p_oci, i_oci) + + print("CreateContainerRequest: true") +} + +# Reject unexpected annotations. +allow_anno(p_oci, i_oci) { + print("allow_anno 1: start") + + not i_oci.Annotations + + print("allow_anno 1: true") +} +allow_anno(p_oci, i_oci) { + print("allow_anno 2: p Annotations =", p_oci.Annotations) + print("allow_anno 2: i Annotations =", i_oci.Annotations) + + i_keys := object.keys(i_oci.Annotations) + print("allow_anno 2: i keys =", i_keys) + + every i_key in i_keys { + allow_anno_key(i_key, p_oci) + } + + print("allow_anno 2: true") +} + +allow_anno_key(i_key, p_oci) { + print("allow_anno_key 1: i key =", i_key) + + startswith(i_key, "io.kubernetes.cri.") + + print("allow_anno_key 1: true") +} +allow_anno_key(i_key, p_oci) { + print("allow_anno_key 2: i key =", i_key) + + some p_key, _ in p_oci.Annotations + p_key == i_key + + print("allow_anno_key 2: true") +} + +# Get the value of the "io.kubernetes.cri.sandbox-name" annotation and +# correlate it with other annotations and process fields. +allow_by_anno(p_oci, i_oci, p_storages, i_storages) { + print("allow_by_anno 1: start") + + s_name := "io.kubernetes.cri.sandbox-name" + + not p_oci.Annotations[s_name] + + i_s_name := i_oci.Annotations[s_name] + print("allow_by_anno 1: i_s_name =", i_s_name) + + allow_by_sandbox_name(p_oci, i_oci, p_storages, i_storages, i_s_name) + + print("allow_by_anno 1: true") +} +allow_by_anno(p_oci, i_oci, p_storages, i_storages) { + print("allow_by_anno 2: start") + + s_name := "io.kubernetes.cri.sandbox-name" + + p_s_name := p_oci.Annotations[s_name] + i_s_name := i_oci.Annotations[s_name] + print("allow_by_anno 2: i_s_name =", i_s_name, "p_s_name =", p_s_name) + + allow_sandbox_name(p_s_name, i_s_name) + allow_by_sandbox_name(p_oci, i_oci, p_storages, i_storages, i_s_name) + + print("allow_by_anno 2: true") +} + +allow_by_sandbox_name(p_oci, i_oci, p_storages, i_storages, s_name) { + print("allow_by_sandbox_name: start") + + s_namespace := "io.kubernetes.cri.sandbox-namespace" + + p_namespace := p_oci.Annotations[s_namespace] + i_namespace := i_oci.Annotations[s_namespace] + print("allow_by_sandbox_name: p_namespace =", p_namespace, "i_namespace =", i_namespace) + p_namespace == i_namespace + + allow_by_container_types(p_oci, i_oci, s_name, p_namespace) + allow_by_bundle_or_sandbox_id(p_oci, i_oci, p_storages, i_storages) + allow_process(p_oci, i_oci, s_name) + + print("allow_by_sandbox_name: true") +} + +allow_sandbox_name(p_s_name, i_s_name) { + print("allow_sandbox_name 1: start") + + p_s_name == i_s_name + + print("allow_sandbox_name 1: true") +} +allow_sandbox_name(p_s_name, i_s_name) { + print("allow_sandbox_name 2: start") + + # TODO: should generated names be handled differently? + contains(p_s_name, "$(generated-name)") + + print("allow_sandbox_name 2: true") +} + +# Check that the "io.kubernetes.cri.container-type" and +# "io.katacontainers.pkg.oci.container_type" annotations designate the +# expected type - either a "sandbox" or a "container". Then, validate +# other annotations based on the actual "sandbox" or "container" value +# from the input container. +allow_by_container_types(p_oci, i_oci, s_name, s_namespace) { + print("allow_by_container_types: checking io.kubernetes.cri.container-type") + + c_type := "io.kubernetes.cri.container-type" + + p_cri_type := p_oci.Annotations[c_type] + i_cri_type := i_oci.Annotations[c_type] + print("allow_by_container_types: p_cri_type =", p_cri_type, "i_cri_type =", i_cri_type) + p_cri_type == i_cri_type + + allow_by_container_type(i_cri_type, p_oci, i_oci, s_name, s_namespace) + + print("allow_by_container_types: true") +} + +allow_by_container_type(i_cri_type, p_oci, i_oci, s_name, s_namespace) { + print("allow_by_container_type 1: i_cri_type =", i_cri_type) + i_cri_type == "sandbox" + + i_kata_type := i_oci.Annotations["io.katacontainers.pkg.oci.container_type"] + print("allow_by_container_type 1: i_kata_type =", i_kata_type) + i_kata_type == "pod_sandbox" + + allow_sandbox_container_name(p_oci, i_oci) + allow_sandbox_net_namespace(p_oci, i_oci) + allow_sandbox_log_directory(p_oci, i_oci, s_name, s_namespace) + + print("allow_by_container_type 1: true") +} + +allow_by_container_type(i_cri_type, p_oci, i_oci, s_name, s_namespace) { + print("allow_by_container_type 2: i_cri_type =", i_cri_type) + i_cri_type == "container" + + i_kata_type := i_oci.Annotations["io.katacontainers.pkg.oci.container_type"] + print("allow_by_container_type 2: i_kata_type =", i_kata_type) + i_kata_type == "pod_container" + + allow_container_name(p_oci, i_oci) + allow_net_namespace(p_oci, i_oci) + allow_log_directory(p_oci, i_oci) + + print("allow_by_container_type 2: true") +} + +# "io.kubernetes.cri.container-name" annotation +allow_sandbox_container_name(p_oci, i_oci) { + print("allow_sandbox_container_name: start") + + container_annotation_missing(p_oci, i_oci, "io.kubernetes.cri.container-name") + + print("allow_sandbox_container_name: true") +} + +allow_container_name(p_oci, i_oci) { + print("allow_container_name: start") + + allow_container_annotation(p_oci, i_oci, "io.kubernetes.cri.container-name") + + print("allow_container_name: true") +} + +container_annotation_missing(p_oci, i_oci, key) { + print("container_annotation_missing:", key) + + not p_oci.Annotations[key] + not i_oci.Annotations[key] + + print("container_annotation_missing: true") +} + +allow_container_annotation(p_oci, i_oci, key) { + print("allow_container_annotation: key =", key) + + p_value := p_oci.Annotations[key] + i_value := i_oci.Annotations[key] + print("allow_container_annotation: p_value =", p_value, "i_value =", i_value) + + p_value == i_value + + print("allow_container_annotation: true") +} + +# "nerdctl/network-namespace" annotation +allow_sandbox_net_namespace(p_oci, i_oci) { + print("allow_sandbox_net_namespace: start") + + key := "nerdctl/network-namespace" + + p_namespace := p_oci.Annotations[key] + i_namespace := i_oci.Annotations[key] + print("allow_sandbox_net_namespace: p_namespace =", p_namespace, "i_namespace =", i_namespace) + + regex.match(p_namespace, i_namespace) + + print("allow_sandbox_net_namespace: true") +} + +allow_net_namespace(p_oci, i_oci) { + print("allow_net_namespace: start") + + key := "nerdctl/network-namespace" + + not p_oci.Annotations[key] + not i_oci.Annotations[key] + + print("allow_net_namespace: true") +} + +# "io.kubernetes.cri.sandbox-log-directory" annotation +allow_sandbox_log_directory(p_oci, i_oci, s_name, s_namespace) { + print("allow_sandbox_log_directory: start") + + key := "io.kubernetes.cri.sandbox-log-directory" + + p_dir := p_oci.Annotations[key] + regex1 := replace(p_dir, "$(sandbox-name)", s_name) + regex2 := replace(regex1, "$(sandbox-namespace)", s_namespace) + print("allow_sandbox_log_directory: regex2 =", regex2) + + i_dir := i_oci.Annotations[key] + print("allow_sandbox_log_directory: i_dir =", i_dir) + + regex.match(regex2, i_dir) + + print("allow_sandbox_log_directory: true") +} + +allow_log_directory(p_oci, i_oci) { + print("allow_log_directory: start") + + key := "io.kubernetes.cri.sandbox-log-directory" + + not p_oci.Annotations[key] + not i_oci.Annotations[key] + + print("allow_log_directory: true") +} + +allow_linux(p_oci, i_oci) { + 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 + + allow_masked_paths(p_oci, i_oci) + allow_readonly_paths(p_oci, i_oci) + + print("allow_linux: true") +} + +allow_masked_paths(p_oci, i_oci) { + p_paths := p_oci.Linux.MaskedPaths + print("allow_masked_paths 1: p_paths =", p_paths) + + i_paths := i_oci.Linux.MaskedPaths + print("allow_masked_paths 1: i_paths =", i_paths) + + allow_masked_paths_array(p_paths, i_paths) + + print("allow_masked_paths 1: true") +} +allow_masked_paths(p_oci, i_oci) { + print("allow_masked_paths 2: start") + + not p_oci.Linux.MaskedPaths + not i_oci.Linux.MaskedPaths + + print("allow_masked_paths 2: true") +} + +# All the policy masked paths must be masked in the input data too. +# Input is allowed to have more masked paths than the policy. +allow_masked_paths_array(p_array, i_array) { + every p_elem in p_array { + allow_masked_path(p_elem, i_array) + } +} + +allow_masked_path(p_elem, i_array) { + print("allow_masked_path: p_elem =", p_elem) + + some i_elem in i_array + p_elem == i_elem + + print("allow_masked_path: true") +} + +allow_readonly_paths(p_oci, i_oci) { + p_paths := p_oci.Linux.ReadonlyPaths + print("allow_readonly_paths 1: p_paths =", p_paths) + + i_paths := i_oci.Linux.ReadonlyPaths + print("allow_readonly_paths 1: i_paths =", i_paths) + + allow_readonly_paths_array(p_paths, i_paths, i_oci.Linux.MaskedPaths) + + print("allow_readonly_paths 1: true") +} +allow_readonly_paths(p_oci, i_oci) { + print("allow_readonly_paths 2: start") + + not p_oci.Linux.ReadonlyPaths + not i_oci.Linux.ReadonlyPaths + + print("allow_readonly_paths 2: true") +} + +# All the policy readonly paths must be either: +# - Present in the input readonly paths, or +# - Present in the input masked paths. +# Input is allowed to have more readonly paths than the policy. +allow_readonly_paths_array(p_array, i_array, masked_paths) { + every p_elem in p_array { + allow_readonly_path(p_elem, i_array, masked_paths) + } +} + +allow_readonly_path(p_elem, i_array, masked_paths) { + print("allow_readonly_path 1: p_elem =", p_elem) + + some i_elem in i_array + p_elem == i_elem + + print("allow_readonly_path 1: true") +} +allow_readonly_path(p_elem, i_array, masked_paths) { + print("allow_readonly_path 2: p_elem =", p_elem) + + some i_masked in masked_paths + p_elem == i_masked + + print("allow_readonly_path 2: true") +} + +# Check the consistency of the input "io.katacontainers.pkg.oci.bundle_path" +# and io.kubernetes.cri.sandbox-id" values with other fields. +allow_by_bundle_or_sandbox_id(p_oci, i_oci, p_storages, i_storages) { + print("allow_by_bundle_or_sandbox_id: start") + + bundle_path := i_oci.Annotations["io.katacontainers.pkg.oci.bundle_path"] + bundle_id := replace(bundle_path, "/run/containerd/io.containerd.runtime.v2.task/k8s.io/", "") + + key := "io.kubernetes.cri.sandbox-id" + + p_regex := p_oci.Annotations[key] + sandbox_id := i_oci.Annotations[key] + + print("allow_by_bundle_or_sandbox_id: sandbox_id =", sandbox_id, "regex =", p_regex) + regex.match(p_regex, sandbox_id) + + allow_root_path(p_oci, i_oci, bundle_id) + + every i_mount in input.OCI.Mounts { + allow_mount(p_oci, i_mount, bundle_id, sandbox_id) + } + + # TODO: enable allow_storages() after fixing https://github.com/kata-containers/kata-containers/issues/8833 + # allow_storages(p_storages, i_storages, bundle_id, sandbox_id) + + print("allow_by_bundle_or_sandbox_id: true") +} + +allow_process(p_oci, i_oci, s_name) { + p_process := p_oci.Process + i_process := i_oci.Process + + print("allow_process: i terminal =", i_process.Terminal, "p terminal =", p_process.Terminal) + p_process.Terminal == i_process.Terminal + + print("allow_process: i cwd =", i_process.Cwd, "i cwd =", p_process.Cwd) + p_process.Cwd == i_process.Cwd + + print("allow_process: i noNewPrivileges =", i_process.NoNewPrivileges, "p noNewPrivileges =", p_process.NoNewPrivileges) + p_process.NoNewPrivileges == i_process.NoNewPrivileges + + allow_caps(p_process.Capabilities, i_process.Capabilities) + allow_user(p_process, i_process) + allow_args(p_process, i_process, s_name) + allow_env(p_process, i_process, s_name) + + print("allow_process: true") +} + +allow_user(p_process, i_process) { + p_user := p_process.User + i_user := i_process.User + + # TODO: track down the reason for mcr.microsoft.com/oss/bitnami/redis:6.0.8 being + # executed with uid = 0 despite having "User": "1001" in its container image + # config. + #print("allow_user: input uid =", i_user.UID, "policy uid =", p_user.UID) + #p_user.UID == i_user.UID + + # TODO: track down the reason for registry.k8s.io/pause:3.9 being + # executed with gid = 0 despite having "65535:65535" in its container image + # config. + #print("allow_user: input gid =", i_user.GID, "policy gid =", p_user.GID) + #p_user.GID == i_user.GID + + # TODO: compare the additionalGids field too after computing its value + # based on /etc/passwd and /etc/group from the container image. +} + +allow_args(p_process, i_process, s_name) { + print("allow_args 1: no args") + + not p_process.Args + not i_process.Args + + print("allow_args 1: true") +} +allow_args(p_process, i_process, s_name) { + print("allow_args 2: policy args =", p_process.Args) + print("allow_args 2: input args =", i_process.Args) + + count(p_process.Args) == count(i_process.Args) + + every i, i_arg in i_process.Args { + allow_arg(i, i_arg, p_process, s_name) + } + + print("allow_args 2: true") +} +allow_arg(i, i_arg, p_process, s_name) { + p_arg := p_process.Args[i] + print("allow_arg 1: i =", i, "i_arg =", i_arg, "p_arg =", p_arg) + + p_arg2 := replace(p_arg, "$$", "$") + p_arg2 == i_arg + + print("allow_arg 1: true") +} +allow_arg(i, i_arg, p_process, s_name) { + p_arg := p_process.Args[i] + print("allow_arg 2: i =", i, "i_arg =", i_arg, "p_arg =", p_arg) + + # TODO: can $(node-name) be handled better? + contains(p_arg, "$(node-name)") + + print("allow_arg 2: true") +} +allow_arg(i, i_arg, p_process, s_name) { + p_arg := p_process.Args[i] + print("allow_arg 3: i =", i, "i_arg =", i_arg, "p_arg =", p_arg) + + p_arg2 := replace(p_arg, "$$", "$") + p_arg3 := replace(p_arg2, "$(sandbox-name)", s_name) + print("allow_arg 3: p_arg3 =", p_arg3) + p_arg3 == i_arg + + print("allow_arg 3: true") +} + +# OCI process.Env field +allow_env(p_process, i_process, s_name) { + print("allow_env: p env =", p_process.Env) + print("allow_env: i env =", i_process.Env) + + every i_var in i_process.Env { + print("allow_env: i_var =", i_var) + allow_var(p_process, i_process, i_var, s_name) + } + + print("allow_env: true") +} + +# Allow input env variables that are present in the policy data too. +allow_var(p_process, i_process, i_var, s_name) { + some p_var in p_process.Env + p_var == i_var + print("allow_var 1: true") +} + +# Match input with one of the policy variables, after substituting $(sandbox-name). +allow_var(p_process, i_process, i_var, s_name) { + some p_var in p_process.Env + p_var2 := replace(p_var, "$(sandbox-name)", s_name) + + print("allow_var 2: p_var2 =", p_var2) + p_var2 == i_var + + print("allow_var 2: true") +} + +# Allow input env variables that match with a request_defaults regex. +allow_var(p_process, i_process, i_var, s_name) { + some p_regex1 in policy_data.request_defaults.CreateContainerRequest.allow_env_regex + p_regex2 := replace(p_regex1, "$(ipv4_a)", policy_data.common.ipv4_a) + p_regex3 := replace(p_regex2, "$(ip_p)", policy_data.common.ip_p) + p_regex4 := replace(p_regex3, "$(svc_name)", policy_data.common.svc_name) + p_regex5 := replace(p_regex4, "$(dns_label)", policy_data.common.dns_label) + + print("allow_var 3: p_regex5 =", p_regex5) + regex.match(p_regex5, i_var) + + print("allow_var 3: true") +} + +# Allow fieldRef "fieldPath: status.podIP" values. +allow_var(p_process, i_process, i_var, s_name) { + name_value := split(i_var, "=") + count(name_value) == 2 + is_ip(name_value[1]) + + some p_var in p_process.Env + allow_pod_ip_var(name_value[0], p_var) + + print("allow_var 4: true") +} + +# Allow common fieldRef variables. +allow_var(p_process, i_process, i_var, s_name) { + name_value := split(i_var, "=") + count(name_value) == 2 + + some p_var in p_process.Env + p_name_value := split(p_var, "=") + count(p_name_value) == 2 + + p_name_value[0] == name_value[0] + + # TODO: should these be handled in a different way? + always_allowed := ["$(host-name)", "$(node-name)", "$(pod-uid)"] + some allowed in always_allowed + contains(p_name_value[1], allowed) + + print("allow_var 5: true") +} + +# Allow fieldRef "fieldPath: status.hostIP" values. +allow_var(p_process, i_process, i_var, s_name) { + name_value := split(i_var, "=") + count(name_value) == 2 + is_ip(name_value[1]) + + some p_var in p_process.Env + allow_host_ip_var(name_value[0], p_var) + + print("allow_var 6: true") +} + +# Allow resourceFieldRef values (e.g., "limits.cpu"). +allow_var(p_process, i_process, i_var, s_name) { + name_value := split(i_var, "=") + count(name_value) == 2 + + some p_var in p_process.Env + p_name_value := split(p_var, "=") + count(p_name_value) == 2 + + p_name_value[0] == name_value[0] + + # TODO: should these be handled in a different way? + always_allowed = ["$(resource-field)", "$(todo-annotation)"] + some allowed in always_allowed + contains(p_name_value[1], allowed) + + print("allow_var 7: true") +} + +allow_pod_ip_var(var_name, p_var) { + print("allow_pod_ip_var: var_name =", var_name, "p_var =", p_var) + + p_name_value := split(p_var, "=") + count(p_name_value) == 2 + + p_name_value[0] == var_name + p_name_value[1] == "$(pod-ip)" + + print("allow_pod_ip_var: true") +} + +allow_host_ip_var(var_name, p_var) { + print("allow_host_ip_var: var_name =", var_name, "p_var =", p_var) + + p_name_value := split(p_var, "=") + count(p_name_value) == 2 + + p_name_value[0] == var_name + p_name_value[1] == "$(host-ip)" + + print("allow_host_ip_var: true") +} + +is_ip(value) { + bytes = split(value, ".") + count(bytes) == 4 + + is_ip_first_byte(bytes[0]) + is_ip_other_byte(bytes[1]) + is_ip_other_byte(bytes[2]) + is_ip_other_byte(bytes[3]) +} +is_ip_first_byte(component) { + number = to_number(component) + number >= 1 + number <= 255 +} +is_ip_other_byte(component) { + number = to_number(component) + number >= 0 + number <= 255 +} + +# OCI root.Path +allow_root_path(p_oci, i_oci, bundle_id) { + i_path := i_oci.Root.Path + p_path1 := p_oci.Root.Path + print("allow_root_path: i_path =", i_path, "p_path1 =", p_path1) + + p_path2 := replace(p_path1, "$(cpath)", policy_data.common.cpath) + print("allow_root_path: p_path2 =", p_path2) + + p_path3 := replace(p_path2, "$(bundle-id)", bundle_id) + print("allow_root_path: p_path3 =", p_path3) + + p_path3 == i_path + + print("allow_root_path: true") +} + +# device mounts +allow_mount(p_oci, i_mount, bundle_id, sandbox_id) { + print("allow_mount: i_mount =", i_mount) + + some p_mount in p_oci.Mounts + print("allow_mount: p_mount =", p_mount) + check_mount(p_mount, i_mount, bundle_id, sandbox_id) + + # TODO: are there any other required policy checks for mounts - e.g., + # multiple mounts with same source or destination? + + print("allow_mount: true") +} + +check_mount(p_mount, i_mount, bundle_id, sandbox_id) { + p_mount == i_mount + print("check_mount 1: true") +} +check_mount(p_mount, i_mount, bundle_id, sandbox_id) { + p_mount.destination == i_mount.destination + p_mount.type_ == i_mount.type_ + p_mount.options == i_mount.options + + mount_source_allows(p_mount, i_mount, bundle_id, sandbox_id) + + print("check_mount 2: true") +} + +mount_source_allows(p_mount, i_mount, bundle_id, sandbox_id) { + regex1 := p_mount.source + regex2 := replace(regex1, "$(sfprefix)", policy_data.common.sfprefix) + regex3 := replace(regex2, "$(cpath)", policy_data.common.cpath) + regex4 := replace(regex3, "$(bundle-id)", bundle_id) + + print("mount_source_allows 1: regex4 =", regex4) + regex.match(regex4, i_mount.source) + + print("mount_source_allows 1: true") +} +mount_source_allows(p_mount, i_mount, bundle_id, sandbox_id) { + regex1 := p_mount.source + regex2 := replace(regex1, "$(sfprefix)", policy_data.common.sfprefix) + regex3 := replace(regex2, "$(cpath)", policy_data.common.cpath) + regex4 := replace(regex3, "$(sandbox-id)", sandbox_id) + + print("mount_source_allows 2: regex4 =", regex4) + regex.match(regex4, i_mount.source) + + print("mount_source_allows 2: true") +} + +###################################################################### +# Storages + +allow_storages(p_storages, i_storages, bundle_id, sandbox_id) { + p_count := count(p_storages) + i_count := count(i_storages) + print("allow_storages: p_count =", p_count, "i_count =", i_count) + + p_count == i_count + + # Get the container image layer IDs and verity root hashes, from the "overlayfs" storage. + some overlay_storage in p_storages + overlay_storage.driver == "overlayfs" + print("allow_storages: overlay_storage =", overlay_storage) + count(overlay_storage.options) == 2 + + layer_ids := split(overlay_storage.options[0], ":") + print("allow_storages: layer_ids =", layer_ids) + + root_hashes := split(overlay_storage.options[1], ":") + print("allow_storages: root_hashes =", root_hashes) + + every i_storage in i_storages { + allow_storage(p_storages, i_storage, bundle_id, sandbox_id, layer_ids, root_hashes) + } + + print("allow_storages: true") +} + +allow_storage(p_storages, i_storage, bundle_id, sandbox_id, layer_ids, root_hashes) { + some p_storage in p_storages + + print("allow_storage: p_storage =", p_storage) + print("allow_storage: i_storage =", i_storage) + + p_storage.driver == i_storage.driver + p_storage.driver_options == i_storage.driver_options + p_storage.fs_group == i_storage.fs_group + + allow_storage_options(p_storage, i_storage, layer_ids, root_hashes) + allow_mount_point(p_storage, i_storage, bundle_id, sandbox_id, layer_ids) + + # TODO: validate the source field too. + + print("allow_storage: true") +} + +allow_storage_options(p_storage, i_storage, layer_ids, root_hashes) { + print("allow_storage_options 1: start") + + p_storage.driver != "blk" + p_storage.driver != "overlayfs" + p_storage.options == i_storage.options + + print("allow_storage_options 1: true") +} +allow_storage_options(p_storage, i_storage, layer_ids, root_hashes) { + print("allow_storage_options 2: start") + + p_storage.driver == "overlayfs" + count(p_storage.options) == 2 + + policy_ids := split(p_storage.options[0], ":") + print("allow_storage_options 2: policy_ids =", policy_ids) + policy_ids == layer_ids + + policy_hashes := split(p_storage.options[1], ":") + print("allow_storage_options 2: policy_hashes =", policy_hashes) + + p_count := count(policy_ids) + print("allow_storage_options 2: p_count =", p_count) + p_count >= 1 + p_count == count(policy_hashes) + + i_count := count(i_storage.options) + print("allow_storage_options 2: i_count =", i_count) + i_count == p_count + 3 + + print("allow_storage_options 2: i_storage.options[0] =", i_storage.options[0]) + i_storage.options[0] == "io.katacontainers.fs-opt.layer-src-prefix=/var/lib/containerd/io.containerd.snapshotter.v1.tardev/layers" + + print("allow_storage_options 2: i_storage.options[i_count - 2] =", i_storage.options[i_count - 2]) + i_storage.options[i_count - 2] == "io.katacontainers.fs-opt.overlay-rw" + + lowerdir := concat("=", ["lowerdir", p_storage.options[0]]) + print("allow_storage_options 2: lowerdir =", lowerdir) + + i_storage.options[i_count - 1] == lowerdir + print("allow_storage_options 2: i_storage.options[i_count - 1] =", i_storage.options[i_count - 1]) + + every i, policy_id in policy_ids { + allow_overlay_layer(policy_id, policy_hashes[i], i_storage.options[i + 1]) + } + + print("allow_storage_options 2: true") +} +allow_storage_options(p_storage, i_storage, layer_ids, root_hashes) { + print("allow_storage_options 3: start") + + p_storage.driver == "blk" + count(p_storage.options) == 1 + + startswith(p_storage.options[0], "$(hash") + hash_suffix := trim_left(p_storage.options[0], "$(hash") + + endswith(hash_suffix, ")") + hash_index := trim_right(hash_suffix, ")") + i := to_number(hash_index) + print("allow_storage_options 3: i =", i) + + hash_option := concat("=", ["io.katacontainers.fs-opt.root-hash", root_hashes[i]]) + print("allow_storage_options 3: hash_option =", hash_option) + + count(i_storage.options) == 4 + i_storage.options[0] == "ro" + i_storage.options[1] == "io.katacontainers.fs-opt.block_device=file" + i_storage.options[2] == "io.katacontainers.fs-opt.is-layer" + i_storage.options[3] == hash_option + + print("allow_storage_options 3: true") +} + +allow_overlay_layer(policy_id, policy_hash, i_option) { + print("allow_overlay_layer: policy_id =", policy_id, "policy_hash =", policy_hash) + print("allow_overlay_layer: i_option =", i_option) + + startswith(i_option, "io.katacontainers.fs-opt.layer=") + i_value := replace(i_option, "io.katacontainers.fs-opt.layer=", "") + i_value_decoded := base64.decode(i_value) + print("allow_overlay_layer: i_value_decoded =", i_value_decoded) + + policy_suffix := concat("=", ["tar,ro,io.katacontainers.fs-opt.block_device=file,io.katacontainers.fs-opt.is-layer,io.katacontainers.fs-opt.root-hash", policy_hash]) + p_value := concat(",", [policy_id, policy_suffix]) + print("allow_overlay_layer: p_value =", p_value) + + p_value == i_value_decoded + + print("allow_overlay_layer: true") +} + +allow_mount_point(p_storage, i_storage, bundle_id, sandbox_id, layer_ids) { + p_storage.fstype == "tar" + + startswith(p_storage.mount_point, "$(layer") + mount_suffix := trim_left(p_storage.mount_point, "$(layer") + + endswith(mount_suffix, ")") + layer_index := trim_right(mount_suffix, ")") + i := to_number(layer_index) + print("allow_mount_point 1: i =", i) + + layer_id := layer_ids[i] + print("allow_mount_point 1: layer_id =", layer_id) + + p_mount := concat("/", ["/run/kata-containers/sandbox/layers", layer_id]) + print("allow_mount_point 1: p_mount =", p_mount) + + p_mount == i_storage.mount_point + + print("allow_mount_point 1: true") +} +allow_mount_point(p_storage, i_storage, bundle_id, sandbox_id, layer_ids) { + p_storage.fstype == "fuse3.kata-overlay" + + mount1 := replace(p_storage.mount_point, "$(cpath)", policy_data.common.cpath) + mount2 := replace(mount1, "$(bundle-id)", bundle_id) + print("allow_mount_point 2: mount2 =", mount2) + + mount2 == i_storage.mount_point + + print("allow_mount_point 2: true") +} +allow_mount_point(p_storage, i_storage, bundle_id, sandbox_id, layer_ids) { + p_storage.fstype == "local" + + mount1 := p_storage.mount_point + print("allow_mount_point 3: mount1 =", mount1) + + mount2 := replace(mount1, "$(cpath)", policy_data.common.cpath) + print("allow_mount_point 3: mount2 =", mount2) + + mount3 := replace(mount2, "$(sandbox-id)", sandbox_id) + print("allow_mount_point 3: mount3 =", mount3) + + regex.match(mount3, i_storage.mount_point) + + print("allow_mount_point 3: true") +} +allow_mount_point(p_storage, i_storage, bundle_id, sandbox_id, layer_ids) { + p_storage.fstype == "bind" + + mount1 := p_storage.mount_point + print("allow_mount_point 4: mount1 =", mount1) + + mount2 := replace(mount1, "$(cpath)", policy_data.common.cpath) + print("allow_mount_point 4: mount2 =", mount2) + + mount3 := replace(mount2, "$(bundle-id)", bundle_id) + print("allow_mount_point 4: mount3 =", mount3) + + regex.match(mount3, i_storage.mount_point) + + print("allow_mount_point 4: true") +} +allow_mount_point(p_storage, i_storage, bundle_id, sandbox_id, layer_ids) { + p_storage.fstype == "tmpfs" + + mount1 := p_storage.mount_point + print("allow_mount_point 5: mount1 =", mount1) + + regex.match(mount1, i_storage.mount_point) + + print("allow_mount_point 5: true") +} + +# process.Capabilities +allow_caps(p_caps, i_caps) { + print("allow_caps: policy Ambient =", p_caps.Ambient) + print("allow_caps: input Ambient =", i_caps.Ambient) + match_caps(p_caps.Ambient, i_caps.Ambient) + + print("allow_caps: policy Bounding =", p_caps.Bounding) + print("allow_caps: input Bounding =", i_caps.Bounding) + match_caps(p_caps.Bounding, i_caps.Bounding) + + print("allow_caps: policy Effective =", p_caps.Effective) + print("allow_caps: input Effective =", i_caps.Effective) + match_caps(p_caps.Effective, i_caps.Effective) + + print("allow_caps: policy Inheritable =", p_caps.Inheritable) + print("allow_caps: input Inheritable =", i_caps.Inheritable) + match_caps(p_caps.Inheritable, i_caps.Inheritable) + + print("allow_caps: policy Permitted =", p_caps.Permitted) + print("allow_caps: input Permitted =", i_caps.Permitted) + match_caps(p_caps.Permitted, i_caps.Permitted) +} + +match_caps(p_caps, i_caps) { + print("match_caps 1: start") + + p_caps == i_caps + + print("match_caps 1: true") +} +match_caps(p_caps, i_caps) { + print("match_caps 2: start") + + count(p_caps) == 1 + p_caps[0] == "$(default_caps)" + + print("match_caps 2: default_caps =", policy_data.common.default_caps) + policy_data.common.default_caps == i_caps + + print("match_caps 2: true") +} +match_caps(p_caps, i_caps) { + print("match_caps 3: start") + + count(p_caps) == 1 + p_caps[0] == "$(privileged_caps)" + + print("match_caps 3: privileged_caps =", policy_data.common.privileged_caps) + policy_data.common.privileged_caps == i_caps + + print("match_caps 3: true") +} + +###################################################################### +CopyFileRequest { + print("CopyFileRequest: input.path =", input.path) + + some regex1 in policy_data.request_defaults.CopyFileRequest + regex2 := replace(regex1, "$(cpath)", policy_data.common.cpath) + regex.match(regex2, input.path) + + print("CopyFileRequest: true") +} + +CreateSandboxRequest { + i_pidns := input.sandbox_pidns + print("CreateSandboxRequest: i_pidns =", i_pidns) + i_pidns == false +} + +ExecProcessRequest { + print("ExecProcessRequest 1: input =", input) + + i_command = concat(" ", input.process.Args) + print("ExecProcessRequest 1: i_command =", i_command) + + some p_command in policy_data.request_defaults.ExecProcessRequest.commands + print("ExecProcessRequest 1: p_command =", p_command) + p_command == i_command + + print("ExecProcessRequest 1: true") +} +ExecProcessRequest { + print("ExecProcessRequest 2: input =", input) + + # TODO: match input container ID with its corresponding container.exec_commands. + i_command = concat(" ", input.process.Args) + print("ExecProcessRequest 3: i_command =", i_command) + + some container in policy_data.containers + some p_command in container.exec_commands + print("ExecProcessRequest 2: p_command =", p_command) + + # TODO: should other input data fields be validated as well? + p_command == i_command + + print("ExecProcessRequest 2: true") +} +ExecProcessRequest { + print("ExecProcessRequest 3: input =", input) + + i_command = concat(" ", input.process.Args) + print("ExecProcessRequest 3: i_command =", i_command) + + some p_regex in policy_data.request_defaults.ExecProcessRequest.regex + print("ExecProcessRequest 3: p_regex =", p_regex) + + regex.match(p_regex, i_command) + + print("ExecProcessRequest 3: true") +} + +CloseStdinRequest { + policy_data.request_defaults.CloseStdinRequest == true +} + +ReadStreamRequest { + policy_data.request_defaults.ReadStreamRequest == true +} + +WriteStreamRequest { + policy_data.request_defaults.WriteStreamRequest == true +} + +policy_data := { + "containers": [ + { + "OCI": { + "Version": "1.1.0", + "Process": { + "Terminal": false, + "User": { + "UID": 65535, + "GID": 65535, + "AdditionalGids": [], + "Username": "" + }, + "Args": [ + "/pause" + ], + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "Cwd": "/", + "Capabilities": { + "Ambient": [], + "Bounding": [ + "$(default_caps)" + ], + "Effective": [ + "$(default_caps)" + ], + "Inheritable": [], + "Permitted": [ + "$(default_caps)" + ] + }, + "NoNewPrivileges": true + }, + "Root": { + "Path": "$(cpath)/$(bundle-id)/rootfs", + "Readonly": true + }, + "Mounts": [ + { + "destination": "/proc", + "source": "proc", + "type_": "proc", + "options": [ + "nosuid", + "noexec", + "nodev" + ] + }, + { + "destination": "/dev", + "source": "tmpfs", + "type_": "tmpfs", + "options": [ + "nosuid", + "strictatime", + "mode=755", + "size=65536k" + ] + }, + { + "destination": "/dev/pts", + "source": "devpts", + "type_": "devpts", + "options": [ + "nosuid", + "noexec", + "newinstance", + "ptmxmode=0666", + "mode=0620", + "gid=5" + ] + }, + { + "destination": "/dev/shm", + "source": "/run/kata-containers/sandbox/shm", + "type_": "bind", + "options": [ + "rbind" + ] + }, + { + "destination": "/dev/mqueue", + "source": "mqueue", + "type_": "mqueue", + "options": [ + "nosuid", + "noexec", + "nodev" + ] + }, + { + "destination": "/sys", + "source": "sysfs", + "type_": "sysfs", + "options": [ + "nosuid", + "noexec", + "nodev", + "ro" + ] + }, + { + "destination": "/etc/resolv.conf", + "source": "$(sfprefix)resolv.conf$", + "type_": "bind", + "options": [ + "rbind", + "ro", + "nosuid", + "nodev", + "noexec" + ] + } + ], + "Annotations": { + "io.katacontainers.pkg.oci.bundle_path": "/run/containerd/io.containerd.runtime.v2.task/k8s.io/$(bundle-id)", + "io.katacontainers.pkg.oci.container_type": "pod_sandbox", + "io.kubernetes.cri.container-type": "sandbox", + "io.kubernetes.cri.sandbox-id": "^[a-z0-9]{64}$", + "io.kubernetes.cri.sandbox-log-directory": "^/var/log/pods/$(sandbox-namespace)_$(sandbox-name)_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", + "io.kubernetes.cri.sandbox-namespace": "kata-containers-k8s-tests", + "nerdctl/network-namespace": "^/var/run/netns/cni-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" + }, + "Linux": { + "Namespaces": [ + { + "Type": "ipc", + "Path": "" + }, + { + "Type": "uts", + "Path": "" + }, + { + "Type": "mount", + "Path": "" + } + ], + "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" + ] + } + }, + "storages": [ + { + "driver": "blk", + "driver_options": [], + "source": "", + "fstype": "tar", + "options": [ + "$(hash0)" + ], + "mount_point": "$(layer0)", + "fs_group": null + }, + { + "driver": "overlayfs", + "driver_options": [], + "source": "", + "fstype": "fuse3.kata-overlay", + "options": [ + "5a5aad80055ff20012a50dc25f8df7a29924474324d65f7d5306ee8ee27ff71d", + "817250f1a3e336da76f5bd3fa784e1b26d959b9c131876815ba2604048b70c18" + ], + "mount_point": "$(cpath)/$(bundle-id)/rootfs", + "fs_group": null + } + ], + "sandbox_pidns": false, + "exec_commands": [] + }, + { + "OCI": { + "Version": "1.1.0", + "Process": { + "Terminal": false, + "User": { + "UID": 0, + "GID": 0, + "AdditionalGids": [], + "Username": "" + }, + "Args": [ + "nginx", + "-g", + "daemon off;" + ], + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "NGINX_VERSION=1.15.12", + "HOSTNAME=$(host-name)" + ], + "Cwd": "/", + "Capabilities": { + "Ambient": [], + "Bounding": [ + "$(default_caps)" + ], + "Effective": [ + "$(default_caps)" + ], + "Inheritable": [], + "Permitted": [ + "$(default_caps)" + ] + }, + "NoNewPrivileges": false + }, + "Root": { + "Path": "$(cpath)/$(bundle-id)/rootfs", + "Readonly": false + }, + "Mounts": [ + { + "destination": "/proc", + "source": "proc", + "type_": "proc", + "options": [ + "nosuid", + "noexec", + "nodev" + ] + }, + { + "destination": "/dev", + "source": "tmpfs", + "type_": "tmpfs", + "options": [ + "nosuid", + "strictatime", + "mode=755", + "size=65536k" + ] + }, + { + "destination": "/dev/pts", + "source": "devpts", + "type_": "devpts", + "options": [ + "nosuid", + "noexec", + "newinstance", + "ptmxmode=0666", + "mode=0620", + "gid=5" + ] + }, + { + "destination": "/dev/shm", + "source": "/run/kata-containers/sandbox/shm", + "type_": "bind", + "options": [ + "rbind" + ] + }, + { + "destination": "/dev/mqueue", + "source": "mqueue", + "type_": "mqueue", + "options": [ + "nosuid", + "noexec", + "nodev" + ] + }, + { + "destination": "/sys", + "source": "sysfs", + "type_": "sysfs", + "options": [ + "nosuid", + "noexec", + "nodev", + "ro" + ] + }, + { + "destination": "/sys/fs/cgroup", + "source": "cgroup", + "type_": "cgroup", + "options": [ + "nosuid", + "noexec", + "nodev", + "relatime", + "ro" + ] + }, + { + "destination": "/etc/hosts", + "source": "$(sfprefix)hosts$", + "type_": "bind", + "options": [ + "rbind", + "rprivate", + "rw" + ] + }, + { + "destination": "/dev/termination-log", + "source": "$(sfprefix)termination-log$", + "type_": "bind", + "options": [ + "rbind", + "rprivate", + "rw" + ] + }, + { + "destination": "/etc/hostname", + "source": "$(sfprefix)hostname$", + "type_": "bind", + "options": [ + "rbind", + "rprivate", + "rw" + ] + }, + { + "destination": "/etc/resolv.conf", + "source": "$(sfprefix)resolv.conf$", + "type_": "bind", + "options": [ + "rbind", + "rprivate", + "rw" + ] + }, + { + "destination": "/var/run/secrets/kubernetes.io/serviceaccount", + "source": "$(sfprefix)serviceaccount$", + "type_": "bind", + "options": [ + "rbind", + "rprivate", + "ro" + ] + }, + { + "destination": "/var/run/secrets/azure/tokens", + "source": "$(sfprefix)tokens$", + "type_": "bind", + "options": [ + "rbind", + "rprivate", + "ro" + ] + }, + { + "destination": "/host/cache", + "source": "^$(cpath)/$(sandbox-id)/rootfs/local/host-empty-vol$", + "type_": "local", + "options": [ + "rbind", + "rprivate", + "rw" + ] + } + ], + "Annotations": { + "io.katacontainers.pkg.oci.bundle_path": "/run/containerd/io.containerd.runtime.v2.task/k8s.io/$(bundle-id)", + "io.katacontainers.pkg.oci.container_type": "pod_container", + "io.kubernetes.cri.container-name": "nginxtest", + "io.kubernetes.cri.container-type": "container", + "io.kubernetes.cri.image-name": "quay.io/sjenning/nginx:1.15-alpine", + "io.kubernetes.cri.sandbox-id": "^[a-z0-9]{64}$", + "io.kubernetes.cri.sandbox-namespace": "kata-containers-k8s-tests" + }, + "Linux": { + "Namespaces": [ + { + "Type": "ipc", + "Path": "" + }, + { + "Type": "uts", + "Path": "" + }, + { + "Type": "mount", + "Path": "" + } + ], + "MaskedPaths": [ + "/proc/acpi", + "/proc/kcore", + "/proc/keys", + "/proc/latency_stats", + "/proc/timer_list", + "/proc/timer_stats", + "/proc/sched_debug", + "/proc/scsi", + "/sys/firmware" + ], + "ReadonlyPaths": [ + "/proc/asound", + "/proc/bus", + "/proc/fs", + "/proc/irq", + "/proc/sys", + "/proc/sysrq-trigger" + ] + } + }, + "storages": [ + { + "driver": "blk", + "driver_options": [], + "source": "", + "fstype": "tar", + "options": [ + "$(hash0)" + ], + "mount_point": "$(layer0)", + "fs_group": null + }, + { + "driver": "blk", + "driver_options": [], + "source": "", + "fstype": "tar", + "options": [ + "$(hash1)" + ], + "mount_point": "$(layer1)", + "fs_group": null + }, + { + "driver": "blk", + "driver_options": [], + "source": "", + "fstype": "tar", + "options": [ + "$(hash2)" + ], + "mount_point": "$(layer2)", + "fs_group": null + }, + { + "driver": "blk", + "driver_options": [], + "source": "", + "fstype": "tar", + "options": [ + "$(hash3)" + ], + "mount_point": "$(layer3)", + "fs_group": null + }, + { + "driver": "overlayfs", + "driver_options": [], + "source": "", + "fstype": "fuse3.kata-overlay", + "options": [ + "524c870e3d999ee05dd1e2f1a8c2f36c517ad69234e37b223a020d8e8ab6b1cd:89f962330499abced6774003a72400193594cfc037ed75cc110fe0447bf15799:f0115a4f30fbf5902f6c38345d499cf6d388a71df3946be0b5a37302be29ca5e:5a28797aba5723bef07477ac9db6376701e13335241e2bb5c67df464dcce112d", + "7de469d18180ee313d8d7960b9e2f4edefcad5895bba6edf3fd6b16e57f45308:e926dd69c6c5a8604bdf1f8c92faab9672e308577d3f38cfd7895601f7c7d3e0:86fc0ee2db5cb5e7bf5e69d914e62600a01b376f7e20999de6e879328095778b:12055326c275405a192062137469888e1a498cd829a5c51bc0ddcc1880a10bc3" + ], + "mount_point": "$(cpath)/$(bundle-id)/rootfs", + "fs_group": null + }, + { + "driver": "local", + "driver_options": [], + "source": "local", + "fstype": "local", + "options": [ + "mode=0777" + ], + "mount_point": "^$(cpath)/$(sandbox-id)/local/host-empty-vol$", + "fs_group": null + } + ], + "sandbox_pidns": false, + "exec_commands": [] + } + ], + "common": { + "cpath": "/run/kata-containers/shared/containers", + "sfprefix": "^$(cpath)/$(bundle-id)-[a-z0-9]{16}-", + "ipv4_a": "(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])", + "ip_p": "[0-9]{1,5}", + "svc_name": "[A-Z_\\.\\-]+", + "dns_label": "[a-zA-Z0-9_\\.\\-]+", + "default_caps": [ + "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" + ], + "privileged_caps": [ + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_DAC_READ_SEARCH", + "CAP_FOWNER", + "CAP_FSETID", + "CAP_KILL", + "CAP_SETGID", + "CAP_SETUID", + "CAP_SETPCAP", + "CAP_LINUX_IMMUTABLE", + "CAP_NET_BIND_SERVICE", + "CAP_NET_BROADCAST", + "CAP_NET_ADMIN", + "CAP_NET_RAW", + "CAP_IPC_LOCK", + "CAP_IPC_OWNER", + "CAP_SYS_MODULE", + "CAP_SYS_RAWIO", + "CAP_SYS_CHROOT", + "CAP_SYS_PTRACE", + "CAP_SYS_PACCT", + "CAP_SYS_ADMIN", + "CAP_SYS_BOOT", + "CAP_SYS_NICE", + "CAP_SYS_RESOURCE", + "CAP_SYS_TIME", + "CAP_SYS_TTY_CONFIG", + "CAP_MKNOD", + "CAP_LEASE", + "CAP_AUDIT_WRITE", + "CAP_AUDIT_CONTROL", + "CAP_SETFCAP", + "CAP_MAC_OVERRIDE", + "CAP_MAC_ADMIN", + "CAP_SYSLOG", + "CAP_WAKE_ALARM", + "CAP_BLOCK_SUSPEND", + "CAP_AUDIT_READ", + "CAP_PERFMON", + "CAP_BPF", + "CAP_CHECKPOINT_RESTORE" + ] + }, + "request_defaults": { + "CreateContainerRequest": { + "allow_env_regex": [ + "^HOSTNAME=$(dns_label)$", + "^$(svc_name)_PORT_$(ip_p)_TCP=tcp://$(ipv4_a):$(ip_p)$", + "^$(svc_name)_PORT_$(ip_p)_TCP_PROTO=tcp$", + "^$(svc_name)_PORT_$(ip_p)_TCP_PORT=$(ip_p)$", + "^$(svc_name)_PORT_$(ip_p)_TCP_ADDR=$(ipv4_a)$", + "^$(svc_name)_SERVICE_HOST=$(ipv4_a)$", + "^$(svc_name)_SERVICE_PORT=$(ip_p)$", + "^$(svc_name)_SERVICE_PORT_$(dns_label)=$(ip_p)$", + "^$(svc_name)_PORT=tcp://$(ipv4_a):$(ip_p)$", + "^AZURE_CLIENT_ID=[A-Fa-f0-9-]+$", + "^AZURE_TENANT_ID=[A-Fa-f0-9-]+$", + "^AZURE_FEDERATED_TOKEN_FILE=/var/run/secrets/azure/tokens/azure-identity-token$", + "^AZURE_AUTHORITY_HOST=https://login\\.microsoftonline\\.com/$" + ] + }, + "CopyFileRequest": [ + "^$(cpath)/" + ], + "ExecProcessRequest": { + "commands": [], + "regex": [] + }, + "CloseStdinRequest": false, + "ReadStreamRequest": true, + "WriteStreamRequest": false + } +} diff --git a/tests/coco/data/large.rego b/tests/kata/data/large.rego similarity index 100% rename from tests/coco/data/large.rego rename to tests/kata/data/large.rego diff --git a/tests/kata/main.rs b/tests/kata/main.rs new file mode 100644 index 0000000..c329725 --- /dev/null +++ b/tests/kata/main.rs @@ -0,0 +1,115 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +use regorus::*; + +use std::path::Path; + +use anyhow::Result; +use clap::Parser; +use walkdir::WalkDir; + +fn run_kata_tests(tests_dir: &Path, generate: bool) -> Result<()> { + for entry in WalkDir::new(tests_dir) + .max_depth(1) // Do not recurse + .sort_by_file_name() + .into_iter() + .filter_map(|e| e.ok()) + { + let path = entry.path(); + if path == tests_dir || !path.is_dir() { + continue; + } + + let policy_file = path.join("policy.rego"); + let inputs_file = path.join("inputs.txt"); + let outputs_file = path.join("output.json"); + + let mut engine = Engine::new(); + engine.add_policy_from_file(&policy_file)?; + engine.set_gather_prints(true); + engine.set_strict_builtin_errors(false); + + #[cfg(feature = "coverage")] + engine.set_enable_coverage(true); + + // Keep a copy of the engine. + let engine_base = engine.clone(); + let mut results = if generate { + vec![] + } else { + Value::from_json_str(&std::fs::read_to_string(&outputs_file)?)? + .as_array()? + .iter() + .cloned() + .rev() + .collect() + }; + + let inputs = std::fs::read_to_string(&inputs_file)?; + for (lineno, line) in inputs.split('\n').enumerate() { + let line = line.trim(); + if line.is_empty() { + continue; + } + // Remove "ep": + let line = line.replace("\"ep\":", ""); + // Remove trailing , + let line = &line[0..line.len() - 1]; + + let request = Value::from_json_str(line)?; + + let rule = format!("data.agent_policy.{}", request[0].as_string()?.as_ref()); + let input = request[1].clone(); + + // Evaluate using engine. + engine.set_input(input.clone()); + let r = engine.eval_rule(rule.clone())?; + + // Evaluate using fresh engine. + let mut new_engine = engine_base.clone(); + new_engine.set_input(input); + let r_new = new_engine.eval_rule(rule)?; + + // Ensure that both evaluations produced the same result. + assert_eq!(r, r_new); + + if generate { + results.push(r); + } else { + let expected = results.pop().unwrap(); + assert_eq!(r, expected, "{lineno} failed in {}", inputs_file.display()); + } + } + + if generate { + std::fs::write(outputs_file, Value::from(results).to_json_str()?)?; + } + #[cfg(feature = "coverage")] + { + let report = engine.get_coverage_report()?; + println!("{}", report.to_colored_string()?); + } + } + + println!("kata tests passed"); + Ok(()) +} + +#[derive(clap::Parser)] +#[command(author, version, about, long_about = None)] +struct Cli { + /// Path to Kata test suite. + #[arg(long, short)] + #[clap(default_value = "tests/kata/data")] + test_dir: String, + + /// Generate outputs instead of testing. + #[arg(long, short)] + #[clap(default_value = "false")] + generate: bool, +} + +fn main() -> Result<()> { + let cli = Cli::parse(); + run_kata_tests(&Path::new(&cli.test_dir), cli.generate) +} diff --git a/tests/lexer/mod.rs b/tests/lexer/mod.rs index 7887f0a..909709f 100644 --- a/tests/lexer/mod.rs +++ b/tests/lexer/mod.rs @@ -226,7 +226,7 @@ fn invalid_line() -> Result<()> { #[test] fn file_more_than_64_kb_size() -> Result<()> { - let source = Source::from_file("tests/coco/data/large.rego")?; + let source = Source::from_file("tests/kata/data/large.rego")?; let mut lexer = Lexer::new(&source); let mut count = 0;