add Phase 2B contained runtime artifacts

This commit is contained in:
makearmy 2026-07-12 21:01:17 -04:00
parent a9fec0d83d
commit 82e64cec43
35 changed files with 5177 additions and 40 deletions

View file

@ -0,0 +1,28 @@
#!/bin/sh
set -eu
test "$(id -u)" -eq 0
test "$(docker version --format '{{.Server.Version}}')" = 29.6.1
test "$(sha256sum payload/usr/local/libexec/dockerd-rootless-29.6.1.sh | cut -d' ' -f1)" = 904c9b9e35f6927c0a5e65afb4d35b6bc9eb1278c878044501281fc728c9be46
test ! -S /run/user/1200/docker.sock
test "$(systemctl show user@1200.service -p ActiveState --value)" = inactive
test ! -e /var/lib/systemd/linger/le_app_codex
test -z "$(pgrep -u 1200 || true)"
test ! -e /run/netns/le-app-codex
test ! -e /etc/systemd/system/user@1200.service.d/50-le-app-codex-containment.conf
test ! -e /etc/systemd/system/user-1200.slice.d/50-le-app-codex-resources.conf
docker network inspect $(docker network ls -q) --format '{{.Name}} {{range .IPAM.Config}}{{.Subnet}} {{end}}'
ip -brief address
ip -4 route show table all
ip -6 route show table all
sed -n '1,120p' /etc/resolv.conf
nft --numeric list ruleset
systemctl cat user@1200.service
loginctl show-user 1200 || true
namei -om /srv/le-app-codex
find /srv/le-app-codex -xdev -printf '%M %u:%g %p\n'
getfacl -R -p /srv/le-app-codex
findmnt -R /srv/le-app-codex
grep -q 'elements = { 9\.9\.9\.9, 149\.112\.112\.112 }' payload/etc/nftables.d/50-le-app-codex.nft
grep -q 'elements = { 74\.67\.173\.56 }' payload/etc/nftables.d/50-le-app-codex.nft
grep -q '^nameserver 9\.9\.9\.9$' payload/etc/le-app-codex-runtime/resolv.conf
grep -q '^nameserver 149\.112\.112\.112$' payload/etc/le-app-codex-runtime/resolv.conf

View file

