wgtunnel: S3 Part 2 — manager state machine + loop + desired raw-consumer seam

Manager: one-shot registration (marker gate; backoff cap 15m), adopt-lost-marker,
re-key-on-mismatch, REVOKED-STAYS-REVOKED teardown (marker kept, zero execs on
later ticks), no-teardown-on-absent-data, hash-gated apply (zero execs steady
state), restart-not-reload on conf change, self-heal enable. Status stanza with
latest-handshakes-ONLY wg read. Collector WireguardReporter seam. desired.Syncer
AddConsumer fan-out with panic containment. Red-proofs a/b/d run + reverted;
no-key-material-in-logs asserted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
2026-07-04 07:09:01 +02:00
parent 0daae92c4f
commit fb248961c6
6 changed files with 1114 additions and 3 deletions
+19
View File
@@ -49,6 +49,12 @@ type PBSReporter interface {
PBSSnapshots(ctx context.Context) []PBSSnapshot
}
// WireguardReporter is the S3 seam the wgtunnel loop plugs into (same consumer-side pattern —
// hub does not import wgtunnel). nil (feature disabled) → no wireguard stanza on the report.
type WireguardReporter interface {
WireguardStatus(ctx context.Context) *WireguardStatus
}
// Collector builds a HostReport from read-only sources. All deps are behind narrow
// interfaces for unit testing.
type Collector struct {
@@ -61,6 +67,7 @@ type Collector struct {
temp TempReader // slice 9: host CPU/chassis temp (nil-safe → nil temp)
capProbe func(ctx context.Context) []capability.Status // v0.44.0: privileged-capability self-check (nil → empty)
leafFP string // v0.48.0: served local-API leaf fp (static per process; "" when local API disabled)
wg WireguardReporter // S3: offsite-tunnel status (nil → stanza omitted)
hostID string
agentVersion string
logger *slog.Logger
@@ -110,6 +117,13 @@ func (c *Collector) SetLeafFingerprint(fp string) *Collector {
return c
}
// SetWireguardReporter wires the offsite-tunnel status source (S3; nil-safe → stanza omitted).
// Returns the collector for chaining.
func (c *Collector) SetWireguardReporter(w WireguardReporter) *Collector {
c.wg = w
return c
}
// Collect builds the report. Best-effort liveness: a failed NodeStatus is a hard
// error (no useful report — the cycle skips the POST); a failed per-guest
// GuestConfig degrades that guest to status="unknown" without spec but still sends;
@@ -143,6 +157,11 @@ func (c *Collector) Collect(ctx context.Context) (*HostReport, error) {
// DR recipe host-half — derived from the just-collected guest/storage/PBS facts (no new reads).
// Secret-free by construction (identifiers/intents/sizes/coordinates only).
report.DRRecipe = BuildDRRecipeHostHalf(report.Guests, report.StorageTargets, report.PBSSnapshots)
// S3: offsite-tunnel status stanza (nil reporter = feature disabled → omitted; the pubkey in
// it is the operator's revocation-recovery handle).
if c.wg != nil {
report.Wireguard = c.wg.WireguardStatus(ctx)
}
return report, nil
}