hub v0.167.0 source (not built, not deployed): D31 — the F-14 PBS auto re-issue reads the newest deletion of a host that held a key, a keyless install between no longer blocks it (R-1205); D30 — the new key's handshake clears wg_tunnel_stale (pinned), wording; wire gate entry; Part 1 stray-copy 9202 evidence
gates / gates (push) Successful in 15m46s
gates / gates (push) Successful in 15m46s
This commit is contained in:
@@ -10,8 +10,11 @@ import (
|
||||
// than the last one accepted from that peer), so the box's tunnel to ep0 — its PBS whole-box copies and the
|
||||
// operator's door — stays dead until the real date passes the wrong one, while the box itself reports normally.
|
||||
// Nothing said why. The hub now tells the OPERATOR once per host, edge-triggered from the host's newest tunnel event
|
||||
// (the host_swap shape), and once when it is healthy again. Operator-only (notify.operatorOnlyEvents): the household
|
||||
// cannot fix it. The repair (re-key the peer, or ep0 forgets the stored timestamp) is a separate decision.
|
||||
// (the host_swap shape), and records once when it is healthy again (wg_tunnel_ok is an info event: stored, not
|
||||
// mailed). Operator-only (notify.operatorOnlyEvents): the household cannot fix it. The repair is D30 (operator
|
||||
// 2026-10-22): an agent with it makes a new key after 15 minutes of a future-stamped handshake and registers it here
|
||||
// (re-key in place, the full-list push to ep0); the new key's first handshake clears this alarm — pinned by
|
||||
// TestD30_TheAlarmClearsWhenTheNewKeyShakesHands.
|
||||
const (
|
||||
eventWGTunnelStale = "wg_tunnel_stale"
|
||||
eventWGTunnelOK = "wg_tunnel_ok"
|
||||
@@ -36,7 +39,7 @@ func wgTunnelStale(w *hostWireguard) (bool, string) {
|
||||
return false, ""
|
||||
}
|
||||
if w.HandshakeFutureS != nil && *w.HandshakeFutureS > 0 {
|
||||
return true, fmt.Sprintf("its last handshake is stamped %s in the FUTURE by the box's own clock — the clock ran ahead once, and ep0 refuses every newer handshake until that time passes", humanDuration(*w.HandshakeFutureS))
|
||||
return true, fmt.Sprintf("its last handshake is stamped %s in the FUTURE by the box's own clock — the clock ran ahead once, and ep0 refuses every newer handshake until that time passes (an agent with the D30 repair makes a new key by itself within about 15 minutes)", humanDuration(*w.HandshakeFutureS))
|
||||
}
|
||||
if w.LastHandshakeAgeS != nil && *w.LastHandshakeAgeS > wgTunnelStaleAfterS {
|
||||
return true, fmt.Sprintf("it has not shaken hands for %s while the box reports — the box's clock may have run ahead (then ep0 refuses it), or ep0 dropped the peer", humanDuration(*w.LastHandshakeAgeS))
|
||||
@@ -78,7 +81,7 @@ func (h *Handler) checkWGTunnel(hostID, custID string, w *hostWireguard) {
|
||||
default:
|
||||
return
|
||||
}
|
||||
h.logger.Printf("[WARN] host %s: %s", hostID, typ)
|
||||
h.logger.Printf("[%s] host %s: %s", map[bool]string{true: "WARN", false: "INFO"}[stale], hostID, typ)
|
||||
det := map[string]any{"host_id": hostID}
|
||||
if w.LastHandshakeAgeS != nil {
|
||||
det["last_handshake_age_s"] = *w.LastHandshakeAgeS
|
||||
|
||||
@@ -76,3 +76,32 @@ func TestR1204_WhatIsJudged(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// D30 (R-1204): after the agent re-keys, the box reports a restarted tunnel with no handshake yet (nothing to judge),
|
||||
// then the new key's first handshake — which clears the alarm once, and raises nothing new.
|
||||
// RED-PROOF (2026-10-22 night): with the `!stale && wasStale` case removed from checkWGTunnel, this fails (no ok event).
|
||||
func TestD30_TheAlarmClearsWhenTheNewKeyShakesHands(t *testing.T) {
|
||||
h, st, _ := newTestHandler(t)
|
||||
st.SaveCustomerConfig(&store.CustomerConfig{CustomerID: "c1", APIKey: "ckey", RetrievalPassword: "p"})
|
||||
st.UpsertHost(&store.Host{HostID: "h1", CustomerID: "c1", APIKey: "HKEY"})
|
||||
post := func(wg string) {
|
||||
t.Helper()
|
||||
if rr := do(h, http.MethodPost, "/host-report", "HKEY", tunnelReportBody("h1", wg)); rr.Code != http.StatusOK {
|
||||
t.Fatalf("host-report = %d %s", rr.Code, rr.Body.String())
|
||||
}
|
||||
}
|
||||
count := func(typ string) int {
|
||||
evs, _ := st.GetEventsByType("c1", typ, time.Time{})
|
||||
return len(evs)
|
||||
}
|
||||
post(`{"pubkey":"OLD","registered":true,"active":true,"last_handshake_age_s":0,"handshake_future_s":10800}`) // 3 h ahead
|
||||
post(`{"pubkey":"NEW","registered":true,"active":true}`) // re-keyed, no handshake yet
|
||||
if count(eventWGTunnelStale) != 1 || count(eventWGTunnelOK) != 0 {
|
||||
t.Fatalf("stale %d ok %d, want 1 / 0 before the new key shakes hands", count(eventWGTunnelStale), count(eventWGTunnelOK))
|
||||
}
|
||||
post(`{"pubkey":"NEW","registered":true,"active":true,"last_handshake_age_s":20}`)
|
||||
post(`{"pubkey":"NEW","registered":true,"active":true,"last_handshake_age_s":110}`)
|
||||
if count(eventWGTunnelOK) != 1 || count(eventWGTunnelStale) != 1 {
|
||||
t.Fatalf("stale %d ok %d, want 1 / 1 — the new key's handshake clears the alarm once", count(eventWGTunnelStale), count(eventWGTunnelOK))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user