mirror of
https://github.com/swissmakers/fail2ban-ui.git
synced 2026-04-11 13:47:05 +02:00
Fix remote SSH jail.local pharsing
This commit is contained in:
@@ -993,8 +993,8 @@ jail_file = '%s'
|
||||
ignore_ip_str = '%s'
|
||||
banaction_val = '%s'
|
||||
banaction_allports_val = '%s'
|
||||
default_jail_enable_val = %t
|
||||
bantime_increment_val = %t
|
||||
default_jail_enable_val = '%t'
|
||||
bantime_increment_val = '%t'
|
||||
bantime_val = '%s'
|
||||
findtime_val = '%s'
|
||||
maxretry_val = %d
|
||||
@@ -1082,6 +1082,10 @@ PY`, escapeForShell(jailLocalPath), escapeForShell(ignoreIPStr), escapeForShell(
|
||||
}
|
||||
|
||||
// EnsureJailLocalStructure implements Connector.
|
||||
// For SSH connectors we:
|
||||
// 1. Migrate any legacy jails out of jail.local into jail.d/*.local
|
||||
// 2. Rebuild /etc/fail2ban/jail.local with a clean, managed structure
|
||||
// (banner, [DEFAULT] section based on current settings, and action_mwlg/action override).
|
||||
func (sc *SSHConnector) EnsureJailLocalStructure(ctx context.Context) error {
|
||||
jailLocalPath := "/etc/fail2ban/jail.local"
|
||||
settings := config.GetSettings()
|
||||
@@ -1091,6 +1095,7 @@ func (sc *SSHConnector) EnsureJailLocalStructure(ctx context.Context) error {
|
||||
if ignoreIPStr == "" {
|
||||
ignoreIPStr = "127.0.0.1/8 ::1"
|
||||
}
|
||||
|
||||
// Set default banaction values if not set
|
||||
banactionVal := settings.Banaction
|
||||
if banactionVal == "" {
|
||||
@@ -1100,170 +1105,63 @@ func (sc *SSHConnector) EnsureJailLocalStructure(ctx context.Context) error {
|
||||
if banactionAllportsVal == "" {
|
||||
banactionAllportsVal = "iptables-allports"
|
||||
}
|
||||
// Escape values for shell/Python
|
||||
escapeForShell := func(s string) string {
|
||||
return strings.ReplaceAll(s, "'", "'\"'\"'")
|
||||
}
|
||||
|
||||
// Build the structure using Python script
|
||||
ensureScript := fmt.Sprintf(`python3 <<'PY'
|
||||
import os
|
||||
import re
|
||||
// Build the new jail.local content in Go (mirrors local ensureJailLocalStructure)
|
||||
banner := config.JailLocalBanner()
|
||||
|
||||
jail_file = '%s'
|
||||
ignore_ip_str = '%s'
|
||||
banaction_val = '%s'
|
||||
banaction_allports_val = '%s'
|
||||
banner_content = """%s"""
|
||||
settings = {
|
||||
'bantime_increment': %t,
|
||||
'default_jail_enable': %t,
|
||||
'ignoreip': ignore_ip_str,
|
||||
'bantime': '%s',
|
||||
'findtime': '%s',
|
||||
'maxretry': %d,
|
||||
'destemail': '%s',
|
||||
'banaction': banaction_val,
|
||||
'banaction_allports': banaction_allports_val
|
||||
}
|
||||
defaultSection := fmt.Sprintf(`[DEFAULT]
|
||||
enabled = %t
|
||||
bantime.increment = %t
|
||||
ignoreip = %s
|
||||
bantime = %s
|
||||
findtime = %s
|
||||
maxretry = %d
|
||||
destemail = %s
|
||||
banaction = %s
|
||||
banaction_allports = %s
|
||||
|
||||
# Check if file already has our full banner (indicating it's already properly structured)
|
||||
has_full_banner = False
|
||||
has_action_mwlg = False
|
||||
has_action_override = False
|
||||
`,
|
||||
settings.DefaultJailEnable,
|
||||
settings.BantimeIncrement,
|
||||
ignoreIPStr,
|
||||
settings.Bantime,
|
||||
settings.Findtime,
|
||||
settings.Maxretry,
|
||||
settings.Destemail,
|
||||
banactionVal,
|
||||
banactionAllportsVal,
|
||||
)
|
||||
|
||||
try:
|
||||
with open(jail_file, 'r') as f:
|
||||
content = f.read()
|
||||
# Check for the complete banner pattern with hash line separators
|
||||
has_full_banner = '################################################################################' in content and 'Fail2Ban-UI Managed Configuration' in content and 'DO NOT EDIT THIS FILE MANUALLY' in content
|
||||
has_action_mwlg = 'action_mwlg' in content and 'ui-custom-action' in content
|
||||
has_action_override = 'action = %%(action_mwlg)s' in content
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
actionMwlgConfig := `# Custom Fail2Ban action using geo-filter for email alerts
|
||||
action_mwlg = %(action_)s
|
||||
ui-custom-action[sender="%(sender)s", dest="%(destemail)s", logpath="%(logpath)s", chain="%(chain)s"]
|
||||
|
||||
# If already properly structured, just update DEFAULT section
|
||||
if has_full_banner and has_action_mwlg and has_action_override:
|
||||
try:
|
||||
with open(jail_file, 'r') as f:
|
||||
lines = f.readlines()
|
||||
except FileNotFoundError:
|
||||
lines = []
|
||||
`
|
||||
|
||||
# Always add the full banner at the start
|
||||
output_lines = []
|
||||
output_lines.extend(banner_content.splitlines())
|
||||
output_lines.append('')
|
||||
actionOverride := `# Custom Fail2Ban action applied by fail2ban-ui
|
||||
action = %(action_mwlg)s
|
||||
`
|
||||
|
||||
# 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
|
||||
content := banner + defaultSection + actionMwlgConfig + actionOverride
|
||||
|
||||
# Process lines after we found a section
|
||||
if stripped.startswith('[') and stripped.endswith(']'):
|
||||
section_name = stripped.strip('[]')
|
||||
if section_name == "DEFAULT":
|
||||
in_default = True
|
||||
output_lines.append(line)
|
||||
else:
|
||||
in_default = False
|
||||
output_lines.append(line)
|
||||
elif in_default:
|
||||
key_updated = False
|
||||
for key, new_value in [
|
||||
('enabled', 'enabled = ' + str(settings['default_jail_enable'])),
|
||||
('bantime.increment', 'bantime.increment = ' + str(settings['bantime_increment'])),
|
||||
('ignoreip', 'ignoreip = ' + settings['ignoreip']),
|
||||
('bantime', 'bantime = ' + settings['bantime']),
|
||||
('findtime', 'findtime = ' + settings['findtime']),
|
||||
('maxretry', 'maxretry = ' + str(settings['maxretry'])),
|
||||
('destemail', 'destemail = ' + settings['destemail']),
|
||||
('banaction', 'banaction = ' + settings['banaction']),
|
||||
('banaction_allports', 'banaction_allports = ' + settings['banaction_allports']),
|
||||
]:
|
||||
pattern = r'^\s*' + re.escape(key) + r'\s*='
|
||||
if re.match(pattern, stripped):
|
||||
output_lines.append(new_value + '\n')
|
||||
keys_updated.add(key)
|
||||
key_updated = True
|
||||
break
|
||||
if not key_updated:
|
||||
output_lines.append(line)
|
||||
else:
|
||||
output_lines.append(line)
|
||||
|
||||
# Add missing keys
|
||||
if in_default:
|
||||
for key, new_value in [
|
||||
('enabled', 'enabled = ' + str(settings['default_jail_enable'])),
|
||||
('bantime.increment', 'bantime.increment = ' + str(settings['bantime_increment'])),
|
||||
('ignoreip', 'ignoreip = ' + settings['ignoreip']),
|
||||
('bantime', 'bantime = ' + settings['bantime']),
|
||||
('findtime', 'findtime = ' + settings['findtime']),
|
||||
('maxretry', 'maxretry = ' + str(settings['maxretry'])),
|
||||
('destemail', 'destemail = ' + settings['destemail']),
|
||||
('banaction', 'banaction = ' + settings['banaction']),
|
||||
('banaction_allports', 'banaction_allports = ' + settings['banaction_allports']),
|
||||
]:
|
||||
if key not in keys_updated:
|
||||
for i, output_line in enumerate(output_lines):
|
||||
if output_line.strip() == "[DEFAULT]":
|
||||
output_lines.insert(i + 1, new_value + '\n')
|
||||
break
|
||||
|
||||
with open(jail_file, 'w') as f:
|
||||
f.writelines(output_lines)
|
||||
else:
|
||||
# Create new structure
|
||||
banner = banner_content
|
||||
|
||||
default_section = """[DEFAULT]
|
||||
enabled = """ + str(settings['default_jail_enable']) + """
|
||||
bantime.increment = """ + str(settings['bantime_increment']) + """
|
||||
ignoreip = """ + settings['ignoreip'] + """
|
||||
bantime = """ + settings['bantime'] + """
|
||||
findtime = """ + settings['findtime'] + """
|
||||
maxretry = """ + str(settings['maxretry']) + """
|
||||
destemail = """ + settings['destemail'] + """
|
||||
banaction = """ + settings['banaction'] + """
|
||||
banaction_allports = """ + settings['banaction_allports'] + """
|
||||
|
||||
"""
|
||||
|
||||
action_mwlg_config = """# Custom Fail2Ban action using geo-filter for email alerts
|
||||
action_mwlg = %%(action_)s
|
||||
ui-custom-action[sender="%%(sender)s", dest="%%(destemail)s", logpath="%%(logpath)s", chain="%%(chain)s"]
|
||||
|
||||
"""
|
||||
|
||||
action_override = """# Custom Fail2Ban action applied by fail2ban-ui
|
||||
action = %%(action_mwlg)s
|
||||
"""
|
||||
|
||||
new_content = banner + default_section + action_mwlg_config + action_override
|
||||
|
||||
with open(jail_file, 'w') as f:
|
||||
f.write(new_content)
|
||||
PY`, escapeForShell(jailLocalPath), escapeForShell(ignoreIPStr), escapeForShell(banactionVal), escapeForShell(banactionAllportsVal), escapeForShell(config.JailLocalBanner()), settings.BantimeIncrement, settings.DefaultJailEnable,
|
||||
escapeForShell(settings.Bantime), escapeForShell(settings.Findtime), settings.Maxretry, escapeForShell(settings.Destemail))
|
||||
// Escape single quotes for safe use in a single-quoted heredoc
|
||||
escaped := strings.ReplaceAll(content, "'", "'\"'\"'")
|
||||
|
||||
// IMPORTANT: Run migration FIRST before ensuring structure
|
||||
// This is because ensureJailLocalStructure may overwrite jail.local,
|
||||
// which would destroy any jail sections that need to be migrated
|
||||
// This is because EnsureJailLocalStructure may overwrite jail.local,
|
||||
// which would destroy any jail sections that need to be migrated.
|
||||
if err := sc.MigrateJailsFromJailLocalRemote(ctx); err != nil {
|
||||
config.DebugLog("Warning: No migration done (may be normal if no jails to migrate): %v", err)
|
||||
// Don't fail - continue with ensuring structure
|
||||
}
|
||||
|
||||
// Then ensure the basic structure
|
||||
_, err := sc.runRemoteCommand(ctx, []string{"bash", "-lc", ensureScript})
|
||||
// Write the rebuilt content via heredoc over SSH
|
||||
writeScript := fmt.Sprintf(`cat > %s <<'JAILLOCAL'
|
||||
%s
|
||||
JAILLOCAL
|
||||
`, jailLocalPath, escaped)
|
||||
|
||||
_, err := sc.runRemoteCommand(ctx, []string{"bash", "-lc", writeScript})
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user