Hub v0.158.0 code (built, NOT deployed): agent-only vouch, R-895 snapshot alarms, the hub's own 90-day access log (R-1140 A); installer --rotate-recovery-only (R-1141); 02/03/05/09 + break-glass
gates / gates (push) Successful in 6m45s
gates / gates (push) Successful in 6m45s
Hub: the R-120 golden guard judges only a CHANGED golden selection; offsite_snapshots_vanished / _unexplained (operator-only, hub-origin) on the 07:10 listing; internal/accesslog writes path-only lines to /data/access-log, 90 days. Installer: --rotate-recovery-only runs step 4b alone (rides the next installer tag). 03: the orphan check runs after a failed backup (agent 6545ccc). 02: the allow-list is empty, appdata_folders (controller v0.329.0). All red-proofed.
This commit is contained in:
1 parent
5a5486104a
commit
7d64038f02
27 files changed
+1813
-77
No files matched your search
@@ -153,18 +153,22 @@ else
|
||||
verdict FAIL "C6 byo PVE-major gate present (static grep)"
|
||||
fi
|
||||
|
||||
# INV-1: exactly ONE step_break_glass invocation, and it is gated on MODE == appliance.
|
||||
# (Comment lines and the function definition itself don't count.)
|
||||
# INV-1: exactly TWO step_break_glass invocations, each appliance-gated: the install's (gated on
|
||||
# MODE == appliance) and --rotate-recovery-only's (R-1141, gated on the agent config's
|
||||
# deployment_mode == appliance). (Comment lines and the function definition itself don't count.)
|
||||
inv_calls=$(grep -n 'step_break_glass' "$SCRIPT" \
|
||||
| grep -v -E '^[0-9]+:[[:space:]]*#' \
|
||||
| grep -v 'step_break_glass()' || true)
|
||||
inv_count=$(echo "$inv_calls" | grep -c 'step_break_glass' || true)
|
||||
if [[ "$inv_count" == "1" ]] \
|
||||
inv_bare=$(echo "$inv_calls" | grep -v 'should_skip break_glass' | cut -d: -f1 || true)
|
||||
if [[ "$inv_count" == "2" ]] \
|
||||
&& echo "$inv_calls" | grep -q 'should_skip break_glass' \
|
||||
&& grep -B4 'should_skip break_glass' "$SCRIPT" | grep -q 'MODE" == "appliance"'; then
|
||||
verdict PASS "INV-1 break-glass call site: exactly one, appliance-gated"
|
||||
&& grep -B4 'should_skip break_glass' "$SCRIPT" | grep -q 'MODE" == "appliance"' \
|
||||
&& [[ -n "$inv_bare" ]] \
|
||||
&& sed -n "$((inv_bare-1))p" "$SCRIPT" | grep -q 'ROTATE_CFG_MODE" == "appliance"'; then
|
||||
verdict PASS "INV-1 break-glass call sites: exactly two, both appliance-gated"
|
||||
else
|
||||
verdict FAIL "INV-1 break-glass call site: exactly one, appliance-gated" "count=$inv_count; calls: $(echo "$inv_calls" | tr '\n' ' ')"
|
||||
verdict FAIL "INV-1 break-glass call sites: exactly two, both appliance-gated" "count=$inv_count; calls: $(echo "$inv_calls" | tr '\n' ' ')"
|
||||
fi
|
||||
|
||||
# INV-2: chpasswd is unreachable outside step_break_glass's body (non-comment occurrences only).
|
||||
@@ -338,6 +342,91 @@ else
|
||||
verdict FAIL "GL8-F1 uninstall purges the agent config dir (every copy, R-275)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "--- R-1141 --rotate-recovery-only (step 4b alone) ---"
|
||||
# Runs anywhere: refusals die before any host access; the dry-run reads a FAKE agent config through the
|
||||
# test-only FELHOM_AGENT_CONFIG override and points the hub at a closed port. A chpasswd/systemctl/pct
|
||||
# PATH shim records any call — a dry run must make none of them.
|
||||
RK="FAKE-HOST-KEY-r1141-never-printed"
|
||||
mkdir -p "$WORK/r1141shim"
|
||||
for c in chpasswd pct qm pveum systemctl apt-get install; do
|
||||
printf '#!/bin/sh\necho "%s $*" >> "%s/r1141shim/calls"\nexit 1\n' "$c" "$WORK" > "$WORK/r1141shim/$c"
|
||||
chmod 0755 "$WORK/r1141shim/$c"
|
||||
done
|
||||
r1141_cfg() { # <file> <deployment_mode|-> <api_key>
|
||||
python3 - "$1" "$2" "$3" <<'PY'
|
||||
import json, sys
|
||||
f, mode, key = sys.argv[1:]
|
||||
d = {"hub": {"url": "http://127.0.0.1:9", "host_id": "h-test", "api_key": key}}
|
||||
if mode != "-":
|
||||
d["deployment_mode"] = mode
|
||||
json.dump(d, open(f, "w"))
|
||||
PY
|
||||
chmod 0600 "$1"
|
||||
}
|
||||
r1141_run() { # <config> <args...>
|
||||
local cfg="$1"; shift
|
||||
set +e
|
||||
out=$(FELHOM_INSTALL_STATE_DIR="$STATE_OVERRIDE_ENV" FELHOM_AGENT_CONFIG="$cfg" PATH="$WORK/r1141shim:$PATH" \
|
||||
bash "$SCRIPT" --rotate-recovery-only "$@" </dev/null 2>&1)
|
||||
rc=$?
|
||||
set -e
|
||||
}
|
||||
r1141_expect_die() { # <name> <msg> <config> <args...>
|
||||
local name="$1" msg="$2" cfg="$3"; shift 3
|
||||
r1141_run "$cfg" "$@"
|
||||
if [[ $rc -ne 0 && "$out" == *"$msg"* && "$out" != *"4b/8"* ]]; then
|
||||
verdict PASS "$name"
|
||||
else
|
||||
verdict FAIL "$name" "rc=$rc; wanted '$msg', no 4b/8; got: $(echo "$out" | tail -3 | tr '\n' ' ')"
|
||||
fi
|
||||
}
|
||||
r1141_cfg "$WORK/r1141-app.json" appliance "$RK"
|
||||
r1141_cfg "$WORK/r1141-byo.json" byo "$RK"
|
||||
r1141_cfg "$WORK/r1141-nomode.json" - "$RK"
|
||||
r1141_cfg "$WORK/r1141-nokey.json" appliance ""
|
||||
printf 'not json' > "$WORK/r1141-bad.json"
|
||||
r1141_expect_die "R1141-a --mode byo refused" "is not allowed in byo mode" "$WORK/r1141-app.json" --mode byo --dry-run
|
||||
r1141_expect_die "R1141-b with --uninstall refused" "--rotate-recovery-only runs alone" "$WORK/r1141-app.json" --uninstall --dry-run
|
||||
r1141_expect_die "R1141-c with --resume refused" "--rotate-recovery-only runs alone" "$WORK/r1141-app.json" --resume --dry-run
|
||||
r1141_expect_die "R1141-d config deployment_mode=byo refused" "deployment_mode=byo, not appliance" "$WORK/r1141-byo.json" --dry-run
|
||||
r1141_expect_die "R1141-e config without deployment_mode refused (fail-safe)" "deployment_mode=<absent>, not appliance" "$WORK/r1141-nomode.json" --dry-run
|
||||
r1141_expect_die "R1141-f config without a host key refused" "no hub.host_id/hub.api_key" "$WORK/r1141-nokey.json" --dry-run
|
||||
r1141_expect_die "R1141-g config not JSON refused" "agent config is not valid JSON" "$WORK/r1141-bad.json" --dry-run
|
||||
r1141_expect_die "R1141-h config missing refused" "agent config not readable" "$WORK/r1141-absent.json" --dry-run
|
||||
if [[ "$(id -u)" != 0 ]]; then
|
||||
r1141_expect_die "R1141-i a real run needs root" "must run as root" "$WORK/r1141-app.json"
|
||||
else
|
||||
verdict SKIP "R1141-i a real run needs root" "running as root — the refusal cannot be shown here"
|
||||
fi
|
||||
# The positive dry run: step 4b's dry line only, the host key never printed, no other step, no host
|
||||
# command, no state written.
|
||||
rm -f "$WORK/r1141shim/calls" "$STATE_OVERRIDE/state.json"
|
||||
r1141_run "$WORK/r1141-app.json" --dry-run
|
||||
r_ok=true; r_why=""
|
||||
[[ $rc -eq 0 ]] || { r_ok=false; r_why+="rc=$rc "; }
|
||||
for want in "mode=rotate-recovery-only" "4b/8 break-glass credential" \
|
||||
"PUT http://127.0.0.1:9/api/v1/hosts/h-test/recovery-credential" "a real run would stop here"; do
|
||||
[[ "$out" == *"$want"* ]] || { r_ok=false; r_why+="missing '$want' "; }
|
||||
done
|
||||
for bad in "$RK" "1/8" "2/8" "3/8" "4/8" "5/8" "6/8" "7/8" "8/8" "agent install" "PASSPHRASE" "retrieval passphrase"; do
|
||||
[[ "$out" != *"$bad"* ]] || { r_ok=false; r_why+="contains '$bad' "; }
|
||||
done
|
||||
[[ ! -s "$WORK/r1141shim/calls" ]] || { r_ok=false; r_why+="host commands called: $(tr '\n' ';' < "$WORK/r1141shim/calls") "; }
|
||||
[[ ! -e "$STATE_OVERRIDE/state.json" ]] || { r_ok=false; r_why+="state.json written "; }
|
||||
if $r_ok; then
|
||||
verdict PASS "R1141-j dry run: step 4b only, host key not printed, no host command, no state"
|
||||
else
|
||||
verdict FAIL "R1141-j dry run: step 4b only, host key not printed, no host command, no state" "$r_why"
|
||||
fi
|
||||
# Usage documents the flag (the real -h path).
|
||||
run_script -h
|
||||
if [[ $rc -eq 0 && "$out" == *"--rotate-recovery-only"* ]]; then
|
||||
verdict PASS "R1141-k usage documents --rotate-recovery-only"
|
||||
else
|
||||
verdict FAIL "R1141-k usage documents --rotate-recovery-only" "rc=$rc"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "--- F5 appliance guest auto-sizing (VALIDATION-n100) ---"
|
||||
# The auto-size log line is emitted right after mode validation (BEFORE any PVE/hub contact), so it
|
||||
|
||||
Reference in new issue
Block a user