From def440611abe4cd184bb634f128e694bcd0812f1 Mon Sep 17 00:00:00 2001 From: Michael Reber Date: Thu, 4 Dec 2025 20:36:23 +0100 Subject: [PATCH] Dedublicate banner stuff and add missing init-vars for banactions --- internal/config/settings.go | 49 ++++++++++++++++++++-------- internal/fail2ban/connector_ssh.go | 29 ++++++++-------- internal/fail2ban/jail_management.go | 32 +++++++++++++----- 3 files changed, 75 insertions(+), 35 deletions(-) diff --git a/internal/config/settings.go b/internal/config/settings.go index a008d85..84e9f94 100644 --- a/internal/config/settings.go +++ b/internal/config/settings.go @@ -132,6 +132,24 @@ const ( actionServerIDPlaceholder = "__SERVER_ID__" ) +// jailLocalBanner is the standard banner for jail.local files +const jailLocalBanner = `################################################################################ +# Fail2Ban-UI Managed Configuration +# +# WARNING: This file is automatically managed by Fail2Ban-UI. +# DO NOT EDIT THIS FILE MANUALLY - your changes will be overwritten. +# +# This file overrides settings from /etc/fail2ban/jail.conf +# Custom jail configurations should be placed in /etc/fail2ban/jail.d/ +################################################################################ + +` + +// JailLocalBanner returns the standard banner for jail.local files +func JailLocalBanner() string { + return jailLocalBanner +} + const fail2banActionTemplate = `[INCLUDES] before = sendmail-common.conf @@ -771,18 +789,8 @@ func ensureJailLocalStructure() error { return updateJailLocalDefaultSection(settings) } - // Build the banner - banner := `################################################################################ -# Fail2Ban-UI Managed Configuration -# -# WARNING: This file is automatically managed by Fail2Ban-UI. -# DO NOT EDIT THIS FILE MANUALLY - your changes will be overwritten. -# -# This file overrides settings from /etc/fail2ban/jail.conf -# Custom jail configurations should be placed in /etc/fail2ban/jail.d/ -################################################################################ - -` + // Use the standard banner + banner := jailLocalBanner // Build [DEFAULT] section // Convert IgnoreIPs array to space-separated string @@ -846,7 +854,8 @@ func updateJailLocalDefaultSection(settings AppSettings) error { return fmt.Errorf("failed to read jail.local: %w", err) } - lines := strings.Split(string(content), "\n") + contentStr := string(content) + lines := strings.Split(contentStr, "\n") var outputLines []string inDefault := false defaultUpdated := false @@ -878,9 +887,23 @@ func updateJailLocalDefaultSection(settings AppSettings) error { } keysUpdated := make(map[string]bool) + // Always add the full banner at the start + outputLines = append(outputLines, strings.Split(strings.TrimRight(jailLocalBanner, "\n"), "\n")...) + + // Skip everything before [DEFAULT] section (old banner, comments, empty lines) + foundSection := false for _, line := range lines { trimmed := strings.TrimSpace(line) + if strings.HasPrefix(trimmed, "[") && strings.HasSuffix(trimmed, "]") { + // Found a section - stop skipping and process this line + foundSection = true + } + if !foundSection { + // Skip lines before any section (old banner, comments, empty lines) + continue + } + // Process lines after we found a section if strings.HasPrefix(trimmed, "[") && strings.HasSuffix(trimmed, "]") { sectionName := strings.Trim(trimmed, "[]") if sectionName == "DEFAULT" { diff --git a/internal/fail2ban/connector_ssh.go b/internal/fail2ban/connector_ssh.go index e813369..f1f0c14 100644 --- a/internal/fail2ban/connector_ssh.go +++ b/internal/fail2ban/connector_ssh.go @@ -782,6 +782,7 @@ jail_file = '%s' ignore_ip_str = '%s' banaction_val = '%s' banaction_allports_val = '%s' +banner_content = """%s""" settings = { 'bantime_increment': %t, 'ignoreip': ignore_ip_str, @@ -816,13 +817,23 @@ if has_full_banner and has_action_mwlg and has_action_override: except FileNotFoundError: lines = [] + # Always add the full banner at the start output_lines = [] - in_default = False - keys_updated = set() + output_lines.extend(banner_content.splitlines()) + output_lines.append('') + # Skip everything before [DEFAULT] section (old banner, comments, empty lines) + found_section = False for line in lines: stripped = line.strip() + if stripped.startswith('[') and stripped.endswith(']'): + # Found a section - stop skipping and process this line + found_section = True + if not found_section: + # Skip lines before any section (old banner, comments, empty lines) + continue + # Process lines after we found a section if stripped.startswith('[') and stripped.endswith(']'): section_name = stripped.strip('[]') if section_name == "DEFAULT": @@ -876,17 +887,7 @@ if has_full_banner and has_action_mwlg and has_action_override: f.writelines(output_lines) else: # Create new structure - banner = """################################################################################ -# Fail2Ban-UI Managed Configuration -# -# WARNING: This file is automatically managed by Fail2Ban-UI. -# DO NOT EDIT THIS FILE MANUALLY - your changes will be overwritten. -# -# This file overrides settings from /etc/fail2ban/jail.conf -# Custom jail configurations should be placed in /etc/fail2ban/jail.d/ -################################################################################ - -""" + banner = banner_content default_section = """[DEFAULT] bantime.increment = """ + str(settings['bantime_increment']) + """ @@ -914,7 +915,7 @@ action = %%(action_mwlg)s with open(jail_file, 'w') as f: f.write(new_content) -PY`, escapeForShell(jailLocalPath), escapeForShell(ignoreIPStr), escapeForShell(banactionVal), escapeForShell(banactionAllportsVal), settings.BantimeIncrement, +PY`, escapeForShell(jailLocalPath), escapeForShell(ignoreIPStr), escapeForShell(banactionVal), escapeForShell(banactionAllportsVal), escapeForShell(config.JailLocalBanner()), settings.BantimeIncrement, escapeForShell(settings.Bantime), escapeForShell(settings.Findtime), settings.Maxretry, escapeForShell(settings.Destemail)) _, err := sc.runRemoteCommand(ctx, []string{"bash", "-lc", ensureScript}) diff --git a/internal/fail2ban/jail_management.go b/internal/fail2ban/jail_management.go index d836663..3f0462b 100644 --- a/internal/fail2ban/jail_management.go +++ b/internal/fail2ban/jail_management.go @@ -627,17 +627,19 @@ func UpdateDefaultSettingsLocal(settings config.AppSettings) error { // Parse existing content and update only specific keys in DEFAULT section if existingContent == "" { - // File doesn't exist, create new one with DEFAULT section - defaultLines := []string{"[DEFAULT]"} - for _, key := range []string{"bantime.increment", "ignoreip", "bantime", "findtime", "maxretry", "destemail"} { - defaultLines = append(defaultLines, keysToUpdate[key]) + // File doesn't exist, create new one with banner and DEFAULT section + var newLines []string + newLines = append(newLines, strings.Split(strings.TrimRight(config.JailLocalBanner(), "\n"), "\n")...) + newLines = append(newLines, "[DEFAULT]") + for _, key := range []string{"bantime.increment", "ignoreip", "bantime", "findtime", "maxretry", "destemail", "banaction", "banaction_allports"} { + newLines = append(newLines, keysToUpdate[key]) } - defaultLines = append(defaultLines, "") - newContent := strings.Join(defaultLines, "\n") + newLines = append(newLines, "") + newContent := strings.Join(newLines, "\n") if err := os.WriteFile(localPath, []byte(newContent), 0644); err != nil { return fmt.Errorf("failed to write jail.local: %w", err) } - config.DebugLog("Created new jail.local with DEFAULT section") + config.DebugLog("Created new jail.local with banner and DEFAULT section") return nil } @@ -647,9 +649,23 @@ func UpdateDefaultSettingsLocal(settings config.AppSettings) error { inDefault := false defaultSectionFound := false + // Always add the full banner at the start + outputLines = append(outputLines, strings.Split(strings.TrimRight(config.JailLocalBanner(), "\n"), "\n")...) + + // Skip everything before [DEFAULT] section (old banner, comments, empty lines) + foundSection := false for _, line := range lines { trimmed := strings.TrimSpace(line) + if strings.HasPrefix(trimmed, "[") && strings.HasSuffix(trimmed, "]") { + // Found a section - stop skipping and process this line + foundSection = true + } + if !foundSection { + // Skip lines before any section (old banner, comments, empty lines) + continue + } + // Process lines after we found a section if strings.HasPrefix(trimmed, "[") && strings.HasSuffix(trimmed, "]") { sectionName := strings.Trim(trimmed, "[]") if sectionName == "DEFAULT" { @@ -695,7 +711,7 @@ func UpdateDefaultSettingsLocal(settings config.AppSettings) error { outputLines = append(defaultLines, outputLines...) } else { // Add any missing keys to the DEFAULT section - for _, key := range []string{"bantime.increment", "ignoreip", "bantime", "findtime", "maxretry", "destemail"} { + for _, key := range []string{"bantime.increment", "ignoreip", "bantime", "findtime", "maxretry", "destemail", "banaction", "banaction_allports"} { if !keysUpdated[key] { // Find the DEFAULT section and insert after it for i, line := range outputLines {