R-276 (hub half): host delete asks for the WireGuard peer-list push at once
DeleteHost already removed the host's wg_peers row, but only the 5-minute reconciler tick pushed the list to the off-site endpoint. The host delete now triggers the push as the customer delete does (R-600) and logs that it was requested. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -955,7 +955,10 @@ func (s *Server) handleHostDelete(w http.ResponseWriter, r *http.Request, hostID
|
||||
http.Error(w, "Internal error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
s.logger.Printf("[INFO] host deleted: %s (%s)", hostID, hostDeleteEscrowEffect(escrowBefore != nil, escErr))
|
||||
// R-276: DeleteHost removed the host's wg_peers row; ask for the full-list push NOW (as the customer
|
||||
// delete does, R-600), so an uninstalled box's tunnel stops being accepted by the off-site endpoint
|
||||
// at once instead of on the reconciler's next 5-minute tick.
|
||||
s.logger.Printf("[INFO] host deleted: %s (%s; %s)", hostID, hostDeleteEscrowEffect(escrowBefore != nil, escErr), s.requestWGPeerPush())
|
||||
// R-509: the customer record stays and now waits for a box → send the connect link.
|
||||
if host.CustomerID != "" {
|
||||
s.autoMintSelfBindIfWaiting(host.CustomerID, "host delete")
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-hub/internal/store"
|
||||
)
|
||||
|
||||
// R-276 (hub half): deleting a host removes its WireGuard peer row AND asks the peer-sync reconciler for
|
||||
// an immediate full-list push (the seam is SetWGPeerSync — no SSH, no ep0), so the endpoint stops
|
||||
// accepting the uninstalled box's tunnel now. The log says the push was REQUESTED, never "removed".
|
||||
// RED-PROOF: drop s.requestWGPeerPush() from handleHostDelete → triggers = 0 → FAIL.
|
||||
func TestR276_HostDeleteRemovesPeerAndPushes(t *testing.T) {
|
||||
s, st := newTestServer(t)
|
||||
var logBuf strings.Builder
|
||||
s.logger = log.New(&logBuf, "", 0)
|
||||
triggers := 0
|
||||
s.SetWGPeerSync(func() { triggers++ })
|
||||
if err := st.UpsertHost(&store.Host{HostID: "gone-1", CustomerID: "c9", APIKey: "k"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := st.SetWGEndpoint(&store.WGEndpoint{EndpointID: "ep0", DNSName: "ep0.example", WGPort: 51820,
|
||||
ServerPubkey: "srv", TunnelSubnet: "10.77.0.0/24", PBSTunnelIP: "10.77.0.1"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, _, err := st.AddWGPeer("pk-gone-1", "gone-1", "test"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rr := postHostDelete(t, s, "gone-1", url.Values{"confirm_host_id": {"gone-1"}})
|
||||
if rr.Code != http.StatusSeeOther {
|
||||
t.Fatalf("delete = %d (%s)", rr.Code, rr.Body.String())
|
||||
}
|
||||
peers, err := st.ListWGPeers()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, p := range peers {
|
||||
if p.HostID == "gone-1" || p.Pubkey == "pk-gone-1" {
|
||||
t.Fatal("the deleted host's WireGuard peer is still registered")
|
||||
}
|
||||
}
|
||||
if triggers != 1 {
|
||||
t.Fatalf("WG peer-sync triggers = %d, want 1 (the endpoint must drop the peer now, not on the next tick)", triggers)
|
||||
}
|
||||
if !strings.Contains(logBuf.String(), "WG peer removal push requested") {
|
||||
t.Errorf("the delete line must name the pending push:\n%s", logBuf.String())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user