From 4b4107854289693a9c83c1e76c69990841fb5abe Mon Sep 17 00:00:00 2001 From: Michael Reber Date: Fri, 14 Nov 2025 11:20:18 +0100 Subject: [PATCH] fix Fail2ban Callback URL update also on ssh/agent servers --- internal/fail2ban/manager.go | 35 +++++++++++++++++++++++++++++++++++ pkg/web/handlers.go | 24 ++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/internal/fail2ban/manager.go b/internal/fail2ban/manager.go index fd31a2a..0b7e78c 100644 --- a/internal/fail2ban/manager.go +++ b/internal/fail2ban/manager.go @@ -108,6 +108,41 @@ func (m *Manager) Connectors() []Connector { return result } +// UpdateActionFiles updates action files for all active remote connectors (SSH and Agent). +func (m *Manager) UpdateActionFiles(ctx context.Context) error { + m.mu.RLock() + connectors := make([]Connector, 0, len(m.connectors)) + for _, conn := range m.connectors { + server := conn.Server() + // Only update remote servers (SSH and Agent), not local + if server.Type == "ssh" || server.Type == "agent" { + connectors = append(connectors, conn) + } + } + m.mu.RUnlock() + + var lastErr error + for _, conn := range connectors { + if err := updateConnectorAction(ctx, conn); err != nil { + fmt.Printf("warning: failed to update action file for server %s: %v\n", conn.Server().Name, err) + lastErr = err + } + } + return lastErr +} + +// updateConnectorAction updates the action file for a specific connector. +func updateConnectorAction(ctx context.Context, conn Connector) error { + switch c := conn.(type) { + case *SSHConnector: + return c.ensureAction(ctx) + case *AgentConnector: + return c.ensureAction(ctx) + default: + return nil // Local connectors are handled separately + } +} + func newConnectorForServer(server config.Fail2banServer) (Connector, error) { switch server.Type { case "local": diff --git a/pkg/web/handlers.go b/pkg/web/handlers.go index 0939c60..23f4f2d 100644 --- a/pkg/web/handlers.go +++ b/pkg/web/handlers.go @@ -282,6 +282,18 @@ func UpsertServerHandler(c *gin.Context) { return } + // Update action file for this server if it's a remote server (SSH or Agent) and enabled + if server.Enabled && (server.Type == "ssh" || server.Type == "agent") { + // ReloadFromSettings already created the connector, so we can update its action file + // We need to trigger an action file update for this specific server + // Since UpdateActionFiles updates all, we can call it, or we can add a single-server method + // For now, we'll update all remote servers (it's idempotent and ensures consistency) + if err := fail2ban.GetManager().UpdateActionFiles(c.Request.Context()); err != nil { + config.DebugLog("Warning: failed to update some remote action files: %v", err) + // Don't fail the request, just log the warning + } + } + c.JSON(http.StatusOK, gin.H{"server": server}) } @@ -634,6 +646,7 @@ func UpdateSettingsHandler(c *gin.Context) { } config.DebugLog("JSON binding successful, updating settings (handlers.go)") + oldSettings := config.GetSettings() newSettings, err := config.UpdateSettings(req) if err != nil { fmt.Println("Error updating settings:", err) @@ -642,11 +655,22 @@ func UpdateSettingsHandler(c *gin.Context) { } config.DebugLog("Settings updated successfully (handlers.go)") + // Check if callback URL changed - if so, update action files for all active remote servers + callbackURLChanged := oldSettings.CallbackURL != newSettings.CallbackURL + if err := fail2ban.GetManager().ReloadFromSettings(config.GetSettings()); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to reload fail2ban connectors: " + err.Error()}) return } + // Update action files for remote servers if callback URL changed + if callbackURLChanged { + if err := fail2ban.GetManager().UpdateActionFiles(c.Request.Context()); err != nil { + config.DebugLog("Warning: failed to update some remote action files: %v", err) + // Don't fail the request, just log the warning + } + } + c.JSON(http.StatusOK, gin.H{ "message": "Settings updated", "restartNeeded": newSettings.RestartNeeded,