Skip to content

Commit d52c256

Browse files
committed
Add network admin tooling: delete-network, admin-token on rendezvous
- Add -admin-token flag to cmd/rendezvous (was missing, network creation silently disabled on deployed rendezvous) - Add DeleteNetwork to registry protocol (admin-gated, cannot delete backbone, cleans member node network lists, persists state) - Add admin token bypass for backbone node listing - Update ListNodes client to accept optional admin token - Guard internal tooling build in Makefile
1 parent 80923a6 commit d52c256

5 files changed

Lines changed: 101 additions & 5 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ web/.wrangler/
9090

9191
# Internal tooling
9292
cmd/spoof/
93+
cmd/pilot-admin/
94+
tests/admin_cli_test.go
9395

9496
# Blog messaging (internal)
9597
web/MESSAGING.md

Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ build:
2020
go build -o $(BINDIR)/pilotctl ./cmd/pilotctl
2121
go build -o $(BINDIR)/nameserver ./cmd/nameserver
2222
go build -o $(BINDIR)/gateway ./cmd/gateway
23+
@test -d cmd/pilot-admin && go build -o $(BINDIR)/pilot-admin ./cmd/pilot-admin || true
2324
go build -o $(BINDIR)/webserver ./examples/go/webserver
2425
go build -o $(BINDIR)/client ./examples/go/client
2526
go build -o $(BINDIR)/echo ./examples/go/echo
@@ -70,6 +71,7 @@ build-linux:
7071
GOOS=linux GOARCH=amd64 go build -o $(BINDIR)/pilotctl-linux ./cmd/pilotctl
7172
GOOS=linux GOARCH=amd64 go build -o $(BINDIR)/nameserver-linux ./cmd/nameserver
7273
GOOS=linux GOARCH=amd64 go build -o $(BINDIR)/gateway-linux ./cmd/gateway
74+
@test -d cmd/pilot-admin && GOOS=linux GOARCH=amd64 go build -o $(BINDIR)/pilot-admin-linux ./cmd/pilot-admin || true
7375
GOOS=linux GOARCH=amd64 go build -o $(BINDIR)/echo-linux ./examples/go/echo
7476
GOOS=linux GOARCH=amd64 go build -o $(BINDIR)/client-linux ./examples/go/client
7577
GOOS=linux GOARCH=amd64 go build -o $(BINDIR)/webserver-linux ./examples/go/webserver

