0ec7555126
store.SetOperatorOOBPeer/GetOperatorOOBPeer (empty-host_id wg_peers row, explicit /32, validated in-subnet/not-reserved/not-taken, last-write-wins rotation). PUT/GET /admin/wg/operator-peer (global key). mergeWireguard adds oob_peer_ip when an operator peer exists (absent = byte-identical). BumpAllHostGenerations forces fleet re-fetch. Non-hollow tests both sides. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
74 lines
2.4 KiB
Go
74 lines
2.4 KiB
Go
package store
|
|
|
|
import "testing"
|
|
|
|
const opPubkey = "cdmN4U+fjR18zBk+SKoceJQyz9HgA9+hN8/FiKF1u0o="
|
|
|
|
func TestOperatorOOBPeer_RoundTripRotateAndUnbound(t *testing.T) {
|
|
s := newTestStore(t)
|
|
setTestEndpoint(t, s, "10.77.0.0/24", "10.77.0.1")
|
|
|
|
// none → (nil,nil)
|
|
if op, err := s.GetOperatorOOBPeer(); err != nil || op != nil {
|
|
t.Fatalf("no operator peer: got %+v / %v", op, err)
|
|
}
|
|
|
|
// register at an explicit /32
|
|
if err := s.SetOperatorOOBPeer(opPubkey, "10.77.0.250"); err != nil {
|
|
t.Fatalf("SetOperatorOOBPeer: %v", err)
|
|
}
|
|
op, err := s.GetOperatorOOBPeer()
|
|
if err != nil || op == nil {
|
|
t.Fatalf("GetOperatorOOBPeer: %+v / %v", op, err)
|
|
}
|
|
if op.AssignedIP != "10.77.0.250" || op.Pubkey != opPubkey {
|
|
t.Fatalf("operator peer = %+v", op)
|
|
}
|
|
if op.HostID != "" || op.Note != OperatorOOBNote {
|
|
t.Fatalf("operator peer must be UNBOUND (host_id '') + note operator-oob: %+v", op)
|
|
}
|
|
// it IS in the peer list (so peersync pushes it to the endpoint)
|
|
if peerCount(t, s) != 1 {
|
|
t.Fatalf("operator peer must appear in ListWGPeers")
|
|
}
|
|
|
|
// rotate: new key + new IP replaces the old (last-write-wins, still one operator row)
|
|
const opPubkey2 = "tQ6eJC9y8pSKtam44DaSrrtYBZYUvZG2z1stS6PsaD4="
|
|
if err := s.SetOperatorOOBPeer(opPubkey2, "10.77.0.249"); err != nil {
|
|
t.Fatalf("rotate: %v", err)
|
|
}
|
|
op, _ = s.GetOperatorOOBPeer()
|
|
if op.Pubkey != opPubkey2 || op.AssignedIP != "10.77.0.249" {
|
|
t.Fatalf("rotate did not replace: %+v", op)
|
|
}
|
|
if peerCount(t, s) != 1 {
|
|
t.Fatalf("rotate must keep exactly one operator peer, got %d", peerCount(t, s))
|
|
}
|
|
}
|
|
|
|
func TestOperatorOOBPeer_ValidationRejectsReservedAndTaken(t *testing.T) {
|
|
s := newTestStore(t)
|
|
setTestEndpoint(t, s, "10.77.0.0/24", "10.77.0.1")
|
|
|
|
for _, bad := range []struct{ ip, why string }{
|
|
{"10.77.0.1", "pbs_tunnel_ip"},
|
|
{"10.77.0.0", "network address"},
|
|
{"10.77.0.255", "broadcast"},
|
|
{"10.88.0.5", "outside subnet"},
|
|
{"not-an-ip", "not an ip"},
|
|
} {
|
|
if err := s.SetOperatorOOBPeer(opPubkey, bad.ip); err == nil {
|
|
t.Errorf("SetOperatorOOBPeer(%q) accepted but should reject (%s)", bad.ip, bad.why)
|
|
}
|
|
}
|
|
|
|
// a customer peer holds 10.77.0.2 → operator can't take it
|
|
if _, _, err := s.AddWGPeer("PCUST", "h1", ""); err != nil {
|
|
t.Fatalf("AddWGPeer: %v", err)
|
|
}
|
|
custIP, _ := s.GetWGPeerForHost("h1")
|
|
if err := s.SetOperatorOOBPeer(opPubkey, custIP.AssignedIP); err == nil {
|
|
t.Fatalf("operator peer must not take a customer's IP %s", custIP.AssignedIP)
|
|
}
|
|
}
|