feat(wake): W7 A10 idempotent installer + mosaic-wake.service (component-manifest, Gate-A, blank-reset retire, snapshot-guard, fail-closed install-validate) (#911)
All checks were successful
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful

Co-authored-by: jason.woltje <jason@diversecanvas.com>
Co-committed-by: jason.woltje <jason@diversecanvas.com>
This commit was merged in pull request #911.
This commit is contained in:
2026-07-26 04:19:09 +00:00
committed by Mos
parent 003cdaa1a6
commit 2378665eaf
9 changed files with 951 additions and 9 deletions

View File

@@ -271,6 +271,70 @@ echo "== B10: fresh beacon within SLO -> ALIVE (exit 0, no alarm) =="
[ ! -s "$ALARM_OUT" ] || fail_msg "B10: a fresh beacon must NOT fire an alarm"
) && ok
# ── W7 monitor-integration hardening (folded-in W6 observations, #910 review) ──
echo "== B11: staleness from monitor ingested_ts -> a far-future emit_ts STILL goes stale =="
(
H="$(fresh_home b11)"
export ALARM_OUT="$H/alarm.json"
export WAKE_BEACON_RECEIVED="$H/received.json"
export WAKE_ALARM_SINK_CMD="$CAPTURE_ALARM"
now="$(date +%s)"
# A host ships a FAR-FUTURE emit_ts (now + 100000s) to try to defer staleness.
# record stamps the monitor's own ingested_ts; check must use THAT, not emit_ts.
jq -cn --argjson ts "$((now + 100000))" \
'{kind:"wake-beacon", beacon_seq:9, emit_ts:$ts, host_id:"h", independence:"off-host", degraded:false}' \
| "$BEACON" record
# Simulate the monitor having received it 100s ago (ingested_ts backdated) while
# the host's emit_ts stays far in the future. Under emit_ts-based staleness this
# would read as ALIVE (age hugely negative); under receive-time it is STALE.
jq -c --argjson ing "$((now - 100))" '.ingested_ts = $ing' "$WAKE_BEACON_RECEIVED" >"$H/tmp" && mv "$H/tmp" "$WAKE_BEACON_RECEIVED"
out="$("$BEACON" check --slo-seconds 5 2>&1)"; rc=$?
[ "$rc" -eq 1 ] || fail_msg "B11: a far-future emit_ts must NOT defer staleness — receive-time governs (expected absence exit 1); got $rc [$out]"
echo "$out" | grep -qi 'ALARM FIRED' || fail_msg "B11: the receive-time-stale beacon must fire the absence alarm [$out]"
jq -e '.age_seconds >= 100' "$ALARM_OUT" >/dev/null 2>&1 || fail_msg "B11: staleness age must be measured from ingested_ts (>=100s), not emit_ts [$(cat "$ALARM_OUT" 2>/dev/null)]"
) && ok
echo "== B12: HMAC-verify at record -> a spoofed (bad-sig) beacon is REJECTED =="
if ! command -v openssl >/dev/null 2>&1; then
echo "SKIP: openssl not available" >&2
else
(
H="$(fresh_home b12)"
export WAKE_STATE_HOME="$H"
export MOSAIC_CREDENTIALS_FILE="$H/credentials.json"
export WAKE_BEACON_HMAC_KEY_NAME="beacon"
jq -cn --arg k "test-beacon-hmac-key-do-not-echo" '{wake:{hmac_keys:{"beacon":$k}}}' >"$MOSAIC_CREDENTIALS_FILE"
export WAKE_BEACON_INDEPENDENCE="off-host"
# A capturing sink that keeps the exact shipped (signed) beacon record.
SHIPPED="$H/shipped.json"
SINK="$H/sink.sh"; printf '#!/usr/bin/env bash\ncat >"%s"\n' "$SHIPPED" >"$SINK"; chmod +x "$SINK"
export WAKE_BEACON_SINK_CMD="$SINK"
export WAKE_BEACON_RECEIVED="$H/received.json"
"$BEACON" emit >/dev/null 2>&1 || fail_msg "B12: a signed emit must succeed with a by-name key"
jq -e '.beacon_envelope' "$SHIPPED" >/dev/null 2>&1 || fail_msg "B12: a configured key must produce a signed beacon_envelope [$(cat "$SHIPPED" 2>/dev/null)]"
# (a) the authentic signed beacon is ACCEPTED at record.
"$BEACON" record <"$SHIPPED" >/dev/null 2>&1 || fail_msg "B12: an authentic signed beacon must be accepted at record"
[ -s "$WAKE_BEACON_RECEIVED" ] || fail_msg "B12: the authentic beacon must be stored"
# (b) a SPOOFED beacon (fields altered, envelope lifted) is REJECTED.
rm -f "$WAKE_BEACON_RECEIVED"
spoof="$H/spoof.json"
jq -c '.beacon_seq = 99999 | .host_id = "attacker"' "$SHIPPED" >"$spoof"
out="$("$BEACON" record <"$spoof" 2>&1)"; rc=$?
[ "$rc" -ne 0 ] || fail_msg "B12: a spoofed beacon (altered fields) must be REJECTED at record; got rc=$rc [$out]"
echo "$out" | grep -qi 'spoofed beacon' || fail_msg "B12: the rejection must name the spoof [$out]"
[ ! -s "$WAKE_BEACON_RECEIVED" ] || fail_msg "B12: a rejected spoof must NOT be stored (dead-man clock not advanced)"
# (c) an UNSIGNED beacon is rejected when signing is configured.
unsigned="$H/unsigned.json"
jq -c 'del(.beacon_envelope)' "$SHIPPED" >"$unsigned"
out2="$("$BEACON" record <"$unsigned" 2>&1)"; rc2=$?
[ "$rc2" -ne 0 ] || fail_msg "B12: an unsigned beacon must be REJECTED when signing is configured; got rc=$rc2 [$out2]"
# beacon.sh must not inline the key material.
grep -qF "test-beacon-hmac-key-do-not-echo" "$BEACON" && fail_msg "B12: beacon.sh must NOT inline the HMAC key"
true
) && ok
fi
echo
if [ -s "$FAILFILE" ]; then
echo "wake beacon harness: FAILED ($(grep -c . "$FAILFILE") assertion(s))" >&2