diff --git a/.github/test-apparmor.sh b/.github/test-apparmor.sh index 440c2900..d3fa2d88 100755 --- a/.github/test-apparmor.sh +++ b/.github/test-apparmor.sh @@ -164,11 +164,11 @@ else fail "retool-executor profile NOT found in kernel" fi -# nsjail profile uses flags=(unconfined), so it appears as "(unconfined)" in the kernel -if echo "$PROFILES" | grep -q "/usr/bin/nsjail"; then - pass "usr.bin.nsjail profile loaded" +# nsjail child profile loaded via px transition from retool-executor +if echo "$PROFILES" | grep -q "retool-executor//nsjail"; then + pass "retool-executor//nsjail child profile loaded" else - fail "usr.bin.nsjail profile NOT found in kernel" + fail "retool-executor//nsjail child profile NOT found in kernel" fi if echo "$PROFILES" | grep -q "retool-agent-sandbox (enforce)"; then @@ -205,10 +205,13 @@ else fail "test-executor pod profile is '$CURRENT', expected 'retool-executor (enforce)'" fi +# Under the px transition design, the parent profile denies mount/userns. +# Only /usr/bin/nsjail (which transitions to retool-executor//nsjail) gets them. +# Verify that a regular binary CANNOT use these operations. if kubectl exec test-executor -n "$NAMESPACE" -- unshare --user --mount --pid --fork echo "OK" 2>/dev/null; then - pass "unshare --user --mount --pid --fork succeeds under retool-executor" + fail "unshare --user --mount should be DENIED under retool-executor (only nsjail gets these via px transition)" else - fail "unshare --user --mount --pid --fork FAILED under retool-executor" + pass "unshare --user --mount correctly denied under retool-executor (restricted to nsjail)" fi # --------------------------------------------------------------------------- diff --git a/charts/retool/Chart.yaml b/charts/retool/Chart.yaml index 47e125e1..29e9c10c 100644 --- a/charts/retool/Chart.yaml +++ b/charts/retool/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: retool description: A Helm chart for Kubernetes type: application -version: 6.11.26 +version: 6.11.27 maintainers: - name: Retool Engineering email: engineering+helm@retool.com diff --git a/charts/retool/ci/test-apparmor-nsjail-option.yaml b/charts/retool/ci/test-apparmor-nsjail-option.yaml index 59894d00..87d11ff4 100644 --- a/charts/retool/ci/test-apparmor-nsjail-option.yaml +++ b/charts/retool/ci/test-apparmor-nsjail-option.yaml @@ -1,7 +1,7 @@ # Exercises the AppArmor profile installer for nsjail (code-executor and # js-executor). When codeExecutor.appArmorProfileInstaller is true the chart # renders: -# - apparmor_nsjail_configmap.yaml (usr.bin.nsjail + retool-executor profiles) +# - apparmor_nsjail_configmap.yaml (retool-executor + retool-executor//nsjail profiles) # - apparmor_nsjail_daemonset.yaml (DaemonSet that loads them on each node) # - container.apparmor.security.beta.kubernetes.io annotations switch from # "unconfined" to "localhost/retool-executor" on code-executor and js-executor diff --git a/charts/retool/templates/apparmor_nsjail_configmap.yaml b/charts/retool/templates/apparmor_nsjail_configmap.yaml index ffbb76d9..4a8e7042 100644 --- a/charts/retool/templates/apparmor_nsjail_configmap.yaml +++ b/charts/retool/templates/apparmor_nsjail_configmap.yaml @@ -8,16 +8,45 @@ metadata: {{- include "retool.labels" . | nindent 4 }} app.kubernetes.io/component: apparmor-installer data: - usr.bin.nsjail: | - profile /usr/bin/nsjail flags=(unconfined) { - userns, - } retool-executor: | #include profile retool-executor flags=(attach_disconnected,mediate_deleted) { #include + network, + capability, + file, + + signal (send,receive) peer=retool-executor, + signal (receive) peer=unconfined, + + ptrace (trace,read,tracedby,readby) peer=retool-executor, + + deny @{PROC}/* w, + deny @{PROC}/sys/[^k]*/** wklx, + deny @{PROC}/sys/kernel/{?,??,[^s][^h][^m]**} wklx, + deny @{PROC}/sysrq-trigger rwklx, + deny @{PROC}/kcore rwklx, + + deny /sys/[^f]*/** wklx, + deny /sys/f[^s]*/** wklx, + deny /sys/fs/[^c]*/** wklx, + deny /sys/fs/c[^g]*/** wklx, + deny /sys/fs/cg[^r]*/** wklx, + deny /sys/firmware/** rwklx, + deny /sys/devices/virtual/powercap/** rwklx, + deny /sys/kernel/security/** rwklx, + + # Only nsjail gets mount/pivot_root/userns — mandatory profile + # transition on exec. The container's own processes (Node.js, etc.) + # cannot call these syscalls directly. + /usr/bin/nsjail px -> retool-executor//nsjail, + } + + profile retool-executor//nsjail flags=(attach_disconnected,mediate_deleted) { + #include + network, capability, file, @@ -26,10 +55,11 @@ data: pivot_root, userns, + signal (send,receive) peer=retool-executor//nsjail, signal (send,receive) peer=retool-executor, signal (receive) peer=unconfined, - ptrace (trace,read,tracedby,readby) peer=retool-executor, + ptrace (trace,read,tracedby,readby) peer=retool-executor//nsjail, deny @{PROC}/* w, deny @{PROC}/sys/[^k]*/** wklx, diff --git a/charts/retool/templates/apparmor_nsjail_daemonset.yaml b/charts/retool/templates/apparmor_nsjail_daemonset.yaml index 9a86d8bc..0e6399f9 100644 --- a/charts/retool/templates/apparmor_nsjail_daemonset.yaml +++ b/charts/retool/templates/apparmor_nsjail_daemonset.yaml @@ -43,13 +43,11 @@ spec: - | set -e rm -f /host-run/retool-executor.loaded - cp /profiles/usr.bin.nsjail /host-apparmor/usr.bin.nsjail cp /profiles/retool-executor /host-apparmor/retool-executor if ! nsenter -t 1 -m -- which apparmor_parser >/dev/null 2>&1; then echo "ERROR: apparmor_parser not found on host but AppArmor profile installation was requested" >&2 exit 1 fi - nsenter -t 1 -m -- apparmor_parser -r /etc/apparmor.d/usr.bin.nsjail nsenter -t 1 -m -- apparmor_parser -r /etc/apparmor.d/retool-executor touch /host-run/retool-executor.loaded echo "AppArmor profiles loaded successfully"