@ -0,0 +1,101 @@
#!/bin/sh
failures=0
artifact_root=$(CDPATH= cd -- "$(dirname -- "$0")/.." 2>/dev/null && pwd)
pass() { printf 'PASS: %s\n' "$1"; }
fail() { printf 'FAIL: %s\n' "$1" >&2; failures=$((failures + 1)); }
run_check() {
label=$1
shift
if "$@"; then pass "$label"; else fail "$label"; fi
}
expect_stat() {
path=$1 expected=$2
actual=$(/usr/bin/stat -c '%u:%g:%a:%F' "$path" 2>/dev/null)
if [ "$actual" = "$expected" ]; then pass "$path ownership/mode"; else fail "$path expected $expected, got ${actual:-missing}"; fi
}
expect_absent() {
path=$1
if [ ! -e "$path" ] && [ ! -L "$path" ]; then pass "$path absent"; else fail "$path must not already exist"; fi
}
if [ "$(id -u)" = 0 ]; then pass 'running as root'; else fail 'must be run as root'; fi
if [ -n "$artifact_root" ] && [ -d "$artifact_root/payload" ]; then pass 'artifact root resolved'; else fail 'artifact root not found'; fi
if (cd "$artifact_root" && /usr/bin/sha256sum -c MANIFEST.sha256); then pass 'complete artifact manifest'; else fail 'complete artifact manifest'; fi
if (cd "$artifact_root/inventory" && /usr/bin/sha256sum -c SHA256SUMS); then pass 'original root inventory'; else fail 'original root inventory'; fi
run_check 'staged nftables syntax' /usr/bin/nft --check -f "$artifact_root/payload/etc/nftables.d/50-le-app-codex.nft"
system_unit_path="$artifact_root/payload/etc/systemd/system:/etc/systemd/system:/run/systemd/system:/usr/local/lib/systemd/system:/usr/lib/systemd/system"
systemd_output=$(SYSTEMD_UNIT_PATH="$system_unit_path" /usr/bin/systemd-analyze verify --recursive-errors=no user@1200.service user-1200.slice le-app-codex-netns.service 2>&1)
systemd_rc=$?
printf '%s\n' "$systemd_output"
systemd_unexpected=$(printf '%s\n' "$systemd_output" | /usr/bin/sed -e '\#le-app-codex-netns.service: Command /usr/local/libexec/le-app-codex-netns is not executable: No such file or directory#d')
if [ "$systemd_rc" -eq 0 ] || [ -z "$systemd_unexpected" ]; then pass 'staged system units/drop-ins via host search path'; else fail 'staged system units/drop-ins via host search path'; fi
user_unit_path="$artifact_root/payload/srv/le-app-codex/home/.config/systemd/user:/etc/systemd/user:/run/systemd/user:/usr/local/lib/systemd/user:/usr/lib/systemd/user"
user_systemd_output=$(SYSTEMD_UNIT_PATH="$user_unit_path" /usr/bin/systemd-analyze verify --recursive-errors=no docker.service 2>&1)
user_systemd_rc=$?
printf '%s\n' "$user_systemd_output"
user_systemd_unexpected=$(printf '%s\n' "$user_systemd_output" | /usr/bin/sed -e '\#docker.service: Command /usr/local/libexec/dockerd-rootless-29.6.1.sh is not executable: No such file or directory#d')
if [ "$user_systemd_rc" -eq 0 ] || [ -z "$user_systemd_unexpected" ]; then pass 'staged Docker user unit via host search path'; else fail 'staged Docker user unit via host search path'; fi
shell_failures=0
for file in "$artifact_root"/tests/*.sh "$artifact_root"/payload/usr/local/libexec/*; do
/bin/sh -n "$file" || shell_failures=$((shell_failures + 1))
done
if [ "$shell_failures" -eq 0 ]; then pass 'shell syntax'; else fail 'shell syntax'; fi
json_failures=0
for file in "$artifact_root"/payload/srv/le-app-codex/home/.config/docker/daemon.json \
"$artifact_root"/payload/srv/le-app-codex/home/.docker/config.json \
"$artifact_root"/payload/srv/le-app-codex/home/.docker/contexts/meta/1b645e55bd6af77d420732b143423e7a27b12fbb5a71497e5d89c52481b6a535/meta.json \
"$artifact_root"/inventory/docker-networks.json; do
/usr/bin/jq empty "$file" || json_failures=$((json_failures + 1))
done
if [ "$json_failures" -eq 0 ]; then pass 'JSON syntax'; else fail 'JSON syntax'; fi
expect_stat /srv/le-app-codex '0:1200:750:directory'
expect_stat /srv/le-app-codex/home '1200:1200:700:directory'
expect_stat /srv/le-app-codex/lasereverything.net.db '1200:1200:700:directory'
expect_stat /srv/le-app-codex/database-source-readonly '0:1200:550:directory'
expect_stat /srv/le-app-codex/nonproduction-secrets '0:1200:750:directory'
expect_stat /srv/le-app-codex/container-runtime '1200:1200:700:directory'
expect_stat /srv/le-app-codex/testing-data '1200:1200:700:directory'
expect_stat /srv/le-app-codex/build-artifacts '1200:1200:700:directory'
project_mounts=$(/usr/bin/findmnt -rn -o TARGET | /usr/bin/awk '$0 == "/srv/le-app-codex" || index($0, "/srv/le-app-codex/") == 1 { print }')
if [ -z "$project_mounts" ]; then pass 'no project-rooted mounts'; else fail "unexpected project-rooted mounts: $project_mounts"; fi
expect_absent /etc/le-app-codex-runtime/OPERATOR-INPUTS-APPROVED
expect_absent /etc/le-app-codex-runtime/resolv.conf
expect_absent /etc/nftables.d/50-le-app-codex.nft
expect_absent /etc/systemd/system/le-app-codex-netns.service
expect_absent /etc/systemd/system/user-1200.slice.d/50-le-app-codex-resources.conf
expect_absent /etc/systemd/system/user@1200.service.d/50-le-app-codex-containment.conf
expect_absent /usr/local/libexec/dockerd-rootless-29.6.1.sh
expect_absent /usr/local/libexec/le-app-codex-netns
expect_absent /srv/le-app-codex/home/.config/systemd/user/docker.service
expect_absent /srv/le-app-codex/home/.config/docker/daemon.json
expect_absent /srv/le-app-codex/home/.docker/config.json
expect_absent /srv/le-app-codex/home/.docker/contexts/meta/1b645e55bd6af77d420732b143423e7a27b12fbb5a71497e5d89c52481b6a535/meta.json
expect_absent /srv/le-app-codex/phase2b-tests/00-root-preinstall-validate.sh
expect_absent /srv/le-app-codex/phase2b-tests/00-read-only-preflight.sh
expect_absent /srv/le-app-codex/phase2b-tests/10-inert-containment.sh
expect_absent /srv/le-app-codex/phase2b-tests/20-rootless-docker.sh
expect_absent /srv/le-app-codex/phase2b-tests/run-inert-without-user-manager.sh
active_state=$(/usr/bin/systemctl show user@1200.service -p ActiveState --value 2>/dev/null)
if [ "$active_state" = inactive ]; then pass 'user@1200.service inactive'; else fail "user@1200.service state is ${active_state:-unknown}"; fi
if [ ! -e /var/lib/systemd/linger/le_app_codex ] && [ ! -e /var/lib/systemd/linger/1200 ]; then pass 'linger absent'; else fail 'linger present'; fi
uid_processes=$(/usr/bin/pgrep -u 1200 2>/dev/null)
if [ -z "$uid_processes" ]; then pass 'UID 1200 has no processes'; else fail "UID 1200 processes found: $uid_processes"; fi
if [ ! -S /run/user/1200/docker.sock ]; then pass 'rootless Docker socket absent'; else fail 'rootless Docker socket exists'; fi
public_ipv4=$(/usr/bin/curl -4fsS --max-time 10 https://api.ipify.org 2>/dev/null)
if [ "$public_ipv4" = 74.67.173.56 ]; then pass 'public/NAT IPv4 unchanged'; else fail "public/NAT IPv4 drift or lookup failure: ${public_ipv4:-unavailable}"; fi
printf 'root pre-install validation failures=%s\n' "$failures"
[ "$failures" -eq 0 ]

View file

@ -0,0 +1,41 @@
#!/bin/sh
set -eu
fail=0
expect_denied() { if "$@" >/dev/null 2>&1; then echo "UNEXPECTED SUCCESS: $*" >&2; fail=1; fi; }
test "$(id -u)" -eq 1200
test "$DOCKER_HOST" = unix:///run/user/1200/docker.sock
touch /srv/le-app-codex/tmp/phase2b-write-test
rm /srv/le-app-codex/tmp/phase2b-write-test
expect_denied ls /var/www
expect_denied ls /root
expect_denied ls /home
expect_denied ls /srv/directus-archive
expect_denied ls /srv/codex-work
expect_denied stat /run/docker.sock
expect_denied stat /var/run/docker.sock
expect_denied stat /run/containerd/containerd.sock
expect_denied stat /dev/sda
expect_denied stat /proc/1/root
expect_denied sh -c 'echo x > /etc/phase2b-write-test'
expect_denied sh -c 'echo x > /srv/phase2b-write-test'
expect_denied curl -4fsS --connect-timeout 2 http://127.0.0.1
expect_denied curl -4fsS --connect-timeout 2 http://10.200.120.1
expect_denied curl -4fsS --connect-timeout 2 http://192.168.10.151
expect_denied nc -w2 -z 192.168.10.1 53
expect_denied curl -4fsS --connect-timeout 2 http://10.98.0.2
expect_denied curl -4fsS --connect-timeout 2 http://74.67.173.56
expect_denied curl -4fsS --connect-timeout 2 http://169.254.169.254/latest/meta-data/
expect_denied curl -6fsS --connect-timeout 2 https://[::1]/
expect_denied nc -w2 -z 1.1.1.1 25
expect_denied nc -w2 -z 1.1.1.1 465
expect_denied nc -w2 -z 1.1.1.1 587
test "$(ps -e --no-headers | wc -l)" -lt 32
test -w /dev/fuse
test -w /dev/net/tun
grep -q '^nameserver 9\.9\.9\.9$' /etc/resolv.conf
grep -q '^nameserver 149\.112\.112\.112$' /etc/resolv.conf
getent ahostsv4 example.com >/dev/null
test -r /sys/fs/cgroup/cpu.max
test -r /sys/fs/cgroup/memory.max
exit "$fail"

View file

@ -0,0 +1,35 @@
#!/bin/sh
set -eu
fail=0
export HOME=/srv/le-app-codex/home
export DOCKER_HOST=unix:///run/user/1200/docker.sock
ALPINE='docker.io/library/alpine:3.23.3@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659'
NGINX='docker.io/library/nginx:1.29.4-alpine@sha256:4870c12cd2ca986de501a804b4f506ad3875a0b1874940ba0a2c7f763f1855b2'
test "$(id -u)" -eq 1200
test -S /run/user/1200/docker.sock
test "$(docker context show)" = le-app-codex
test "$(docker context inspect le-app-codex --format '{{.Endpoints.docker.Host}}')" = unix:///run/user/1200/docker.sock
docker info --format '{{json .SecurityOptions}}' | grep -q rootless
test "$(docker info --format '{{.DockerRootDir}}')" = /srv/le-app-codex/container-runtime/docker
docker run --rm --network none "$ALPINE" true
docker run --rm "$ALPINE" wget -qO- https://example.com >/dev/null
for target in 127.0.0.1 10.200.120.1 192.168.10.1 192.168.10.151 10.98.0.2 74.67.173.56 169.254.169.254; do
if docker run --rm "$ALPINE" wget -T2 -qO- "http://$target" >/dev/null 2>&1; then
echo "UNEXPECTED CONTAINER ACCESS: $target" >&2; fail=1
fi
done
if docker run --rm "$ALPINE" sh -c 'nc -w2 -z 1.1.1.1 25 || nc -w2 -z 1.1.1.1 465 || nc -w2 -z 1.1.1.1 587'; then
echo 'UNEXPECTED CONTAINER SMTP ACCESS' >&2; fail=1
fi
cid=$(docker run -d --rm -p 18080:80 "$NGINX")
trap 'docker rm -f "$cid" >/dev/null 2>&1 || true' EXIT
sleep 2
for host in 127.0.0.1 192.168.10.151 10.98.0.2; do
if curl -fsS --connect-timeout 2 "http://$host:18080" >/dev/null 2>&1; then
echo "UNEXPECTED PUBLISHED PORT: $host" >&2; fail=1
fi
done
docker rm -f "$cid" >/dev/null
trap - EXIT
test ! -S /run/docker.sock || ! docker -H unix:///run/docker.sock info >/dev/null 2>&1
exit "$fail"

View file

@ -0,0 +1,20 @@
#!/bin/sh
set -eu
test "$(id -u)" -eq 0
test "$(systemctl is-active user@1200.service || true)" = inactive
systemd-run --wait --pipe --collect --unit=le-app-codex-inert-test \
--uid=1200 --gid=1200 \
--setenv=DOCKER_HOST=unix:///run/user/1200/docker.sock \
--property=NetworkNamespacePath=/run/netns/le-app-codex \
--property=ProtectSystem=strict --property=PrivateTmp=yes \
--property=ProtectHome=yes --property=ProtectProc=invisible \
--property=TemporaryFileSystem=/srv:ro \
--property=BindPaths=/srv/le-app-codex:/srv/le-app-codex \
--property=TemporaryFileSystem=/var:ro \
--property=TemporaryFileSystem=/mnt:ro \
--property=TemporaryFileSystem=/media:ro \
--property=TemporaryFileSystem=/opt:ro \
--property=DevicePolicy=closed --property='DeviceAllow=/dev/fuse rw' \
--property='DeviceAllow=/dev/net/tun rw' \
/srv/le-app-codex/phase2b-tests/10-inert-containment.sh