server: P2-18b pending WS + admin accept/reject
GET /ws/agent/pending?pending_id=… runs an Ed25519 nonce-sign
handshake against the row's stored public key, then holds the
connection open. POST /api/pending-hosts/{id}/accept (admin)
mints a real Host row + bearer + AEAD-encrypted repo creds, pushes
the bearer down the open WS, deletes the pending row, and writes
a host.accept_pending audit entry. POST /api/pending-hosts/{id}/reject
closes the socket with code 4001 and audit-logs host.reject_pending.
In-memory pendingHub keyed by pending_id wires accept/reject to
their live socket.
This commit is contained in:
@@ -53,6 +53,11 @@ type Server struct {
|
||||
// announceRL is the per-source-IP token-bucket guarding
|
||||
// POST /api/agents/announce (P2-18). One process-local map.
|
||||
announceRL *announceLimiter
|
||||
|
||||
// pendingHub holds live /ws/agent/pending sockets keyed by
|
||||
// pending_id so the accept/reject handlers can push the bearer
|
||||
// or close cleanly (P2-18b).
|
||||
pendingHub *pendingHub
|
||||
}
|
||||
|
||||
// New builds a configured but not-yet-started server.
|
||||
@@ -75,6 +80,7 @@ func New(deps Deps) *Server {
|
||||
deps: deps,
|
||||
drainLocks: make(map[string]*sync.Mutex),
|
||||
announceRL: newAnnounceLimiter(),
|
||||
pendingHub: newPendingHub(),
|
||||
}
|
||||
s.routes(r)
|
||||
|
||||
@@ -105,6 +111,10 @@ func (s *Server) routes(r chi.Router) {
|
||||
// globally capped (P2-18).
|
||||
r.Post("/agents/announce", s.handleAnnounce)
|
||||
|
||||
// Pending host management — admin-only (gated inside the handler).
|
||||
r.Post("/pending-hosts/{id}/accept", s.handleAcceptPendingHost)
|
||||
r.Post("/pending-hosts/{id}/reject", s.handleRejectPendingHost)
|
||||
|
||||
// Operator → server (authenticated). Spec.md §6.1's
|
||||
// /hosts/{id}/enrollment-token (regenerate) lands when the
|
||||
// host page can call it; for now just the create endpoint.
|
||||
@@ -185,6 +195,10 @@ func (s *Server) routes(r chi.Router) {
|
||||
r.Post("/hosts/{id}/run-backup", s.handleUIRunBackupGone)
|
||||
r.Post("/hosts/{id}/init-repo", s.handleUIInitRepoGone)
|
||||
|
||||
// Pending-host WebSocket (announce-and-approve, P2-18b). Mounted
|
||||
// before /ws/agent so the more-specific route matches first.
|
||||
r.Get("/ws/agent/pending", s.handlePendingWS)
|
||||
|
||||
// Agent ↔ server WebSocket. Bearer-authenticated inside the handler.
|
||||
if s.deps.Hub != nil {
|
||||
r.Mount("/ws/agent", ws.AgentHandler(ws.HandlerDeps{
|
||||
|
||||
Reference in New Issue
Block a user