60 lines
2.5 KiB
Bash
Executable File
60 lines
2.5 KiB
Bash
Executable File
#!/bin/bash
|
|
# exec-watch.sh — 15-min watchdog for the bl exec server's signature-auth path.
|
|
# Signs a canary op as the exec-canary identity and POSTs it to the
|
|
# local exec-constrained server. Records result in /home/super/.exec-watch-status.json
|
|
# and appends to logs/exec-watch.log. Failures are pull-based (status file +
|
|
# log); wire push alerting here if the fleet wants paging.
|
|
#
|
|
# Runs via systemd user timer exec-watch.timer (OnUnitActiveSec=15min).
|
|
# Server: exec-constrained.py — named ops ONLY, no arbitrary shell;
|
|
# signature namespace: exec-constrained, with {op,args,ts,nonce} envelope.
|
|
# NOTE: signers file (/home/super/.exec-signers) is synced MANUALLY from the
|
|
# VM's /srv/board/allowed_signers on identity renames/adds — bl cannot ssh
|
|
# back to the VM, so there is no pull sync. Operator step, documented in
|
|
# docs/TOKEN_POLICY.md.
|
|
set -uo pipefail
|
|
KEY=/home/super/.exec-canary
|
|
URL=https://100.123.153.75:8444/exec
|
|
STATUS=/home/super/.exec-watch-status.json
|
|
LOG=/home/super/Projects/NetVM/logs/exec-watch.log
|
|
|
|
ts=$(date +%s)
|
|
nonce=$(python3 -c "import secrets; print(secrets.token_hex(16))")
|
|
payload=$(python3 -c "import json,sys; print(json.dumps({'op':'exec.ping','args':{},'ts':int(sys.argv[1]),'nonce':sys.argv[2]}))" "$ts" "$nonce")
|
|
sig=$(printf '%s' "$payload" | ssh-keygen -Y sign -f "$KEY" -n exec-constrained 2>/dev/null)
|
|
if [ -z "${sig:-}" ]; then
|
|
result="sign-failed"
|
|
else
|
|
body=$(python3 -c "import json,sys; print(json.dumps({'identity': 'exec-canary', 'payload': sys.argv[1], 'signature': sys.argv[2]}))" "$payload" "$sig")
|
|
out=$(curl -sk -m 25 -X POST "$URL" -H 'Content-Type: application/json' -d "$body" 2>/dev/null)
|
|
if echo "$out" | grep -q '"rc": 0'; then
|
|
result="ok"
|
|
else
|
|
result="bad-response"
|
|
fi
|
|
fi
|
|
|
|
now_iso=$(date -u +%FT%TZ)
|
|
{
|
|
python3 - "$STATUS" "$now_iso" "$result" <<'PYEOF'
|
|
import json, sys
|
|
status_path, now_iso, result = sys.argv[1], sys.argv[2], sys.argv[3]
|
|
try:
|
|
st = json.load(open(status_path))
|
|
except Exception:
|
|
st = {}
|
|
if result == "ok":
|
|
st.update({"last_ok": now_iso, "last_fail": None,
|
|
"consecutive_failures": 0, "result": "ok"})
|
|
else:
|
|
st.update({"last_ok": st.get("last_ok"),
|
|
"last_fail": now_iso,
|
|
"consecutive_failures": st.get("consecutive_failures", 0) + 1,
|
|
"result": result})
|
|
json.dump(st, open(status_path, "w"))
|
|
print(f"[{now_iso}] exec-watch: {result} "
|
|
f"(consecutive_failures={st['consecutive_failures']})")
|
|
PYEOF
|
|
} >> "$LOG" 2>&1
|
|
[ "$result" = "ok" ]
|