GL-8: BYO coexistence hardening (host-install v1.12.0) — F3/F6/F1
F3: the populated-host leaf guard now fires only when a FELHOM guest exists (felhom_guests, not `pct list`) — a fresh byo install on a host running only the OWNER's guests proceeds without --allow-new-leaf; the pin protection for real Felhom guests is unchanged (guard still fires on a Felhom guest). F6: byo preflight REFUSES (never mutates) when a foreign resolver is bound to :53, with remediation — Felhom needs the guest reachable by name and will not stop/mask the owner's DNS (the dnsmasq analog of the break-glass rule). Viktor's ruling: refuse+instruct only, no stop/mask path. ss failure degrades to a warn. Appliance mode untouched (the agent's lan_resolver owns :53). F1: uninstall now purges the agent config's .bak* siblings (one GL-6 residue held a live hub api_key), not just agent.json; WIPED statement updated. Harness +GL8-F3/F6/F1 static + GL8-F1b behavioural + GL8-F6 live (byo preflight refuses on felhom-pve's leftover dnsmasq — the real F6 scenario). Red-proofs RP-F3/F6/F1 each fail their invariant. 36/36 on felhom-pve; shellcheck clean; GL-2 Scenario-A + GL-4 Scenario-D regression green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
@@ -301,10 +301,56 @@ else
|
||||
verdict FAIL "GL4-INV no umount -l/-f, no mkfs/wipefs invocation on /mnt/felhom-drives"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "--- GL-8 static tier (BYO coexistence hardening) ---"
|
||||
|
||||
# GL8-F3: the populated-host leaf guard tests for a FELHOM guest (felhom_guests), NOT any guest
|
||||
# (pct list). Runtime behaviour is impractical to isolate (the guard is inline in step 5, gated on
|
||||
# the real state dir's leaf) — validated grep-level + red-proof here; the true behavioural proof is
|
||||
# GL-7's real byo install on a populated non-Felhom host.
|
||||
if grep -A2 'this host already has a Felhom guest' "$SCRIPT" >/dev/null 2>&1 \
|
||||
&& grep -B3 'this host already has a Felhom guest' "$SCRIPT" | grep -q '\[\[ -n "\$(felhom_guests)" \]\]' \
|
||||
&& ! grep -B3 'has no agent leaf to preserve' "$SCRIPT" | grep -q 'pct list .* | .*tail -n +2 | grep -q'; then
|
||||
verdict PASS "GL8-F3 leaf guard uses felhom_guests (not pct list — fires on Felhom guests only)"
|
||||
else
|
||||
verdict FAIL "GL8-F3 leaf guard uses felhom_guests (not pct list — fires on Felhom guests only)"
|
||||
fi
|
||||
|
||||
# GL8-F6: the byo :53 gate REFUSES (dies) with the remediation and mutates nothing — no
|
||||
# stop/mask/kill of a :53 service anywhere in the script.
|
||||
if grep -q 'a resolver is already bound to :53 on this host — Felhom needs the guest reachable' "$SCRIPT" \
|
||||
&& grep -B6 'a resolver is already bound to :53 on this host — Felhom needs' "$SCRIPT" | grep -q 'MODE" == "byo"' \
|
||||
&& ! grep -vE '^[[:space:]]*#' "$SCRIPT" | grep -E '(systemctl (stop|mask|disable)|kill|pkill).*(dnsmasq|:53|resolv)' >/dev/null; then
|
||||
verdict PASS "GL8-F6 byo :53 gate refuses+instructs, never mutates the owner's resolver"
|
||||
else
|
||||
verdict FAIL "GL8-F6 byo :53 gate refuses+instructs, never mutates the owner's resolver"
|
||||
fi
|
||||
|
||||
# GL8-F1(static): uninstall removes the agent config's .bak* siblings (not just agent.json).
|
||||
if grep -q '"${agent_cfg}".bak\*' "$SCRIPT"; then
|
||||
verdict PASS "GL8-F1 uninstall removes \${agent_cfg}.bak* (secret-bearing backups)"
|
||||
else
|
||||
verdict FAIL "GL8-F1 uninstall removes \${agent_cfg}.bak* (secret-bearing backups)"
|
||||
fi
|
||||
|
||||
# GL8-F1(behavioural): the exact glob-removal pattern the script uses, exercised in a temp dir —
|
||||
# both agent.json AND its .bak* siblings must go (a plain `rm -f agent.json` would leave the .bak).
|
||||
f1dir="$WORK/etc-felhom-agent"; mkdir -p "$f1dir"
|
||||
: > "$f1dir/agent.json"; : > "$f1dir/agent.json.bak-0.75.0"; : > "$f1dir/agent.json.bak-ceremony-2026-07-08"; : > "$f1dir/agent.json.bak-pre064"
|
||||
agent_cfg="$f1dir/agent.json"
|
||||
rm -f "$agent_cfg"
|
||||
for _cfgbak in "${agent_cfg}".bak*; do [[ -e "$_cfgbak" ]] && rm -f "$_cfgbak"; done
|
||||
rmdir "$f1dir" 2>/dev/null || true
|
||||
if [[ ! -e "$f1dir" ]]; then
|
||||
verdict PASS "GL8-F1b glob removal clears agent.json + every .bak* + the empty dir"
|
||||
else
|
||||
verdict FAIL "GL8-F1b glob removal clears agent.json + every .bak* + the empty dir" "residue: $(ls -A "$f1dir" 2>/dev/null | tr '\n' ' ')"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "--- PVE tier ---"
|
||||
if ! command -v pveum >/dev/null 2>&1 || [[ "$(id -u)" != 0 ]]; then
|
||||
verdict SKIP "PVE tier (C5 + Scenario A/B dry transcripts)" "needs a PVE host as root — run there"
|
||||
verdict SKIP "PVE tier (C5 + Scenario A/B dry transcripts + GL8-F6 live)" "needs a PVE host as root — run there"
|
||||
else
|
||||
# C5: byo with a bogus --acl-storages entry dies in preflight NAMING it (read-only: dies before
|
||||
# any hub contact/passphrase and before any mutation; state override active).
|
||||
@@ -313,6 +359,29 @@ else
|
||||
-- --customer-id t --mode byo --cores 4 --memory 8192 \
|
||||
--acl-storages "local definitely-not-a-storage" --dry-run
|
||||
|
||||
# GL8-F6 live: the byo :53 gate is behaviour-checked against whatever THIS host has on :53
|
||||
# (read-only preflight die; state override active). If a resolver is bound (e.g. felhom-pve's
|
||||
# leftover dnsmasq), byo preflight MUST refuse with the remediation; if :53 is free, it must pass
|
||||
# the gate and die LATER for another reason (never at :53). A PATH-shimmed `ss` proves both arms.
|
||||
mkdir -p "$WORK/f6shim"
|
||||
printf '#!/bin/sh\nexit 0\n' > "$WORK/f6shim/ss"; chmod 0755 "$WORK/f6shim/ss" # empty :53 output
|
||||
if ss -H -ltnup 'sport = :53' 2>/dev/null | grep -q .; then
|
||||
expect_die "GL8-F6 live: foreign :53 refused, byo" \
|
||||
"a resolver is already bound to :53 on this host — Felhom needs the guest reachable" \
|
||||
-- --customer-id t --mode byo --cores 4 --memory 8192 --acl-storages "local" --dry-run
|
||||
else
|
||||
verdict SKIP "GL8-F6 live: foreign :53 refused, byo" "nothing bound to :53 on this host — cannot exercise the refuse arm"
|
||||
fi
|
||||
# free-:53 arm (ss shimmed to report nothing): byo preflight passes the :53 gate → dies LATER,
|
||||
# never with the :53 message.
|
||||
out=$(FELHOM_INSTALL_STATE_DIR="$STATE_OVERRIDE_ENV" PATH="$WORK/f6shim:$PATH" \
|
||||
bash "$SCRIPT" --customer-id t --mode byo --cores 4 --memory 8192 --acl-storages "local" --dry-run 2>&1 || true)
|
||||
if [[ "$out" != *"already bound to :53"* ]]; then
|
||||
verdict PASS "GL8-F6 live: free :53 passes the gate (ss-shim empty)"
|
||||
else
|
||||
verdict FAIL "GL8-F6 live: free :53 passes the gate (ss-shim empty)" "died at :53 despite an empty ss"
|
||||
fi
|
||||
|
||||
# GL4 H-U: FULL uninstall dry transcript (Scenario A). Read-only: every mutation is dry-printed,
|
||||
# the typed confirm takes its dry branch, and the state override keeps the live state.json out.
|
||||
# Requires a Felhom guest to target — resolved from felhom_guests-style detection below.
|
||||
|
||||
Reference in New Issue
Block a user