Initial Commit
This commit is contained in:
Executable
+81
@@ -0,0 +1,81 @@
|
||||
#!/bin/bash
|
||||
# =====================================================================
|
||||
# Runs INSIDE the sandbox VM. Proves the guest cannot reach this host or
|
||||
# the LAN, and - just as important - that it CAN reach the internet.
|
||||
#
|
||||
# WHY THE POSITIVE CONTROLS ARE NOT OPTIONAL
|
||||
#
|
||||
# A suite made only of "this must fail" passes perfectly on a machine
|
||||
# with no network at all, on a machine that is switched off, and on a
|
||||
# machine where the test harness is broken. Every must-fail target here
|
||||
# is paired with a must-succeed one, so a pass means "the filter works"
|
||||
# rather than "nothing works".
|
||||
#
|
||||
# ping IS EXPECTED TO FAIL, AND THAT IS NOT THE FILTER
|
||||
#
|
||||
# net.ipv4.ping_group_range on the host is "1 0", so no group may open an
|
||||
# unprivileged ICMP socket and libslirp cannot forward one. ping fails
|
||||
# here permanently, on a machine whose HTTPS works perfectly. It is
|
||||
# asserted as a known-fail rather than left to be rediscovered as a
|
||||
# mystery by everyone who ever debugs this.
|
||||
# =====================================================================
|
||||
fails=0
|
||||
pass() { printf ' ok %s\n' "$*"; }
|
||||
fail() { printf ' FAIL %s\n' "$*"; fails=$((fails+1)); }
|
||||
|
||||
# A TCP connect with a short timeout, using bash's own /dev/tcp so the
|
||||
# result does not depend on nc, curl or python being installed.
|
||||
reach() {
|
||||
timeout 4 bash -c "exec 3<>/dev/tcp/$1/$2" 2>/dev/null
|
||||
}
|
||||
|
||||
echo "== must be UNREACHABLE: the host, the LAN, and the stack =="
|
||||
# 10.0.2.2 and 10.0.2.3 are SLIRP's aliases for the host's loopback and
|
||||
# the host's DNS forwarder. They are the closest thing to a back door
|
||||
# this design has, and the deny on 127.0.0.0/8 is what closes them.
|
||||
for target in \
|
||||
"10.0.2.2:22|SLIRP alias for the host's loopback (sshd)" \
|
||||
"10.0.2.2:3000|SLIRP alias for the host's loopback (Open WebUI)" \
|
||||
"192.168.1.10:22|this host on the LAN (sshd)" \
|
||||
"192.168.1.10:3000|this host on the LAN (Open WebUI)" \
|
||||
"192.168.1.10:7200|this host on the LAN (Gitea)" \
|
||||
"192.168.1.10:9443|this host on the LAN (Portainer)" \
|
||||
"192.168.1.1:80|the LAN gateway" \
|
||||
"172.17.0.1:11434|Ollama on the docker bridge" \
|
||||
"172.22.0.10:8000|the owuitools gateway" \
|
||||
"169.254.169.254:80|cloud metadata" ; do
|
||||
addr="${target%%|*}"; what="${target#*|}"
|
||||
host="${addr%:*}"; port="${addr##*:}"
|
||||
if reach "$host" "$port"; then fail "REACHED $addr - $what"; else pass "blocked $addr ($what)"; fi
|
||||
done
|
||||
|
||||
echo
|
||||
echo "== must be REACHABLE: the internet (or this suite proves nothing) =="
|
||||
for target in "1.1.1.1:443|Cloudflare DNS over TLS" "8.8.8.8:53|Google DNS" "1.1.1.1:80|a plain HTTP port"; do
|
||||
addr="${target%%|*}"; what="${target#*|}"
|
||||
host="${addr%:*}"; port="${addr##*:}"
|
||||
if reach "$host" "$port"; then pass "reachable $addr ($what)"; else fail "UNREACHABLE $addr - $what: the positive control failed, so the negatives above mean nothing"; fi
|
||||
done
|
||||
|
||||
echo
|
||||
echo "== name resolution and a real fetch =="
|
||||
if getent hosts archive.ubuntu.com >/dev/null 2>&1; then pass "DNS resolves (static 1.1.1.1, not SLIRP's forwarder)"; else fail "DNS does not resolve"; fi
|
||||
if timeout 20 curl -fsS -o /dev/null https://example.com/ 2>/dev/null; then pass "HTTPS to a public host works"; else fail "HTTPS to a public host failed"; fi
|
||||
|
||||
echo
|
||||
echo "== known-fail, documented: ICMP =="
|
||||
if timeout 5 ping -c1 -W2 8.8.8.8 >/dev/null 2>&1; then
|
||||
fail "ping WORKED - ping_group_range must have changed; update the docs and check_internet's note"
|
||||
else
|
||||
pass "ping fails as expected (host ping_group_range is '1 0'; use curl instead)"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "== no IPv6, and no neighbours on the LAN =="
|
||||
if ip -6 addr show scope global 2>/dev/null | grep -q inet6; then fail "the guest has a global IPv6 address"; else pass "no global IPv6"; fi
|
||||
neighbours=$(ip neigh show 2>/dev/null | grep -cvE '^\s*$|10\.0\.2\.' || true)
|
||||
if [ "${neighbours:-0}" -gt 0 ]; then fail "the guest can see $neighbours neighbour(s) outside its own SLIRP subnet"; else pass "no neighbours outside 10.0.2.0/24"; fi
|
||||
|
||||
echo
|
||||
if [ "$fails" -gt 0 ]; then echo "FAILED $fails"; exit 1; fi
|
||||
echo "the guest is isolated as designed"
|
||||
Reference in New Issue
Block a user