feat(mgmtplane): break-glass privsep-dir watchdog + mgmt_plane health (TASK G1) — v0.71.0

Prerequisite for felhom-sshd (H1). Closes the SPIKE-felhom-sshd §8 lockout: a
second sshd's RuntimeDirectory=sshd removed the SHARED /run/sshd privsep dir and
took stock sshd on :22 down (sessions reset after KEXINIT).

Host artifacts (configs/, installed by felhom-host-install):
- felhom-privsep.tmpfiles: layer 1, boot-persistent /run/sshd owned by no unit
- felhom-mgmt-watchdog.sh/.service/.timer: layer 2, AGENT-INDEPENDENT ~60s heal
  (stat-first recreate + reset-failed sshd only if failed + heal-marker); never
  RuntimeDirectory=, never restarts stock sshd, never touches a healthy dir.

Go (internal/mgmtplane): read-only Reporter → additive omitempty mgmt_plane
heartbeat stanza (privsep_dir_ok/sshd_reachable/healed_recently/privsep_healed_at),
wired via Collector.SetMgmtPlaneReporter. Non-hollow tests + red-proofs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
2026-07-05 18:49:27 +02:00
parent 1c75a45a42
commit fd4e177216
11 changed files with 459 additions and 1 deletions
+22
View File
@@ -66,6 +66,28 @@ type HostReport struct {
// pending=false, the correct default.
SelfUpdatePending bool `json:"selfupdate_pending,omitempty"`
SelfUpdatePendingVersion string `json:"selfupdate_pending_version,omitempty"`
// MgmtPlane is the management-plane health stanza (TASK G1). It reports whether OpenSSH's SHARED
// privilege-separation dir (/run/sshd) is present and whether the stock sshd listener answers, plus
// whether the agent-independent watchdog auto-healed a missing dir since boot (and when). This is
// the visibility half of the break-glass system: the dumb watchdog fixes /run/sshd with no login,
// and this stanza surfaces a RECURRING clobber to the operator BEFORE it becomes a lockout —
// complementing host_staleness (which only catches a box gone silent). `omitempty` (the
// SelfUpdatePending precedent): stored opaquely hub-side, so these additive fields need no
// hub-schema change and are absent when the reporter is not wired.
MgmtPlane *MgmtPlaneStatus `json:"mgmt_plane,omitempty"`
}
// MgmtPlaneStatus is the per-heartbeat management-plane health (TASK G1). Carries no secret.
// HealedRecently is true while the watchdog's heal-marker exists (a privsep-dir heal happened this
// boot); PrivsepHealedAt is that marker's RFC3339 timestamp (absent when no heal has occurred). The
// hub raises a warning event on a PrivsepHealedAt it has not alerted on — a recurring auto-heal means
// a persistent clobber cause worth investigating before the box locks out.
type MgmtPlaneStatus struct {
PrivsepDirOK bool `json:"privsep_dir_ok"` // /run/sshd exists (the KEXINIT-reset detector)
SshdReachable bool `json:"sshd_reachable"` // the stock sshd listener accepts TCP
HealedRecently bool `json:"healed_recently"` // the watchdog heal-marker is present (this boot)
PrivsepHealedAt string `json:"privsep_healed_at,omitempty"` // marker timestamp; hub warns on a NEW value
}
// WireguardStatus is the per-heartbeat offsite-tunnel status (S3). LastHandshakeAgeS is nil when