#!/usr/bin/env bash # End-to-end test through the real path: real overlayfs, namespaces, slirp4netns, filesystem. # Usage: tests/run.sh [path/to/doos] set -u DOOS=$(readlink -f "${1:-$(dirname "$0")/../build/doos}") export DOOS_HOME=$(mktemp -d) pass=0; fail=0 ok() { pass=$((pass+1)); echo "ok $1"; } bad() { fail=$((fail+1)); echo "FAIL $1"; } check() { local d=$1; shift; if "$@" >/dev/null 2>&1; then ok "$d"; else bad "$d"; fi; } out() { "$DOOS" attach t -- sh -c "$1" 2>&1; } trap ' for id in $(ls "$DOOS_HOME/davmounts" 2>/dev/null); do "$DOOS" davmount umount --yes "$id" >/dev/null 2>&1; done "$DOOS" delete --yes t >/dev/null 2>&1 rm -rf "$DOOS_HOME" ' EXIT check "list empty" "$DOOS" list check "docs cross-linked, links resolve" "$(dirname "$0")/../scripts/check-docs.sh" check "get_or_create creates" "$DOOS" get_or_create t [ "$("$DOOS" get_or_create t)" = "exists t" ] && ok "get_or_create idempotent" || bad "get_or_create idempotent" check "invalid name rejected" bash -c "! '$DOOS' get_or_create 'Bad Name'" check "net mode fixed at creation" bash -c "! '$DOOS' get_or_create --net none t" [ "$(out 'id -u')" = "0" ] && ok "root inside" || bad "root inside" [ "$(out hostname)" = "t" ] && ok "own hostname" || bad "own hostname" [ "$(out 'echo $$')" != "" ] && ok "attach runs command" || bad "attach runs command" [ "$(out 'cat /proc/1/comm; ps -e 2>/dev/null | wc -l' | head -1)" != "" ] && ok "own pid ns" || bad "own pid ns" [ "$(out 'ls /home 2>&1; echo $?' | tail -1)" != "0" ] && ok "host /home hidden" || bad "host /home hidden" [ "$(out 'test -e /sys && echo yes || echo no')" = "no" ] && ok "no /sys" || bad "no /sys" out 'echo x > /usr/doos-test-file && echo x > /etc/doos-test-file && echo x > /root/persist' >/dev/null [ ! -e /usr/doos-test-file ] && [ ! -e /etc/doos-test-file ] && ok "host /usr,/etc untouched" || bad "host /usr,/etc untouched" [ "$(out 'cat /usr/doos-test-file /root/persist | tr -d "\n"')" = "xx" ] && ok "writes persist across attach" || bad "writes persist across attach" [ "$(out 'echo 1 > /proc/sys/kernel/hostname 2>&1; echo $?' | tail -1)" != "0" ] && ok "/proc/sys read-only" || bad "/proc/sys read-only" # network: slirp out 'getent hosts example.com' | grep -q . && ok "slirp: dns" || bad "slirp: dns" out 'bash -c "exec 3<>/dev/tcp/1.1.1.1/443 && echo up"' | grep -q up && ok "slirp: outbound tcp" || bad "slirp: outbound tcp" python3 -m http.server 0 --bind 127.0.0.1 >/tmp/doos-hs.$$ 2>&1 & hs=$!; sleep 1 port=$(grep -o 'port [0-9]*' /tmp/doos-hs.$$ | grep -o '[0-9]*') [ -n "$port" ] && out "bash -c \"exec 3<>/dev/tcp/127.0.0.1/$port && echo reach\"" | grep -q reach && bad "host loopback isolated" || ok "host loopback isolated" kill $hs 2>/dev/null; rm -f /tmp/doos-hs.$$ # concurrent attach joins the running container "$DOOS" attach t -- sh -c 'sleep 4' & sleep 1 [ "$("$DOOS" list | awk '$1=="t"{print $2}')" = "running" ] && ok "list shows running" || bad "list shows running" check "delete refuses running" bash -c "! '$DOOS' delete --yes t" [ "$(out 'ls /tmp/ 2>&1; cat /proc/1/comm' | tail -1)" != "" ] && ok "second attach joins" || bad "second attach joins" wait; sleep 0.5 [ "$("$DOOS" list | awk '$1=="t"{print $2}')" = "stopped" ] && ok "stopped after exit" || bad "stopped after exit" # none / host modes "$DOOS" get_or_create --net none n >/dev/null "$DOOS" attach n -- bash -c 'exec 3<>/dev/tcp/1.1.1.1/443' >/dev/null 2>&1 && bad "net none blocks" || ok "net none blocks" "$DOOS" delete --yes n >/dev/null "$DOOS" get_or_create --net host --i-understand h >/dev/null "$DOOS" attach h --i-understand -- bash -c 'exec 3<>/dev/tcp/1.1.1.1/443' >/dev/null 2>&1 && ok "net host works" || bad "net host works" "$DOOS" delete --yes h >/dev/null # delete: dry-run by default "$DOOS" delete t | grep -q "dry run" && [ -d "$DOOS_HOME/containers/t" ] && ok "delete is dry-run" || bad "delete is dry-run" "$DOOS" delete --yes t | grep -q deleted && [ ! -e "$DOOS_HOME/containers/t" ] && ok "delete --yes removes" || bad "delete --yes removes" # ---- 1.0: skeleton, seeded /etc, uid mapping (host mode, container t was deleted above) "$DOOS" get_or_create s >/dev/null 2>&1 outs() { "$DOOS" attach s -- sh -c "$1" 2>&1; } [ "$(outs 'touch /usr/bin/doos-x && echo y')" = "y" ] && [ ! -e /usr/bin/doos-x ] && ok "skeleton: write into /usr/bin" || bad "skeleton: write into /usr/bin" [ "$(outs 'stat -c %u /etc/shells')" = "0" ] && ok "/etc owned by container root" || bad "/etc owned by container root" [ "$(outs 'chown 100:100 /root && stat -c %u:%g /root')" = "100:100" ] && ok "subuid: chown to other ids" || bad "subuid: chown to other ids" [ "$(outs 'ls /var/lib/dpkg/status >/dev/null && echo y')" = "y" ] && ok "/var/lib/dpkg present" || bad "/var/lib/dpkg present" "$DOOS" get_or_create --ids single s1 >/dev/null 2>&1 [ "$("$DOOS" attach s1 -- cat /proc/self/uid_map | wc -l)" = "1" ] && ok "ids single: one mapping" || bad "ids single: one mapping" "$DOOS" attach s1 -- sh -c 'cat /etc/apt/apt.conf.d/99doos-sandbox' 2>&1 | grep -q 'Sandbox::User "root"' && ok "ids single: apt sandbox override" || bad "ids single: apt sandbox override" check "ids fixed at creation" bash -c "! '$DOOS' get_or_create --ids subuid s1" check "bad size rejected" bash -c "! '$DOOS' get_or_create --size 1x z1" check "size below minimum rejected" bash -c "! '$DOOS' get_or_create --size 1m z2" check "size overflow rejected, not silently wrapped" bash -c "! '$DOOS' get_or_create --size 16777217t z3" # --mount: extra bind mounts mntd=$(mktemp -d); echo host-data > "$mntd/f" "$DOOS" get_or_create --mount "$mntd:/data" m >/dev/null [ "$("$DOOS" attach m -- cat /data/f)" = "host-data" ] && ok "mount: readable inside" || bad "mount: readable inside" "$DOOS" attach m -- sh -c 'echo from-container > /data/g' [ "$(cat "$mntd/g" 2>/dev/null)" = "from-container" ] && ok "mount: writes land on host" || bad "mount: writes land on host" "$DOOS" delete --yes m >/dev/null "$DOOS" get_or_create --mount "$mntd:/data:ro" mro >/dev/null "$DOOS" attach mro -- sh -c 'echo x > /data/should-fail' 2>/dev/null && bad "mount: ro rejects writes" || ok "mount: ro rejects writes" "$DOOS" delete --yes mro >/dev/null check "mount: missing source rejected" bash -c "'$DOOS' get_or_create --mount /no/such/dir:/data mbad >/dev/null && ! '$DOOS' attach mbad -- true" "$DOOS" delete --yes mbad >/dev/null 2>&1 check "mount: too many rejected" bash -c "! '$DOOS' get_or_create $(for i in 1 2 3 4 5 6 7 8 9; do printf -- '--mount %s:/m%s ' "$mntd" "$i"; done) mtoomany" rm -rf "$mntd" # --mount/--davmount with a dav(s):// or http(s):// source: auto-installs a managed rclone and # mounts a real WebDAV target. The target here is a disposable local server, run with its own # throwaway rclone copy (not doos's managed one) so the two downloads stay independent. echo "--- davmount (network)" davd=$(mktemp -d); echo webdav-data > "$davd/f.txt" davport=$(python3 -c "import socket;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.bind(('127.0.0.1',0));print(s.getsockname()[1]);s.close()" 2>/dev/null) davtmp=$(mktemp -d) davok=0 if [ -n "$davport" ] && curl -fsSL --proto '=https' https://downloads.rclone.org/v1.75.2/rclone-v1.75.2-linux-amd64.zip -o "$davtmp/rclone.zip" 2>/dev/null \ && unzip -q -o "$davtmp/rclone.zip" -d "$davtmp/x" 2>/dev/null; then davrclone="$davtmp/x/rclone-v1.75.2-linux-amd64/rclone" chmod +x "$davrclone" 2>/dev/null [ -x "$davrclone" ] && davok=1 fi if [ "$davok" = 1 ]; then "$davrclone" serve webdav "$davd" --addr "127.0.0.1:$davport" --user davuser --pass davpass123 --log-level=ERROR >/tmp/doos-davserve.$$ 2>&1 & davserve=$! sleep 1 davurl="http://davuser:davpass123@127.0.0.1:$davport/" "$DOOS" get_or_create --mount "$davurl" /data dav1 >/dev/null [ "$("$DOOS" attach dav1 -- cat /data/f.txt)" = "webdav-data" ] && ok "davmount: readable inside" || bad "davmount: readable inside" "$DOOS" attach dav1 -- sh -c 'echo from-dav-container > /data/g.txt' sleep 2 [ "$(cat "$davd/g.txt" 2>/dev/null)" = "from-dav-container" ] && ok "davmount: writes reach the webdav backend" || bad "davmount: writes reach the webdav backend" "$DOOS" delete --yes dav1 >/dev/null "$DOOS" get_or_create --mount "$davurl" /data dav2 >/dev/null t0=$(date +%s%N); "$DOOS" attach dav2 -- true; t1=$(date +%s%N) [ $(( (t1 - t0) / 1000000 )) -lt 3000 ] && ok "davmount: reused without remounting" || bad "davmount: reused without remounting" "$DOOS" delete --yes dav2 >/dev/null "$DOOS" get_or_create --davmount "http://127.0.0.1:$davport/" /data --dav-user davuser --dav-pass davpass123 dav3 >/dev/null [ "$("$DOOS" attach dav3 -- cat /data/f.txt)" = "webdav-data" ] && ok "davmount: --davmount with --dav-user/--dav-pass" || bad "davmount: --davmount with --dav-user/--dav-pass" "$DOOS" delete --yes dav3 >/dev/null "$DOOS" get_or_create --mount "$davurl" /data:ro davro >/dev/null "$DOOS" attach davro -- sh -c 'echo x > /data/should-fail' 2>/dev/null && bad "davmount: ro rejects writes" || ok "davmount: ro rejects writes" "$DOOS" delete --yes davro >/dev/null check "davmount: wrong password rejected with a clear error" bash -c " '$DOOS' get_or_create --mount 'http://127.0.0.1:$davport/' /data --dav-user davuser --dav-pass wrongpass davbad >/dev/null '$DOOS' attach davbad -- true 2>&1 | grep -q 'authentication failed' " "$DOOS" delete --yes davbad >/dev/null 2>&1 check "davmount: unknown vendor rejected" bash -c "! '$DOOS' get_or_create --mount 'http://127.0.0.1:$davport/' /data --dav-vendor notavendor davvendor" "$DOOS" davmount list | grep -q "$davport" && ok "davmount list: shows an active target" || bad "davmount list: shows an active target" david=$("$DOOS" davmount list | awk -v p="$davport" '$0 ~ p {print $1}' | head -1) "$DOOS" davmount umount "$david" | grep -q "dry run" && ok "davmount umount: dry-run by default" || bad "davmount umount: dry-run by default" "$DOOS" davmount umount --yes "$david" | grep -q unmounted && ok "davmount umount: --yes removes it" || bad "davmount umount: --yes removes it" kill $davserve 2>/dev/null else echo "skip: davmount tests (no network or unzip for the test webdav server)" fi rm -rf "$davd" "$davtmp" /tmp/doos-davserve.$$ 2>/dev/null "$DOOS" attach s -- sh -c 'apt-get update >/dev/null 2>&1; apt-get install -y --no-install-recommends cowsay >/dev/null 2>&1; dpkg --audit && /usr/games/cowsay ok >/dev/null' && ok "host mode: apt install completes" || bad "host mode: apt install completes" [ ! -e /usr/games/cowsay ] && ok "host untouched by apt install" || bad "host untouched by apt install" "$DOOS" delete --yes s >/dev/null; "$DOOS" delete --yes s1 >/dev/null # ---- images echo "--- images (network)" "$DOOS" image list | grep -q "alpine.*available" && ok "image list shows catalog" || bad "image list shows catalog" check "http url refused" bash -c "! '$DOOS' image add http://example.com/x.tar.gz" check "url without sha256 refused" bash -c "! '$DOOS' image add https://example.com/x.tar.gz" check "unknown image refused" bash -c "! '$DOOS' get_or_create --image nosuch i0" tmpd=$(mktemp -d); mkdir -p $tmpd/r/bin; echo hi > $tmpd/r/bin/f; tar -C $tmpd/r -czf $tmpd/mini.tar.gz . check "wrong sha256 rejected" bash -c "! '$DOOS' image add --sha256 $(printf '0%.0s' $(seq 64)) $tmpd/mini.tar.gz mini" [ ! -e "$DOOS_HOME/images/mini" ] && [ ! -e "$DOOS_HOME/images/.tmp-mini" ] && ok "failed add leaves nothing" || bad "failed add leaves nothing" check "local tar added" "$DOOS" image add $tmpd/mini.tar.gz mini rm -rf $tmpd; "$DOOS" image delete --yes mini >/dev/null "$DOOS" attach --image alpine a1 -- true >/dev/null 2>&1 "$DOOS" image list | grep -q "alpine.*installed" && ok "image auto-fetched on demand" || bad "image auto-fetched on demand" [ "$("$DOOS" attach a1 -- sh -c '. /etc/os-release; echo $ID')" = "alpine" ] && ok "alpine image runs" || bad "alpine image runs" "$DOOS" attach a1 -- sh -c 'apk add --no-cache jq >/dev/null 2>&1 && jq --version' | grep -q jq && ok "alpine: apk add" || bad "alpine: apk add" [ "$("$DOOS" attach a1 -- sh -c 'chown 100:100 /root && stat -c %u /root')" = "100" ] && ok "image: subuid chown" || bad "image: subuid chown" "$DOOS" attach a1 -- sh -c 'exec 3<>/dev/tcp/1.1.1.1/443' >/dev/null 2>&1; "$DOOS" attach a1 -- sh -c 'wget -qO- https://example.com | grep -q html' && ok "image: network + dns" || bad "image: network + dns" check "image delete refused while used" bash -c "! '$DOOS' image delete --yes alpine" check "image in use is a dry run too" bash -c "! '$DOOS' image delete alpine" "$DOOS" get_or_create --image alpine --ids single a2 >/dev/null 2>&1 && bad "image ids mismatch rejected" || ok "image ids mismatch rejected" "$DOOS" delete --yes a1 >/dev/null "$DOOS" image delete alpine | grep -q "dry run" && [ -d "$DOOS_HOME/images/alpine" ] && ok "image delete is dry-run" || bad "image delete is dry-run" "$DOOS" image delete --yes alpine | grep -q deleted && [ ! -e "$DOOS_HOME/images/alpine" ] && ok "image delete --yes removes" || bad "image delete --yes removes" "$DOOS" attach --image ubuntu u1 -- sh -c 'apt-get update >/dev/null 2>&1; apt-get install -y --no-install-recommends cowsay >/dev/null 2>&1; dpkg --audit && /usr/games/cowsay ok >/dev/null' && ok "ubuntu image: apt install completes" || bad "ubuntu image: apt install completes" "$DOOS" delete --yes u1 >/dev/null; "$DOOS" image delete --yes ubuntu >/dev/null # ---- size cap (needs cgroup v2 delegation; scans every 2 s at the earliest) echo "--- size cap, accounting of other ids" "$DOOS" get_or_create --size 64m cap >/dev/null 2>&1 # 80 MB written as container uid 5 into a mode-700 directory the host user cannot read: only an id-map scan sees it "$DOOS" attach cap -- python3 -c ' import os,time os.mkdir("/root/d5"); os.chown("/root/d5",5,5); os.chmod("/root/d5",0o700); os.setgid(5); os.setuid(5) for n in (1,2): with open("/root/d5/f%d"%n,"wb") as f: f.write(b"x"*(40*1024*1024)); f.flush(); os.fsync(f.fileno()) time.sleep(40)' >/tmp/doos-cap.$$ 2>&1 & sleep 9 if grep -q "FROZEN" /tmp/doos-cap.$$; then ok "size cap freezes (counts other uids)" "$DOOS" attach cap -- sh -c 'grep -q /shell /proc/self/cgroup 2>/dev/null; rm -rf /root/d5' >/dev/null 2>&1; sleep 5 grep -q thawed /tmp/doos-cap.$$ && ok "size cap thaws after cleanup" || bad "size cap thaws after cleanup" elif grep -q "cannot freeze\|not enforced" /tmp/doos-cap.$$; then echo "skip size cap: no cgroup delegation here (warn-only mode confirmed)" else bad "size cap freezes (counts other uids)" fi kill %% 2>/dev/null; wait 2>/dev/null; rm -f /tmp/doos-cap.$$ "$DOOS" delete --yes cap >/dev/null 2>&1 # ---- resource limits, rescue leaf, hardening echo "--- limits and hardening" "$DOOS" get_or_create --memory 256m --pids 32 --cpus 0.5 lim >/dev/null 2>&1 "$DOOS" list | grep -q "^lim" && ok "limit options accepted" || bad "limit options accepted" r=$("$DOOS" attach lim -- python3 -c 'b=bytearray(b"x"*(400*1024*1024)); print("survived")' 2>&1) echo "$r" | grep -q "killed by its memory limit" && ! echo "$r" | grep -q survived && ok "memory limit kills the container" || bad "memory limit kills the container" r=$("$DOOS" attach lim -- sh -c 'for i in $(seq 1 80); do sleep 3 & done 2>&1; wait' 2>&1) echo "$r" | grep -q "process limit" && ok "pids limit enforced and reported" || bad "pids limit enforced and reported" check "limits can change later" "$DOOS" get_or_create --memory 512m lim check "memory below minimum refused" bash -c "! '$DOOS' get_or_create --memory 1m z3" [ ! -e "$DOOS_HOME/containers/z3" ] && ok "failed create leaves nothing" || bad "failed create leaves nothing" check "bad cpus refused" bash -c "! '$DOOS' get_or_create --cpus abc z4" "$DOOS" attach lim -- sleep 6 >/dev/null 2>&1 & sleep 1.5 [ "$("$DOOS" attach lim -- sh -c 'grep -c /shell /proc/self/cgroup')" = "1" ] && ok "second attach runs in the shell cgroup" || bad "second attach runs in the shell cgroup" wait h() { "$DOOS" attach lim -- python3 -c "$1" 2>&1; } [ "$(h 'import ctypes,errno;l=ctypes.CDLL(None,use_errno=True);print(l.syscall(250,0,0,0,0,0),errno.errorcode[ctypes.get_errno()])')" = "-1 EPERM" ] && ok "seccomp: keyctl denied" || bad "seccomp: keyctl denied" [ "$(h 'import ctypes,errno;l=ctypes.CDLL(None,use_errno=True);print(l.syscall(272,0x10000000),errno.errorcode[ctypes.get_errno()])')" = "-1 EPERM" ] && ok "seccomp: nested user namespace denied" || bad "seccomp: nested user namespace denied" [ "$(h 'import ctypes,errno;l=ctypes.CDLL(None,use_errno=True);print(l.syscall(435,0,0),errno.errorcode[ctypes.get_errno()])')" = "-1 ENOSYS" ] && ok "seccomp: clone3 answers ENOSYS" || bad "seccomp: clone3 answers ENOSYS" "$DOOS" attach lim -- sh -c 'mount -t tmpfs t /mnt' >/dev/null 2>&1 && bad "mount denied" || ok "mount denied" [ "$("$DOOS" attach lim -- sh -c 'grep ^CapEff /proc/self/status | cut -f2')" = "00000000a80425fb" ] && ok "capabilities reduced to the default set" || bad "capabilities reduced to the default set" "$DOOS" attach lim -- sh -c 'gcc --version >/dev/null && curl -sS -o /dev/null https://example.com && git --version >/dev/null && python3 -c "import ssl" && ping -c1 -W3 1.1.1.1 >/dev/null' && ok "common tools work under hardening" || bad "common tools work under hardening" [ "$("$DOOS" attach lim -- sh -c 'ls /etc | wc -l')" -lt 80 ] && ok "etc is an allowlist" || bad "etc is an allowlist" "$DOOS" attach lim -- sh -c 'ls /etc/shadow /etc/sudoers /etc/ssh/ssh_host_rsa_key' >/dev/null 2>&1 && bad "etc: no credentials" || ok "etc: no credentials" "$DOOS" get_or_create --hardening relaxed --i-understand rx >/dev/null 2>&1 "$DOOS" attach rx --i-understand -- sh -c 'unshare -U true && grep -q "^Seccomp:.0" /proc/self/status' >/dev/null 2>&1 && ok "relaxed: no seccomp, nested userns allowed" || bad "relaxed: no seccomp, nested userns allowed" check "hardening fixed at creation" bash -c "! '$DOOS' get_or_create --hardening relaxed --i-understand lim" "$DOOS" delete --yes lim >/dev/null; "$DOOS" delete --yes rx >/dev/null # ---- pasta network mode echo "--- pasta" python3 -c ' import socket,time s=socket.socket(); s.bind(("127.0.0.1",48765)); s.listen(5) a=socket.socket(socket.AF_UNIX); a.bind("\0doos-abstract-test"); a.listen(5) time.sleep(25)' & lp=$! sleep 1 "$DOOS" get_or_create --net pasta pa >/dev/null 2>&1 pr() { "$DOOS" attach pa -- python3 -c "$1" 2>&1; } [ "$(pr 'import socket;s=socket.create_connection(("127.0.0.1",48765),2);print("ok")')" = "ok" ] && ok "pasta: host loopback reachable" || bad "pasta: host loopback reachable" [ "$(pr 'import socket;s=socket.socket(socket.AF_UNIX) try: s.connect("\0doos-abstract-test");print("reachable") except Exception: print("blocked")')" = "blocked" ] && ok "pasta: host abstract sockets not shared" || bad "pasta: host abstract sockets not shared" [ "$(pr 'import urllib.request;print(urllib.request.urlopen("https://example.com",timeout=6).status)')" = "200" ] && ok "pasta: dns and https" || bad "pasta: dns and https" "$DOOS" attach pa -- python3 -c ' import socket s=socket.socket(); s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1); s.bind(("0.0.0.0",48911)); s.listen(1); s.settimeout(8) c,_=s.accept(); print(c.recv(5).decode())' >/tmp/doos-in.$$ 2>&1 & sleep 2.5; python3 -c 'import socket;s=socket.create_connection(("127.0.0.1",48911),2);s.sendall(b"hello")' 2>/dev/null; wait %2 2>/dev/null; sleep 0.5 grep -q hello /tmp/doos-in.$$ && ok "pasta: inbound port forwarded" || bad "pasta: inbound port forwarded" rm -f /tmp/doos-in.$$; kill $lp 2>/dev/null; wait 2>/dev/null "$DOOS" delete --yes pa >/dev/null # ---- documented network examples that were not covered yet, and the apparmor command echo "--- network examples" "$DOOS" apparmor | grep -q "profile doos.* $DOOS flags=(unconfined)" && ok "apparmor: profile names this binary" || bad "apparmor: profile names this binary" "$DOOS" attach --net none iso -- sh -c 'curl -sS -m 3 https://example.com >/dev/null 2>&1; [ $? -eq 6 ] && [ "$(tail -n +3 /proc/net/dev | cut -d: -f1 | tr -d " ")" = lo ]' >/dev/null 2>&1 && ok "none: curl exits 6, only lo" || bad "none: curl exits 6, only lo" "$DOOS" attach nets -- timeout 5 python3 -m http.server 8077 --bind 0.0.0.0 >/dev/null 2>&1 & sleep 2.5 curl -sS -m 2 -o /dev/null http://127.0.0.1:8077/ >/dev/null 2>&1 && bad "slirp: no inbound path" || ok "slirp: no inbound path" wait %% 2>/dev/null "$DOOS" get_or_create --net pasta pa1 >/dev/null 2>&1; "$DOOS" get_or_create --net pasta pa2 >/dev/null 2>&1 "$DOOS" attach pa1 -- timeout 25 python3 -m http.server 8066 --bind 0.0.0.0 >/dev/null 2>&1 & # pasta notices a newly bound port after a few seconds: poll instead of sleeping a fixed time c2c=000 for _ in $(seq 1 18); do sleep 1 c2c=$("$DOOS" attach pa2 -- curl -sS -m 2 -o /dev/null -w '%{http_code}' http://127.0.0.1:8066/ 2>/dev/null) [ "$c2c" = "200" ] && break done [ "$c2c" = "200" ] && ok "pasta: container to container via loopback" || bad "pasta: container to container via loopback" wait 2>/dev/null "$DOOS" delete --yes pa1 >/dev/null; "$DOOS" delete --yes pa2 >/dev/null; "$DOOS" delete --yes nets >/dev/null; "$DOOS" delete --yes iso >/dev/null # ---- signatures and more catalog images echo "--- signatures" "$DOOS" image add alpine 2>&1 | grep -q "signature verified" && ok "alpine signature verified against pinned key" || bad "alpine signature verified against pinned key" "$DOOS" image delete --yes alpine >/dev/null shim=$(mktemp -d) mkdir -p "$DOOS_HOME/bin" printf '#!/bin/sh\ncase "$*" in\n *--with-colons*) echo "fpr:::::::::0482D84022F52DF1C4E7CD43293ACD0907D9495A:";;\n *--verify*) echo "[GNUPG:] GOODSIG AAAAAAAAAAAAAAAA M"; echo "[GNUPG:] VALIDSIG 1111111111111111111111111111111111111111 2026-01-01 1 0 4 0 1 10 00 1111111111111111111111111111111111111111";;\nesac\nexit 0\n' > $shim/gpg; chmod +x $shim/gpg rm -f "$DOOS_HOME/bin/gpg"; cp $shim/gpg "$DOOS_HOME/bin/gpg" "$DOOS" image add alpine >/dev/null 2>&1 && bad "signature by the wrong key refused" || ok "signature by the wrong key refused" [ ! -e "$DOOS_HOME/images/alpine" ] && ok "refused signature leaves no image" || bad "refused signature leaves no image" sed -i 's/GOODSIG AAAAAAAAAAAAAAAA M/BADSIG 293ACD0907D9495A N/' $shim/gpg cp $shim/gpg "$DOOS_HOME/bin/gpg" "$DOOS" image add alpine >/dev/null 2>&1 && bad "BADSIG refused" || ok "BADSIG refused" rm -rf $shim; rm -f "$DOOS_HOME/bin/gpg" "$DOOS" attach --image debian d1 -- sh -c '. /etc/os-release; echo $ID; apt-get update >/dev/null 2>&1; apt-get install -y --no-install-recommends cowsay >/dev/null 2>&1; dpkg --audit && /usr/games/cowsay ok >/dev/null' 2>/dev/null | grep -q debian && ok "debian image: apt install completes" || bad "debian image: apt install completes" "$DOOS" delete --yes d1 >/dev/null; "$DOOS" image delete --yes debian >/dev/null echo "passed=$pass failed=$fail" [ "$fail" -eq 0 ]