feat(wake): W7 (vi) fold in W6 monitor-integration hardening in beacon.sh
Some checks failed
ci/woodpecker/pr/ci Pipeline failed
Some checks failed
ci/woodpecker/pr/ci Pipeline failed
Separate, clearly-labeled commit for observation set (vi) from the #910 review. The HMAC-verify half is a non-trivial blast radius (emit must sign, record must verify-and-bind), so it is isolated here rather than expanding the installer diff. Both changes are additive and backward-compatible (the legacy unsigned path and emit_ts fallback are preserved when no key is configured). (vi-a) Monitor-side ingested_ts: `record` stamps the monitor's own receive-time, and `check` computes staleness from ingested_ts (falling back to emit_ts only for a legacy record). A host shipping a far-future emit_ts to defer its own staleness can no longer fool the off-host dead-man clock. (vi-b) Beacon HMAC-verify at record: when a key name is configured (WAKE_BEACON_HMAC_KEY_NAME / WAKE_HMAC_KEY_NAME), `emit` signs the beacon core via the existing sign.sh sign-digest path and `record` verifies via sign.sh verify AND binds the envelope to this beacon (content_hash == sha256(core), seq/emit_ts match). A spoofed or unsigned beacon is REJECTED fail-loud and never advances the received clock. Key is by-name only — beacon.sh inlines no key material. test-wake-beacon.sh extended with B11 (ingested_ts staleness) and B12 (HMAC-verify + spoof/unsigned rejection); test-wake-install.sh gains I7/I8 for the same invariants. Both harnesses shown red-first under targeted mutation, then green. Part of #892 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0158NZqN2n2ymKFeJAZ4GUCb
This commit is contained in:
@@ -18,10 +18,11 @@
|
||||
# wake-install.sh inlines NO secret/endpoint (v)
|
||||
# I6 reset->verify->retire lifecycle — overlap leaves the legacy timer running;
|
||||
# only a §4-vector pass retires it, and only snapshot-guarded (iii)
|
||||
#
|
||||
# The W6 monitor-integration hardening folded into beacon.sh (ingested_ts
|
||||
# staleness + beacon HMAC-verify at record) is exercised by test-wake-beacon.sh
|
||||
# and appended here as I7/I8 in the same change that lands that beacon hardening.
|
||||
# I7 ingested_ts staleness — staleness is computed from the monitor's
|
||||
# receive-time, so a host shipping a FAR-FUTURE emit_ts STILL goes stale (vi-a)
|
||||
# I8 beacon HMAC-verify at record — a spoofed (bad-sig) beacon is REJECTED (vi-b)
|
||||
# (I7/I8 exercise the W6 monitor-integration hardening folded into beacon.sh; the
|
||||
# same invariants are also asserted in depth by test-wake-beacon.sh B11/B12.)
|
||||
#
|
||||
# Isolated per-group XDG/systemd/target dirs; no live network, no operator queue,
|
||||
# no real systemd manager touched (the blank-reset verify uses the deterministic
|
||||
@@ -34,6 +35,7 @@ set -uo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
WI="$SCRIPT_DIR/wake-install.sh"
|
||||
BEACON="$SCRIPT_DIR/beacon.sh"
|
||||
FRAMEWORK_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
command -v jq >/dev/null 2>&1 || { echo "SKIP: jq not available" >&2; exit 0; }
|
||||
@@ -198,6 +200,43 @@ echo "== I6: reset->verify->retire — overlap keeps legacy running; only §4-ve
|
||||
[ -f "$SNAP/$TIMER" ] || fail_msg "I6: the snapshot must survive the retire"
|
||||
) && ok
|
||||
|
||||
# ── W6 monitor-integration hardening folded into beacon.sh (vi) ────────────────
|
||||
|
||||
echo "== I7: ingested_ts staleness — a far-future emit_ts still goes stale (receive-time governs) =="
|
||||
(
|
||||
H="$(fresh i7)"
|
||||
export WAKE_BEACON_RECEIVED="$H/received.json"
|
||||
export WAKE_ALARM_SINK_CMD="cat >$H/alarm.json"
|
||||
now="$(date +%s)"
|
||||
# Host lies with a far-future emit_ts; record stamps the monitor's ingested_ts.
|
||||
jq -cn --argjson ts "$((now + 100000))" \
|
||||
'{kind:"wake-beacon", beacon_seq:3, emit_ts:$ts, host_id:"h", independence:"off-host", degraded:false}' \
|
||||
| bash "$BEACON" record
|
||||
# Simulate the beacon having been received 100s ago (backdate ingested_ts only).
|
||||
jq -c --argjson ing "$((now - 100))" '.ingested_ts = $ing' "$WAKE_BEACON_RECEIVED" >"$H/t" && mv "$H/t" "$WAKE_BEACON_RECEIVED"
|
||||
out="$(bash "$BEACON" check --slo-seconds 5 2>&1)"; rc=$?
|
||||
[ "$rc" -eq 1 ] || fail_msg "I7: far-future emit_ts must NOT defer staleness — receive-time governs (expected exit 1); got $rc [$out]"
|
||||
) && ok
|
||||
|
||||
echo "== I8: beacon HMAC-verify at record — a spoofed (bad-sig) beacon is REJECTED =="
|
||||
(
|
||||
H="$(fresh i8)"
|
||||
export WAKE_STATE_HOME="$H"
|
||||
export MOSAIC_CREDENTIALS_FILE="$H/credentials.json"
|
||||
export WAKE_BEACON_HMAC_KEY_NAME="beacon"
|
||||
jq -cn --arg k "i8-hmac-key" '{wake:{hmac_keys:{"beacon":$k}}}' >"$MOSAIC_CREDENTIALS_FILE"
|
||||
export WAKE_BEACON_INDEPENDENCE="off-host"
|
||||
export WAKE_BEACON_SINK_CMD="cat >$H/shipped.json"
|
||||
export WAKE_BEACON_RECEIVED="$H/received.json"
|
||||
bash "$BEACON" emit >/dev/null 2>&1 || fail_msg "I8: signed emit must succeed"
|
||||
bash "$BEACON" record <"$H/shipped.json" >/dev/null 2>&1 || fail_msg "I8: an authentic signed beacon must be accepted"
|
||||
rm -f "$WAKE_BEACON_RECEIVED"
|
||||
jq -c '.beacon_seq = 88888 | .host_id = "attacker"' "$H/shipped.json" >"$H/spoof.json"
|
||||
out="$(bash "$BEACON" record <"$H/spoof.json" 2>&1)"; rc=$?
|
||||
[ "$rc" -ne 0 ] || fail_msg "I8: a spoofed beacon must be REJECTED at record (rc=$rc) [$out]"
|
||||
[ ! -s "$WAKE_BEACON_RECEIVED" ] || fail_msg "I8: a rejected spoof must NOT advance the dead-man clock"
|
||||
) && ok
|
||||
|
||||
echo
|
||||
if [ -s "$FAILFILE" ]; then
|
||||
echo "wake install harness: FAILED ($(grep -c . "$FAILFILE") assertion(s))" >&2
|
||||
|
||||
Reference in New Issue
Block a user