#!/usr/bin/env bash
# tests/dc-snap-proxy/run-tests.sh -- guard for scripts/dc-snap-proxy.sh
# (D-135/D-107 RULING 2, 2026-07-31: the per-DC snap FORWARD PROXY, squid,
# CONNECT-only, destination-restricted).
#
# TWO HALVES, DELIBERATELY.
# STATIC (T1-T22): the identity values a silent edit would break -- both sites'
# utility .4, ruled edge gateways and MEASURED client CIDRs, the port that
# is NOT MAAS's 3128, stable-identity keying, service-owned net artifacts,
# the consumption keys, the store/CDN allowlist, and arg/site validation.
# BEHAVIORAL (T23-T49): `check` and `node` are DRIVEN, against a mktemp
# SYSROOT and fake curl/ss/systemctl/virsh/ip/dpkg on PATH. A grep cannot
# tell a gate that fails from one that merely cannot fail, and this script's
# whole point is being a GA-R6 gate: every FAIL path and every REFUSE path
# below has its own failing fixture.
#
# WHAT THE GREEN BELOW DOES *NOT* PROVE (say it plainly, GA-R1): no proxy has
# ever been installed, so no snap has been installed through one. This is a
# FIXTURE green. The real gates are `check <site>` on the rack and `node <site>`
# from a client, at apply time.
#
# Exit: 0 all pass | 1 any case failed. ASCII + LF.
set -uo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SCRIPT="$(cd "$HERE/../.." && pwd)/scripts/dc-snap-proxy.sh"
PASS=0; FAIL=0
ok(){ echo " PASS $1"; PASS=$((PASS+1)); }
no(){ echo " FAIL $1"; FAIL=$((FAIL+1)); }
[ -f "$SCRIPT" ] && ok "T1 script present" || { no "T1 script present"; echo; echo "dc-snap-proxy: $PASS passed, $FAIL failed"; exit 1; }
bash -n "$SCRIPT" 2>/dev/null && ok "T2 bash -n clean" || no "T2 bash -n clean"
# ===========================================================================
# STATIC -- identity values and structural invariants.
# ===========================================================================
# T3-T5: dc0 row. The utility .4 is the address the deploy will be pointed at;
# the edge gw is the proxy's only upstream egress; CLIENT_CIDR is the MEASURED
# source the charm CONTAINERS use toward .4 (metal-admin, NOT the metal-internal
# address `juju status` prints -- that misreading would deny every client).
D0="$(awk '/^ dc0\)/{f=1} f{print} f&&/^ ;;/{exit}' "$SCRIPT")"
grep -q 'LISTEN_CIDR="10.12.8.4/22"' <<<"$D0" \
&& grep -q 'UTIL_NET="vr1-dc0-metal-admin"' <<<"$D0" \
&& ok "T3 dc0 LISTEN 10.12.8.4/22 on metal-admin (D-134 utility .4)" \
|| no "T3 dc0 LISTEN 10.12.8.4/22 on metal-admin (D-134 utility .4)"
grep -q 'EDGE_GW="10.12.4.1"' <<<"$D0" \
&& ok "T4 dc0 edge gw 10.12.4.1" || no "T4 dc0 edge gw 10.12.4.1"
grep -q 'CLIENT_CIDR="10.12.8.0/22"' <<<"$D0" \
&& ok "T5 dc0 client ACL is metal-admin 10.12.8.0/22 (measured container source)" \
|| no "T5 dc0 client ACL is metal-admin 10.12.8.0/22 (measured container source)"
# T6-T8: dc1 row -- values from lib-net.sh's own vr1-dc1 arm, never mirrored
# from dc0 by symmetry (the lib-hosts.sh scar).
D1="$(awk '/^ dc1\)/{f=1} f{print} f&&/^ ;;/{exit}' "$SCRIPT")"
grep -q 'LISTEN_CIDR="10.12.68.4/22"' <<<"$D1" \
&& grep -q 'UTIL_NET="vr1-dc1-metal-admin"' <<<"$D1" \
&& ok "T6 dc1 LISTEN 10.12.68.4/22 on metal-admin (utility .4)" \
|| no "T6 dc1 LISTEN 10.12.68.4/22 on metal-admin (utility .4)"
grep -q 'EDGE_GW="10.12.64.1"' <<<"$D1" \
&& ok "T7 dc1 edge gw 10.12.64.1" || no "T7 dc1 edge gw 10.12.64.1"
grep -q 'CLIENT_CIDR="10.12.68.0/22"' <<<"$D1" \
&& ok "T8 dc1 client ACL is metal-admin 10.12.68.0/22" \
|| no "T8 dc1 client ACL is metal-admin 10.12.68.0/22"
# T9: the port. 3128 AND 8000 are MAAS's squid, WILDCARD-bound on BOTH racks
# (measured); 3142 is dc1's apt-cacher-ng; 80 is dc0's mirror nginx. A silent
# move to 3128 would produce a squid that cannot bind, on both DCs.
grep -q 'PROXY_PORT="3129"' "$SCRIPT" \
&& ! grep -qE 'PROXY_PORT="(3128|8000|3142|80)"' "$SCRIPT" \
&& ok "T9 proxy port pinned 3129 (3128/8000 are MAAS's squid, 3142 is acng)" \
|| no "T9 proxy port pinned 3129 (3128/8000 are MAAS's squid, 3142 is acng)"
# T10: stable-identity keying -- bridge from the libvirt net NAME, no virbrN.
# Scoped to NON-COMMENT lines on purpose: the site rows quote the MEASURED
# `ip -4 -o addr` and `ip route show default` output, which names virbr2/virbr5/
# virbr6, and deleting that evidence to satisfy a grep would destroy the
# provenance the values rest on. What must not exist is an EXECUTED literal.
CODE="$(grep -vE '^[[:space:]]*#' "$SCRIPT")"
grep -q 'net-dumpxml' <<<"$CODE" && ! grep -qE 'virbr[0-9]' <<<"$CODE" \
&& ok "T10 bridge resolved from net name; no EXECUTED virbrN literal" \
|| no "T10 bridge resolved from net name; no EXECUTED virbrN literal"
# T11: this service OWNS its net layer and NAMES it for itself. The D-135
# lesson: on a DC running only the mirror (dc0) or only the cache proxy (dc1),
# that one's documented teardown must not be able to kill this proxy.
grep -q 'snap-proxy-net.service' "$SCRIPT" \
&& grep -q 'snap-proxy-net-apply' "$SCRIPT" \
&& ! grep -qE '(NET_UNIT|NET_HELPER|RESOLVED_DROPIN)=.*(mirror|cache-proxy)' "$SCRIPT" \
&& grep -qE 'ip addr replace' "$SCRIPT" && grep -qE 'ip route replace default' "$SCRIPT" \
&& ok "T11 net layer OWNED here and named for THIS service" \
|| no "T11 net layer OWNED here and named for THIS service"
# T11b: no OPERATIVE referral to the other two services' installers. Scoped to
# NON-COMMENT lines: the header's account of WHY the coupling is forbidden cites
# them, and deleting that history to satisfy a grep would lose the reason.
grep -vE '^[[:space:]]*#' "$SCRIPT" | grep -qE '(dc-mirror|dc-cache-proxy)\.sh install' \
&& no "T11b no OPERATIVE dc-mirror/dc-cache-proxy referral (header history exempt)" \
|| ok "T11b no OPERATIVE dc-mirror/dc-cache-proxy referral (header history exempt)"
# T12: idempotent verbs only, so this net unit and the mirror's / cache proxy's
# coexist on a rack running two of them instead of fighting.
! grep -qE 'ip (addr|route) add ' "$SCRIPT" \
&& ok "T12 net writes use idempotent replace (safe to coexist)" \
|| no "T12 net writes use idempotent replace (safe to coexist)"
# T13: MEASURED/RULED tag discipline on every site row (hard rule 2).
MR="$(grep -cE 'MEASURED/RULED' "$SCRIPT")"
[ "$MR" -ge 2 ] && ok "T13 both site rows carry a MEASURED/RULED tag" \
|| no "T13 both site rows carry a MEASURED/RULED tag (found $MR)"
# T14: install requires root.
grep -q 'install requires root' "$SCRIPT" && ok "T14 install requires root" \
|| no "T14 install requires root"
# T15: consumption documents BOTH snap keys, and warns that the store-proxy trio
# is a DIFFERENT product (snapd rejects proxy.store without a signed assertion).
grep -q 'snap-http-proxy=http://' "$SCRIPT" \
&& grep -q 'snap-https-proxy=http://' "$SCRIPT" \
&& grep -qE 'snap-store-proxy.*EMPTY|EMPTY.*snap-store' "$SCRIPT" \
&& ok "T15 documents both snap keys + leaves the store-proxy trio empty" \
|| no "T15 documents both snap keys + leaves the store-proxy trio empty"
# T16: the destination allowlist must carry the API host AND a CDN wildcard.
# snapd uses ONE http client for the API and the DOWNLOAD, so an allowlist with
# only api.snapcraft.io authenticates and then fails every download.
# Scoped to the STORE_DOMAINS VALUE, not to the file: the header discusses every
# one of these hosts in prose, so a file-wide grep would stay green after the
# allowlist itself was narrowed. That near-miss is the "citation is an existence
# claim; only its content is evidence" class.
SD="$(grep -E '^STORE_DOMAINS=' "$SCRIPT")"
grep -q 'api\.snapcraft\.io' <<<"$SD" \
&& grep -q '\.snapcraftcontent\.com' <<<"$SD" \
&& grep -q 'login\.ubuntu\.com' <<<"$SD" \
&& ok "T16 allowlist VALUE carries the store API, login and the CDN wildcard" \
|| no "T16 allowlist VALUE carries the store API, login and the CDN wildcard"
# T17: the generated squid config binds the LISTEN address SPECIFICALLY. A
# wildcard bind would collide with MAAS's squid and would also expose the proxy
# on the transit and provider legs.
grep -q 'http_port ${LISTEN}:${PROXY_PORT}' "$SCRIPT" \
&& ok "T17 squid binds LISTEN:PORT specifically, not wildcard" \
|| no "T17 squid binds LISTEN:PORT specifically, not wildcard"
# T18: the PACKAGED squid.service is stood down at install AND asserted by check.
# Its squid.conf pins http_port 3128 (MAAS holds it), so it can only crash-loop.
# The install half is scoped to do_install's BODY: `check`'s failure message also
# tells the operator to run that command, so a file-wide grep would stay green
# after install stopped doing it.
INSTALL_BODY="$(awk '/^do_install\(\) \{/{f=1} f{print} f&&/^\}/{exit}' "$SCRIPT")"
grep -q 'systemctl disable --now squid' <<<"$INSTALL_BODY" \
&& grep -q 'PACKAGED squid.service is ACTIVE' "$SCRIPT" \
&& ok "T18 packaged squid.service disabled BY install + asserted by check" \
|| no "T18 packaged squid.service disabled BY install + asserted by check"
# T19: install PARSES the config before enabling anything.
grep -q 'squid -k parse -f' "$SCRIPT" \
&& ok "T19 install parses the squid config before enabling" \
|| no "T19 install parses the squid config before enabling"
# T20: install never apt-installs -- it fails loud with the exact line instead,
# so a package install stays operator-visible (the dc-mirror/dc-cache-proxy
# pattern). The first grep must match an INVOCATION (a command at the start of a
# statement), never the guidance string, which contains the same words.
# Double-quoted strings are blanked first, for the same reason as T50: the
# guidance message legitimately CONTAINS `apt-get update && apt-get install -y
# squid`, and a naive invocation regex matches inside it.
if grep -vE '^[[:space:]]*#' "$SCRIPT" | sed 's/"[^"]*"/""/g' \
| grep -qE '(^|;|&&|\|\|)[[:space:]]*(sudo[[:space:]]+)?apt(-get)?[[:space:]]+install'; then
no "T20 install never apt-installs; it prints the line for the operator"
elif grep -q 'run: apt-get update && apt-get install -y squid' "$SCRIPT"; then
ok "T20 install never apt-installs; it prints the line for the operator"
else
no "T20 install never apt-installs; it prints the line for the operator"
fi
# T21-T22: arg / site validation.
out="$(bash "$SCRIPT" bogus dc0 2>&1)"; rc=$?
[ "$rc" = "2" ] && grep -q 'usage:' <<<"$out" \
&& ok "T21 bad mode -> usage, exit 2" || no "T21 bad mode -> usage, exit 2"
bash "$SCRIPT" check zz >/dev/null 2>&1; [ "$?" = "2" ] \
&& ok "T22 unknown site -> exit 2" || no "T22 unknown site -> exit 2"
# ===========================================================================
# BEHAVIORAL -- drive `check dc0` and `node dc0` against a fake world.
# ===========================================================================
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
FB="$TMP/bin"; mkdir -p "$FB"
SR="$TMP/sysroot"
cat > "$FB/curl" <<'FAKE'
#!/usr/bin/env bash
# fake curl: honours -o <file>, writes %{http_code} to stdout and any error text
# to stderr, and exits like the real one. Behaviour is selected per TARGET URL,
# because check makes three probes with three different meanings.
out="/dev/null"; url=""
while [ $# -gt 0 ]; do
case "$1" in
-o) out="$2"; shift 2 ;;
-w|-x|-m|-H) shift 2 ;;
-*) shift ;;
*) url="$1"; shift ;;
esac
done
store_body() { # $1 = which fields to include
case "$1" in
full) printf '{"channel-map":[{"download":{"sha3-384":"deadbeef","size":1}}]}\n' > "$out" ;;
nochan) printf '{"result":"error","message":"nope"}\n' > "$out" ;;
nosha) printf '{"channel-map":[{"download":{"size":1}}]}\n' > "$out" ;;
esac
}
deny_403() { echo "curl: (56) CONNECT tunnel failed, response 403" >&2; printf '000'; exit 56; }
case "$url" in
*api.snapcraft.io*)
case "$(cat "$FAKEDIR/curl.store")" in
ok) store_body full; printf '200' ;;
nochan) store_body nochan; printf '200' ;;
nosha) store_body nosha; printf '200' ;;
http404) printf 'not found\n' > "$out"; printf '404' ;;
unreach) echo "curl: (7) Failed to connect to 10.12.8.4 port 3129" >&2; printf '000'; exit 7 ;;
denied) deny_403 ;;
esac ;;
*snapcraftcontent.com*)
case "$(cat "$FAKEDIR/curl.cdn")" in
ok) printf '403' ;;
refused) deny_403 ;;
weird) echo "curl: (35) TLS connect error" >&2; printf '000'; exit 35 ;;
esac ;;
*)
case "$(cat "$FAKEDIR/curl.deny")" in
denied) deny_403 ;;
served) printf '200' ;;
weird) echo "curl: (28) Operation timed out" >&2; printf '000'; exit 28 ;;
esac ;;
esac
FAKE
cat > "$FB/ss" <<'FAKE'
#!/usr/bin/env bash
case "$(cat "$FAKEDIR/ss.mode")" in
listen) printf 'State Recv-Q Send-Q Local Address:Port Peer Address:Port\nLISTEN 0 256 10.12.8.4:3129 0.0.0.0:* \n' ;;
wildcard) printf 'State Recv-Q Send-Q Local Address:Port Peer Address:Port\nLISTEN 0 256 *:3129 0.0.0.0:* \n' ;;
none) printf 'State Recv-Q Send-Q Local Address:Port Peer Address:Port\n' ;;
esac
FAKE
cat > "$FB/systemctl" <<'FAKE'
#!/usr/bin/env bash
# modes: units.mode = enabled|disabled (our two units)
# pkgsquid.mode = off|active|enabled
verb="$1"; shift
quiet=0; [ "${1:-}" = "--quiet" ] && { quiet=1; shift; }
unit="${1:-}"
u="$(cat "$FAKEDIR/units.mode")"; p="$(cat "$FAKEDIR/pkgsquid.mode")"
case "$unit" in
squid|squid.service)
case "$verb" in
is-enabled) [ "$p" = "enabled" ] && { echo enabled; exit 0; }; echo disabled; exit 1 ;;
is-active) [ "$p" = "active" ] && exit 0 || exit 3 ;;
esac ;;
*snap-proxy.service|*snap-proxy-net.service)
case "$verb" in
is-enabled) [ "$u" = "enabled" ] && { echo enabled; exit 0; }; echo disabled; exit 1 ;;
is-active) [ "$u" = "enabled" ] && exit 0 || exit 3 ;;
esac ;;
esac
exit 0
FAKE
cat > "$FB/virsh" <<'FAKE'
#!/usr/bin/env bash
case "$(cat "$FAKEDIR/virsh.mode")" in
ok) printf "<network>\n <bridge name='virbrTEST' stp='on' delay='0'/>\n</network>\n" ;;
missing) exit 1 ;;
esac
FAKE
cat > "$FB/ip" <<'FAKE'
#!/usr/bin/env bash
args="$*"
case "$args" in
*"addr show dev"*)
case "$(cat "$FAKEDIR/ip.addr")" in
ok) printf '7: virbrTEST inet 10.12.8.4/22 brd 10.12.11.255 scope global secondary virbrTEST\n' ;;
missing) : ;;
esac ;;
*"route show default"*)
case "$(cat "$FAKEDIR/ip.route")" in
ok) printf 'default via 10.12.4.1 dev virbrX proto static\n' ;;
missing) : ;;
esac ;;
esac
FAKE
cat > "$FB/dpkg" <<'FAKE'
#!/usr/bin/env bash
[ "$(cat "$FAKEDIR/dpkg.mode")" = "installed" ] && exit 0 || exit 1
FAKE
chmod +x "$FB"/*
# Lay down a SYSROOT whose five artifacts are byte-identical to what the script
# generates -- by asking THE SCRIPT ITSELF to render them, never by copying its
# text into this file. A hand-copied expectation drifts silently; this cannot.
# Everything above the final MODE dispatch is the script's library half: sourcing
# it with `set -- check dc0` yields the real site row and the real generators.
LIB="$TMP/lib.sh"
LASTCASE="$(grep -n '^case "\$MODE" in$' "$SCRIPT" | tail -1 | cut -d: -f1)"
if [ -z "$LASTCASE" ]; then
no "T-lib could not locate the MODE dispatch -- behavioral cases cannot run"
echo; echo "dc-snap-proxy: $PASS passed, $FAIL failed"; exit 1
fi
head -n "$((LASTCASE-1))" "$SCRIPT" > "$LIB"
render() { # render <gen_fn>
env DC_SNAP_PROXY_SYSROOT="$SR" LIB="$LIB" GENFN="$1" bash -c '
set -- check dc0
. "$LIB"
"$GENFN"
' _
}
seed_sysroot() {
rm -rf "$SR"
mkdir -p "$SR/etc/squid" "$SR/etc/systemd/system" "$SR/etc/systemd/resolved.conf.d" \
"$SR/usr/local/sbin" "$SR/etc"
render gen_squid_conf > "$SR/etc/squid/dc0-snap-proxy.conf"
render gen_proxy_unit > "$SR/etc/systemd/system/dc0-snap-proxy.service"
render gen_net_helper > "$SR/usr/local/sbin/dc0-snap-proxy-net-apply"
render gen_net_unit > "$SR/etc/systemd/system/dc0-snap-proxy-net.service"
render gen_resolved_dropin > "$SR/etc/systemd/resolved.conf.d/dc0-snap-proxy.conf"
printf 'PATH="/usr/bin:/bin"\nhttps_proxy=http://10.12.8.4:3129\nhttp_proxy=http://10.12.8.4:3129\n' \
> "$SR/etc/environment"
}
set_modes() { # all-green defaults
printf 'ok' > "$TMP/curl.store"
printf 'ok' > "$TMP/curl.cdn"
printf 'denied' > "$TMP/curl.deny"
printf 'listen' > "$TMP/ss.mode"
printf 'enabled' > "$TMP/units.mode"
printf 'off' > "$TMP/pkgsquid.mode"
printf 'ok' > "$TMP/virsh.mode"
printf 'ok' > "$TMP/ip.addr"
printf 'ok' > "$TMP/ip.route"
printf 'installed' > "$TMP/dpkg.mode"
}
run_mode() { # run_mode <check|node> <want_rc> <regex> <label>
local mode="$1" want="$2" rx="$3" label="$4" out rc
out="$(env FAKEDIR="$TMP" DC_SNAP_PROXY_SYSROOT="$SR" PATH="$FB:$PATH" \
bash "$SCRIPT" "$mode" dc0 2>&1)"; rc=$?
if [ "$rc" = "$want" ] && grep -qE "$rx" <<<"$out"; then ok "$label"
else no "$label (rc=$rc want=$want)"; sed 's/^/ /' <<<"$out" | head -8; fi
}
# --- T23: the all-green baseline. If this cannot pass, nothing below means
# anything -- a suite whose green case is broken proves only that it is red.
seed_sysroot; set_modes
run_mode check 0 'dc-snap-proxy check \(dc0\): PASS' "T23 check: fully-built rack -> PASS"
# --- T24-T25: artifacts asserted on CONTENT, not existence (the dc-mirror.sh
# false-green class, fixed 2026-07-27).
seed_sysroot; set_modes
printf '# hand-edited on the rack\n' >> "$SR/etc/squid/dc0-snap-proxy.conf"
run_mode check 1 'DIFFERS from generated content' "T24 check: hand-edited squid.conf -> FAIL (content, not existence)"
seed_sysroot; set_modes
rm -f "$SR/etc/systemd/system/dc0-snap-proxy-net.service"
run_mode check 1 'snap-proxy-net.service absent' "T25 check: missing owned net unit -> FAIL"
# --- T26-T27: unit state, including the packaged squid that can only crash-loop.
seed_sysroot; set_modes; printf 'disabled' > "$TMP/units.mode"
run_mode check 1 'not enabled \(it would not survive a reboot\)' "T26 check: units not enabled -> FAIL"
seed_sysroot; set_modes; printf 'active' > "$TMP/pkgsquid.mode"
run_mode check 1 'PACKAGED squid.service is ACTIVE' "T27 check: packaged squid running -> FAIL"
seed_sysroot; set_modes; printf 'enabled' > "$TMP/pkgsquid.mode"
run_mode check 1 'PACKAGED squid.service is ENABLED' "T27b check: packaged squid enabled -> FAIL"
# --- T28-T29: the listener. A WILDCARD bind is not "close enough": it collides
# with MAAS's squid and exposes the proxy on the transit and provider legs.
seed_sysroot; set_modes; printf 'wildcard' > "$TMP/ss.mode"
run_mode check 1 'bound WILDCARD, not on 10.12.8.4' "T28 check: wildcard bind -> FAIL (not accepted)"
seed_sysroot; set_modes; printf 'none' > "$TMP/ss.mode"
run_mode check 1 'nothing listening on 10.12.8.4:3129' "T29 check: no listener -> FAIL"
# --- T30-T32: the net layer's live effect, and the REFUSE that separates
# "the alias is absent" from "I could not look".
seed_sysroot; set_modes; printf 'missing' > "$TMP/ip.addr"
run_mode check 1 'absent on the metal-admin bridge' "T30 check: LISTEN alias missing -> FAIL"
seed_sysroot; set_modes; printf 'missing' > "$TMP/virsh.mode"
run_mode check 3 'did not resolve to a bridge -- the alias is UNKNOWN, not absent' \
"T31 check: unresolvable bridge -> REFUSE (rc 3, not 0)"
seed_sysroot; set_modes; printf 'missing' > "$TMP/ip.route"
run_mode check 1 'no upstream egress' "T32 check: no default route -> FAIL"
# --- T33-T36: the ALLOW-direction probe, asserted on CONTENT. A 200 is not
# evidence: a proxy error page carries one too.
seed_sysroot; set_modes; printf 'nochan' > "$TMP/curl.store"
run_mode check 1 'no "channel-map"' "T33 check: 200 without channel-map -> FAIL (content, not status)"
seed_sysroot; set_modes; printf 'nosha' > "$TMP/curl.store"
run_mode check 1 'no sha3-384 download digest' "T34 check: store body without a digest -> FAIL"
seed_sysroot; set_modes; printf 'http404' > "$TMP/curl.store"
run_mode check 1 'returned HTTP 404 through the proxy' "T35 check: store 404 -> FAIL"
seed_sysroot; set_modes; printf 'unreach' > "$TMP/curl.store"
run_mode check 3 "unreachable is not 'nothing there'" "T36 check: proxy unreachable -> REFUSE (rc 3)"
# --- T37-T38: the CDN tunnel. snapd downloads through the SAME proxy, so an
# allowlist that names only the API host would authenticate and then fail.
seed_sysroot; set_modes; printf 'refused' > "$TMP/curl.cdn"
run_mode check 1 'allowlist is too narrow' "T37 check: CDN tunnel refused -> FAIL"
seed_sysroot; set_modes; printf 'weird' > "$TMP/curl.cdn"
run_mode check 3 'neither an allowed tunnel nor a proxy denial' "T38 check: unrecognised CDN result -> REFUSE"
# --- T39-T40: the DENY direction. This is the assertion that proves the
# destination allowlist is IN FORCE rather than merely written down.
seed_sysroot; set_modes; printf 'served' > "$TMP/curl.deny"
run_mode check 1 'OPEN forward proxy on the node plane' "T39 check: non-allowlisted host SERVED -> FAIL (open proxy)"
seed_sysroot; set_modes; printf 'weird' > "$TMP/curl.deny"
run_mode check 3 'allowlist state is UNKNOWN' "T40 check: unrecognised deny result -> REFUSE"
# --- T41-T42: package absence FAILS (an observed absence), tool absence REFUSES
# ("could not look" is never "nothing there").
seed_sysroot; set_modes; printf 'absent' > "$TMP/dpkg.mode"
run_mode check 1 'squid not installed' "T41 check: squid package absent -> FAIL"
# T42 needs a PATH with NO curl AT ALL. Deleting the fake is not enough -- the
# real /usr/bin/curl is still on PATH and the script would then probe a dead
# 10.12.8.4:3129 and REFUSE for the WRONG REASON, with the right exit code. That
# near-miss is exactly the "would my instrument look like this if it were broken"
# tell, so the case builds a closed PATH: the fakes minus curl, plus explicit
# symlinks to the real utilities the script legitimately uses.
NB="$TMP/nocurl"; mkdir -p "$NB"
for f in "$FB"/*; do
[ "$(basename "$f")" = "curl" ] || ln -sf "$f" "$NB/"
done
for t in bash env diff mktemp grep head tail rm sed cat cut awk tr; do
p="$(command -v "$t" 2>/dev/null)"; [ -n "$p" ] && ln -sf "$p" "$NB/$t"
done
seed_sysroot; set_modes
out="$(env FAKEDIR="$TMP" DC_SNAP_PROXY_SYSROOT="$SR" PATH="$NB" bash "$SCRIPT" check dc0 2>&1)"; rc=$?
if [ "$rc" = "3" ] && grep -q 'curl is not present' <<<"$out"; then
ok "T42 check: no curl on PATH -> REFUSE (rc 3, not 0)"
else
no "T42 check: no curl on PATH -> REFUSE (rc 3, not 0) (rc=$rc want=3)"
sed 's/^/ /' <<<"$out" | tail -6
fi
# ===========================================================================
# `node` -- the CLIENT-side gate. `check` asserts rack-side facts a container
# cannot satisfy, so pointing a client at `check` would fail for reasons that
# say nothing about client reachability. These cases drive the client half.
# ===========================================================================
seed_sysroot; set_modes
run_mode node 0 'dc-snap-proxy node \(dc0\): PASS' "T43 node: reachable + denied + env set -> PASS"
# T44 the failure the measurement in the header exists to prevent: a client whose
# SOURCE address is outside CLIENT_CIDR is refused BY THE PROXY, and that must
# read as a client/ACL problem, not as "the store is down".
seed_sysroot; set_modes; printf 'denied' > "$TMP/curl.store"
run_mode node 1 "source address is outside 10.12.8.0/22" \
"T44 node: proxy denies this client -> FAIL naming the ACL, not the store"
seed_sysroot; set_modes; printf 'unreach' > "$TMP/curl.store"
run_mode node 3 "unreachable is not 'nothing there'" "T45 node: proxy unreachable -> REFUSE (rc 3)"
seed_sysroot; set_modes; printf 'nochan' > "$TMP/curl.store"
run_mode node 1 'no "channel-map"' "T46 node: 200 without channel-map -> FAIL"
seed_sysroot; set_modes; printf 'served' > "$TMP/curl.deny"
run_mode node 1 'OPEN forward proxy on the node plane' "T47 node: allowlist not in force from the client -> FAIL"
# T48-T49: snapd's own view, read WITHOUT root. juju shells out `snap set system
# proxy.*` and snapd writes those to /etc/environment, so this is the artifact --
# and `snap get system proxy` would have needed root and broken the no-root rule.
seed_sysroot; set_modes
grep -v 'https_proxy' "$SR/etc/environment" > "$SR/etc/environment.new" && mv "$SR/etc/environment.new" "$SR/etc/environment"
run_mode node 1 'the juju keys have not reached this client' "T48 node: no https_proxy in /etc/environment -> FAIL"
seed_sysroot; set_modes
printf 'https_proxy=http://10.12.99.99:3128\n' > "$SR/etc/environment"
run_mode node 1 'points somewhere ELSE' "T49 node: https_proxy points at another proxy -> FAIL"
# T50: `node` must stay CLIENT-satisfiable -- no root, no systemctl, no virsh, no
# generated-file diffs. That is the whole reason `check` cannot serve a client.
# Double-quoted strings are BLANKED before the grep: the assertion is about CODE,
# not prose. The body legitimately tells an operator to measure a client's source
# with `ip route get <listen>` inside a failure MESSAGE, and rewording that hint
# to dodge a regex would be gaming the test rather than satisfying it.
BODY="$(awk '/^do_node\(\) \{/{f=1} f{print} f&&/^\}/{exit}' "$SCRIPT")"
BODY_CODE="$(sed 's/"[^"]*"/""/g' <<<"$BODY")"
if grep -qE 'systemctl|virsh|util_bridge|id -u|ip (addr|route)|diff -q|dpkg' <<<"$BODY_CODE"; then
no "T50 node body is client-satisfiable only (no rack-side calls)"
else ok "T50 node body is client-satisfiable only (no rack-side calls)"; fi
echo; echo "dc-snap-proxy: $PASS passed, $FAIL failed"
[ "$FAIL" -eq 0 ] && exit 0 || exit 1