cmd/rendezvous/main.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ func main() {
2929
httpAddr := flag.String("http", "", "HTTP dashboard listen address (e.g. :3000)")
3030
logLevel := flag.String("log-level", "info", "log level (debug, info, warn, error)")
3131
logFormat := flag.String("log-format", "text", "log format (text, json)")
32+
adminToken := flag.String("admin-token", "", "admin token for network creation (empty = creation disabled)")
3233
flag.Parse()
3334

3435
if *configPath != "" {
@@ -53,6 +54,9 @@ func main() {
5354

5455
// Start registry
5556
r := registry.NewWithStore(*beaconAddr, *storePath)
57+
if *adminToken != "" {
58+
r.SetAdminToken(*adminToken)
59+
}
5660
if *enableTLS {
5761
if err := r.SetTLS(*tlsCert, *tlsKey); err != nil {
5862
log.Fatalf("TLS setup: %v", err)

pkg/registry/client.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -310,17 +310,32 @@ func (c *Client) LeaveNetwork(nodeID uint32, networkID uint16, adminToken string
310310
return c.Send(msg)
311311
}
312312

313+
func (c *Client) DeleteNetwork(networkID uint16, adminToken string) (map[string]interface{}, error) {
314+
msg := map[string]interface{}{
315+
"type": "delete_network",
316+
"network_id": networkID,
317+
}
318+
if adminToken != "" {
319+
msg["admin_token"] = adminToken
320+
}
321+
return c.Send(msg)
322+
}
323+
313324
func (c *Client) ListNetworks() (map[string]interface{}, error) {
314325
return c.Send(map[string]interface{}{
315326
"type": "list_networks",
316327
})
317328
}
318329

319-
func (c *Client) ListNodes(networkID uint16) (map[string]interface{}, error) {
320-
return c.Send(map[string]interface{}{
330+
func (c *Client) ListNodes(networkID uint16, adminToken ...string) (map[string]interface{}, error) {
331+
msg := map[string]interface{}{
321332
"type": "list_nodes",
322333
"network_id": networkID,
323-
})
334+
}
335+
if len(adminToken) > 0 && adminToken[0] != "" {
336+
msg["admin_token"] = adminToken[0]
337+
}
338+
return c.Send(msg)
324339
}
325340

326341
func (c *Client) Deregister(nodeID uint32) (map[string]interface{}, error) {

pkg/registry/server.go

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,8 @@ func (s *Server) handleMessage(msg map[string]interface{}, remoteAddr string) (r
718718
return s.handleJoinNetwork(msg)
719719
case "leave_network":
720720
return s.handleLeaveNetwork(msg)
721+
case "delete_network":
722+
return s.handleDeleteNetwork(msg)
721723
case "lookup":
722724
return s.handleLookup(msg)
723725
case "resolve":
@@ -1327,6 +1329,50 @@ func (s *Server) handleLeaveNetwork(msg map[string]interface{}) (map[string]inte
13271329
}, nil
13281330
}
13291331

1332+
func (s *Server) handleDeleteNetwork(msg map[string]interface{}) (map[string]interface{}, error) {
1333+
if err := s.requireAdminToken(msg); err != nil {
1334+
return nil, err
1335+
}
1336+
1337+
netID := jsonUint16(msg, "network_id")
1338+
1339+
// Cannot delete backbone
1340+
if netID == 0 {
1341+
return nil, fmt.Errorf("cannot delete the backbone network")
1342+
}
1343+
1344+
s.mu.Lock()
1345+
defer s.mu.Unlock()
1346+
1347+
network, ok := s.networks[netID]
1348+
if !ok {
1349+
return nil, fmt.Errorf("network %d: %w", netID, protocol.ErrNetworkNotFound)
1350+
}
1351+
1352+
// Remove network from all member nodes
1353+
for _, memberID := range network.Members {
1354+
if node, ok := s.nodes[memberID]; ok {
1355+
for i, n := range node.Networks {
1356+
if n == netID {
1357+
node.Networks = append(node.Networks[:i], node.Networks[i+1:]...)
1358+
break
1359+
}
1360+
}
1361+
}
1362+
}
1363+
1364+
name := network.Name
1365+
delete(s.networks, netID)
1366+
s.save()
1367+
1368+
slog.Info("deleted network", "network_id", netID, "name", name)
1369+
1370+
return map[string]interface{}{
1371+
"type": "delete_network_ok",
1372+
"network_id": netID,
1373+
}, nil
1374+
}
1375+
13301376
func (s *Server) handleLookup(msg map[string]interface{}) (map[string]interface{}, error) {
13311377
nodeID := jsonUint32(msg, "node_id")
13321378

@@ -2028,9 +2074,36 @@ func (s *Server) handleListNetworks() (map[string]interface{}, error) {
20282074
func (s *Server) handleListNodes(msg map[string]interface{}) (map[string]interface{}, error) {
20292075
netID := jsonUint16(msg, "network_id")
20302076

2031-
// Backbone (network 0) node listing is restricted to prevent enumeration
2077+
// Backbone (network 0) node listing is restricted to prevent enumeration.
2078+
// Admin token bypasses this restriction.
20322079
if netID == 0 {
2033-
return nil, fmt.Errorf("listing backbone nodes is not permitted (use lookup with a specific node_id)")
2080+
if err := s.requireAdminToken(msg); err != nil {
2081+
return nil, fmt.Errorf("listing backbone nodes is not permitted (use lookup with a specific node_id)")
2082+
}
2083+
// Admin-authenticated: list all registered nodes
2084+
s.mu.RLock()
2085+
defer s.mu.RUnlock()
2086+
nodes := make([]map[string]interface{}, 0, len(s.nodes))
2087+
for _, node := range s.nodes {
2088+
entry := map[string]interface{}{
2089+
"node_id": node.ID,
2090+
"address": protocol.Addr{Network: 0, Node: node.ID}.String(),
2091+
}
2092+
if node.Hostname != "" {
2093+
entry["hostname"] = node.Hostname
2094+
}
2095+
if node.TaskExec {
2096+
entry["task_exec"] = true
2097+
}
2098+
if node.Public {
2099+
entry["real_addr"] = node.RealAddr
2100+
}
2101+
nodes = append(nodes, entry)
2102+
}
2103+
return map[string]interface{}{
2104+
"type": "list_nodes_ok",
2105+
"nodes": nodes,
2106+
}, nil
20342107
}
20352108

20362109
s.mu.RLock()

0 commit comments

Comments
 (0)