From ac93fd42aa8adb84a3318649736b21bce4477363 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Sat, 13 Dec 2025 21:22:04 +0000 Subject: [PATCH] fix: clear agents that connected during unauthenticated setup window When no auth is configured (fresh install), CheckAuth allows all requests. This creates a race condition where existing agents from a previous setup can report data before the wizard completes security configuration. This fix clears all host agents and docker hosts when /api/security/quick-setup is called, ensuring the wizard shows a clean state after security is configured. Added: - State.ClearAllHosts() - removes all host agents - State.ClearAllDockerHosts() - removes all docker hosts - Monitor.ClearUnauthenticatedAgents() - clears both and resets token bindings - Call to ClearUnauthenticatedAgents() in handleQuickSecuritySetupFixed() --- internal/api/security_setup_fix.go | 12 ++++++++++++ internal/models/models.go | 26 +++++++++++++++++++++++++ internal/monitoring/monitor.go | 31 ++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+) diff --git a/internal/api/security_setup_fix.go b/internal/api/security_setup_fix.go index b4f01ed..5649f8b 100644 --- a/internal/api/security_setup_fix.go +++ b/internal/api/security_setup_fix.go @@ -290,6 +290,18 @@ func handleQuickSecuritySetupFixed(r *Router) http.HandlerFunc { } log.Info().Msg("Runtime config updated with new security settings - active immediately") + // Clear any agents that connected during the brief unauthenticated setup window. + // This prevents stale/unauthorized agent data from appearing in the wizard. + if r.monitor != nil { + hostCleared, dockerCleared := r.monitor.ClearUnauthenticatedAgents() + if hostCleared > 0 || dockerCleared > 0 { + log.Info(). + Int("hosts", hostCleared). + Int("dockerHosts", dockerCleared). + Msg("Cleared agents that connected before security was configured") + } + } + // Save system settings to system.json systemSettings := config.DefaultSystemSettings() systemSettings.ConnectionTimeout = 10 // Default seconds diff --git a/internal/models/models.go b/internal/models/models.go index cf1294b..cf82b88 100644 --- a/internal/models/models.go +++ b/internal/models/models.go @@ -1489,6 +1489,19 @@ func (s *State) RemoveDockerHost(hostID string) (DockerHost, bool) { return DockerHost{}, false } +// ClearAllDockerHosts removes all docker hosts from the state. +// This is used during initial security setup to clear any docker hosts that may have +// connected during the brief unauthenticated window before credentials were configured. +func (s *State) ClearAllDockerHosts() int { + s.mu.Lock() + defer s.mu.Unlock() + + count := len(s.DockerHosts) + s.DockerHosts = nil + s.LastUpdate = time.Now() + return count +} + // SetDockerHostStatus updates the status of a docker host if present. func (s *State) SetDockerHostStatus(hostID, status string) bool { s.mu.Lock() @@ -1904,6 +1917,19 @@ func (s *State) RemoveHost(hostID string) (Host, bool) { return Host{}, false } +// ClearAllHosts removes all host agents from the state. +// This is used during initial security setup to clear any hosts that may have +// connected during the brief unauthenticated window before credentials were configured. +func (s *State) ClearAllHosts() int { + s.mu.Lock() + defer s.mu.Unlock() + + count := len(s.Hosts) + s.Hosts = nil + s.LastUpdate = time.Now() + return count +} + // UpsertCephCluster inserts or updates a Ceph cluster in the state. // Uses ID (typically the FSID) for matching. func (s *State) UpsertCephCluster(cluster CephCluster) { diff --git a/internal/monitoring/monitor.go b/internal/monitoring/monitor.go index f94e8a7..0f52edf 100644 --- a/internal/monitoring/monitor.go +++ b/internal/monitoring/monitor.go @@ -1395,6 +1395,37 @@ func (m *Monitor) RebuildTokenBindings() { Msg("Rebuilt agent token bindings after API token reload") } +// ClearUnauthenticatedAgents removes all host agents and docker hosts from the state. +// This should be called when security is first configured to clear any agents that +// connected during the brief unauthenticated window before credentials were set up. +// This prevents stale/unauthorized agent data from appearing in the UI. +func (m *Monitor) ClearUnauthenticatedAgents() (int, int) { + if m == nil || m.state == nil { + return 0, 0 + } + + // Clear all hosts + hostCount := m.state.ClearAllHosts() + + // Clear all docker hosts + dockerCount := m.state.ClearAllDockerHosts() + + // Clear any token bindings since the tokens used by the old agents are invalid + m.mu.Lock() + m.dockerTokenBindings = make(map[string]string) + m.hostTokenBindings = make(map[string]string) + m.mu.Unlock() + + if hostCount > 0 || dockerCount > 0 { + log.Info(). + Int("hostsCleared", hostCount). + Int("dockerHostsCleared", dockerCount). + Msg("Cleared unauthenticated agents after security setup") + } + + return hostCount, dockerCount +} + // QueueDockerHostStop queues a stop command for the specified docker host. func (m *Monitor) QueueDockerHostStop(hostID string) (models.DockerHostCommandStatus, error) { return m.queueDockerStopCommand(hostID)