#!/usr/bin/env bash # tests/e2e/10_image.sh -- the image itself: build, cache, contents, fail-closed entrypoint. source "$TESTS/lib.sh" PROJ="$WORK/proj"; mkdir -p "$PROJ"; cd "$PROJ" || exit 1 t_name "build" sb build assert_rc 0 "ai-sandbox build succeeds" IMG="$("$AISB" --print-policy --net none 2>/dev/null | grep '^image=' | cut -d= -f2)" [[ -n "$IMG" ]] && _ok "image ref resolved: $IMG" || _bad "image ref resolved" podman image exists "$IMG" && _ok "image exists in podman storage" || _bad "image exists" t_name "build is cached and tagged by content" START=$SECONDS sb build assert_rc 0 "rebuild succeeds" (( SECONDS - START < 15 )) && _ok "second build reused the cache ($((SECONDS - START))s)" \ || _bad "second build reused the cache ($((SECONDS - START))s)" IMG2="$("$AISB" --print-policy --net none 2>/dev/null | grep '^image=' | cut -d= -f2)" assert_eq "$IMG" "$IMG2" "same inputs, same tag" sb --print-policy --net none --install hexd > /dev/null IMG3="$("$AISB" --print-policy --net none --install hexd | grep '^image=' | cut -d= -f2)" [[ "$IMG3" != "$IMG" ]] && _ok "extra packages produce a different tag" || _bad "extra packages change the tag" t_name "the entrypoint fails closed without a policy" podman run --rm --network none --cap-drop=ALL "$IMG" true > "$WORK/fc.out" 2>&1 assert_eq 78 "$?" "no /run/aisb policy -> exit 78, nothing ran" grep -q 'policy file' "$WORK/fc.out" && _ok "it says why" || _bad "it says why: $(cat "$WORK/fc.out")" t_name "image contents" POL="$WORK/pol"; mkdir -p "$POL" printf 'AISB_NET=none\nAISB_UID=1000\nAISB_GID=1000\nAISB_ALLOW_ICMP=1\nAISB_ALLOW_DNS=0\n' > "$POL/policy.env" : > "$POL/direct4.list"; : > "$POL/direct6.list" # podman takes mount options as a comma list: ":ro,:z" not ":ro:z" SEL="${AISB_SELINUX:-:z}"; SELC="${SEL#:}" run_in() { podman run --rm --network none --cap-drop=ALL --cap-add=setuid --cap-add=setgid --cap-add=setpcap \ --userns "keep-id:uid=1000,gid=1000" -u 0:0 \ --security-opt no-new-privileges \ -v "$POL:/run/aisb:ro${SELC:+,$SELC}" -v "$PROJ:/project$SEL" -w /project \ "$IMG" bash -lc "$1" 2>&1 } OUT="$(run_in 'id -u')"; assert_eq "1000" "$(printf '%s' "$OUT" | tail -1)" "drops to uid 1000" OUT="$(run_in 'grep CapEff /proc/self/status | cut -f2')" assert_eq "0000000000000000" "$(printf '%s' "$OUT" | tail -1)" "no capabilities left for the command" OUT="$(run_in 'node --version')" printf '%s' "$OUT" | grep -qE '^v2[2-9]' && _ok "node $(printf '%s' "$OUT" | tail -1)" || _bad "node 22+: $OUT" OUT="$(run_in 'command -v pi')" printf '%s' "$OUT" | grep -q '/pi' && _ok "the pi harness is installed" || _bad "pi installed: $OUT" OUT="$(run_in 'nft --version && setpriv --version | head -1 && git --version')" assert_contains "nftables" "nftables present" assert_contains "git version" "git present" OUT="$(run_in 'pwd')" assert_eq "/project" "$(printf '%s' "$OUT" | tail -1)" "working directory is /project" t_name "the container cannot see the host filesystem" SECRET="$WORK/parent-secret.txt"; echo "do not read" > "$SECRET" OUT="$(run_in "cat '$SECRET' 2>&1 || echo NOT_VISIBLE")" assert_contains "NOT_VISIBLE" "files outside the mount are unreachable" OUT="$(run_in 'ls / | tr "\n" " "')" assert_absent "$(id -un)" "no host home directories in /" t_name "resource caps are visible from inside" OUT="$(podman run --rm --network none --cap-drop=ALL --cap-add=setuid --cap-add=setgid --cap-add=setpcap --pids-limit 64 --memory 128m \ --userns "keep-id:uid=1000,gid=1000" -u 0:0 \ -v "$POL:/run/aisb:ro${SELC:+,$SELC}" "$IMG" \ bash -lc 'cat /sys/fs/cgroup/pids.max; cat /sys/fs/cgroup/memory.max' 2>&1)" assert_contains "64" "pids.max = 64" assert_contains "134217728" "memory.max = 128m"