Update screenshots and pre-fix the filter-test

This commit is contained in:
2025-11-12 19:09:01 +01:00
parent 3b118cb616
commit 0134b7de5e
13 changed files with 75 additions and 76 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -295,7 +295,8 @@ func (sc *SSHConnector) GetAllJails(ctx context.Context) ([]JailInfo, error) {
}
// Parse jail.d directory
jailDList, err := sc.runRemoteCommand(ctx, []string{"sudo", "bash", "-c", "for f in /etc/fail2ban/jail.d/*.conf; do [ -f \"$f\" ] && echo \"$f\"; done"})
jailDCmd := "sudo find /etc/fail2ban/jail.d -maxdepth 1 -name '*.conf' -type f"
jailDList, err := sc.runRemoteCommand(ctx, []string{"sh", "-c", jailDCmd})
if err == nil && jailDList != "" {
for _, file := range strings.Split(jailDList, "\n") {
file = strings.TrimSpace(file)
@@ -351,15 +352,45 @@ func (sc *SSHConnector) UpdateJailEnabledStates(ctx context.Context, updates map
// GetFilters implements Connector.
func (sc *SSHConnector) GetFilters(ctx context.Context) ([]string, error) {
list, err := sc.runRemoteCommand(ctx, []string{"sudo", "bash", "-c", "for f in /etc/fail2ban/filter.d/*.conf; do [ -f \"$f\" ] && basename \"$f\" .conf; done"})
// Use find with sudo - execute sudo separately to avoid shell issues
// First try with sudo, if that fails, the error will be clear
list, err := sc.runRemoteCommand(ctx, []string{"sudo", "find", "/etc/fail2ban/filter.d", "-maxdepth", "1", "-type", "f"})
if err != nil {
return nil, fmt.Errorf("failed to list filters: %w", err)
}
// Filter for .conf files and extract names in Go
var filters []string
seen := make(map[string]bool) // Avoid duplicates
for _, line := range strings.Split(list, "\n") {
line = strings.TrimSpace(line)
if line != "" {
filters = append(filters, line)
if line == "" {
continue
}
// Only process .conf files - be strict about the extension
if !strings.HasSuffix(line, ".conf") {
continue
}
// Exclude backup files and other non-filter files
if strings.HasSuffix(line, ".conf.bak") ||
strings.HasSuffix(line, ".conf~") ||
strings.HasSuffix(line, ".conf.old") ||
strings.HasSuffix(line, ".conf.rpmnew") ||
strings.HasSuffix(line, ".conf.rpmsave") ||
strings.Contains(line, "README") {
continue
}
parts := strings.Split(line, "/")
if len(parts) > 0 {
filename := parts[len(parts)-1]
// Double-check it ends with .conf
if !strings.HasSuffix(filename, ".conf") {
continue
}
name := strings.TrimSuffix(filename, ".conf")
if name != "" && !seen[name] {
seen[name] = true
filters = append(filters, name)
}
}
}
return filters, nil
@@ -371,53 +402,42 @@ func (sc *SSHConnector) TestFilter(ctx context.Context, filterName string, logLi
return []string{}, nil
}
// Read filter config remotely
// Sanitize filter name to prevent path traversal
filterName = strings.TrimSpace(filterName)
if filterName == "" {
return nil, fmt.Errorf("filter name cannot be empty")
}
// Remove any path components
filterName = strings.ReplaceAll(filterName, "/", "")
filterName = strings.ReplaceAll(filterName, "..", "")
// Use fail2ban-regex with filter name directly - it handles everything
// Format: fail2ban-regex "log line" /etc/fail2ban/filter.d/filter-name.conf
filterPath := fmt.Sprintf("/etc/fail2ban/filter.d/%s.conf", filterName)
content, err := sc.runRemoteCommand(ctx, []string{"sudo", "cat", filterPath})
if err != nil {
return nil, fmt.Errorf("filter %s not found: %w", filterName, err)
}
// Extract failregex
var failregex string
scanner := bufio.NewScanner(strings.NewReader(content))
inFailregex := false
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if strings.HasPrefix(line, "[Definition]") {
inFailregex = true
continue
}
if inFailregex && strings.HasPrefix(line, "failregex") {
parts := strings.SplitN(line, "=", 2)
if len(parts) == 2 {
failregex = strings.TrimSpace(parts[1])
}
break
}
if inFailregex && strings.HasPrefix(line, "[") {
break
}
}
if failregex == "" {
return nil, fmt.Errorf("no failregex found in filter %s", filterName)
}
// Test each log line remotely
var matches []string
for _, logLine := range logLines {
logLine = strings.TrimSpace(logLine)
if logLine == "" {
continue
}
// Escape the log line and regex for shell
// Use fail2ban-regex: log line as string, filter file path
// Escape the log line for shell safety
escapedLine := strconv.Quote(logLine)
escapedRegex := strconv.Quote(failregex)
cmd := fmt.Sprintf("echo %s | sudo fail2ban-regex - %s", escapedLine, escapedRegex)
out, err := sc.runRemoteCommand(ctx, []string{"bash", "-lc", cmd})
if err == nil && strings.Contains(out, "Success") {
cmd := fmt.Sprintf("sudo fail2ban-regex %s %s", escapedLine, strconv.Quote(filterPath))
out, err := sc.runRemoteCommand(ctx, []string{"sh", "-c", cmd})
// fail2ban-regex returns success (exit 0) if the line matches
// Look for "Lines: 1 lines, 0 ignored, 1 matched" or similar success indicators
if err == nil {
// Check if output indicates a match
output := strings.ToLower(out)
if strings.Contains(output, "matched") ||
strings.Contains(output, "success") ||
strings.Contains(output, "1 matched") {
matches = append(matches, logLine)
}
}
}
return matches, nil
}

View File

@@ -17,7 +17,6 @@
package fail2ban
import (
"bufio"
"context"
"fmt"
"os"
@@ -89,46 +88,26 @@ func TestFilterLocal(filterName string, logLines []string) ([]string, error) {
if _, err := os.Stat(filterPath); err != nil {
return nil, fmt.Errorf("filter %s not found: %w", filterName, err)
}
// Read the filter config to extract the failregex
content, err := os.ReadFile(filterPath)
if err != nil {
return nil, fmt.Errorf("failed to read filter config: %w", err)
}
// Extract failregex from the config
var failregex string
scanner := bufio.NewScanner(strings.NewReader(string(content)))
inFailregex := false
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if strings.HasPrefix(line, "[Definition]") {
inFailregex = true
continue
}
if inFailregex && strings.HasPrefix(line, "failregex") {
parts := strings.SplitN(line, "=", 2)
if len(parts) == 2 {
failregex = strings.TrimSpace(parts[1])
}
break
}
if inFailregex && strings.HasPrefix(line, "[") {
break
}
}
if failregex == "" {
return nil, fmt.Errorf("no failregex found in filter %s", filterName)
}
// Use fail2ban-regex to test
// Use fail2ban-regex with filter file directly - it handles everything
// Format: fail2ban-regex "log line" /etc/fail2ban/filter.d/filter-name.conf
var matches []string
for _, logLine := range logLines {
logLine = strings.TrimSpace(logLine)
if logLine == "" {
continue
}
cmd := exec.Command("fail2ban-regex", logLine, failregex)
cmd := exec.Command("fail2ban-regex", logLine, filterPath)
out, err := cmd.CombinedOutput()
if err == nil && strings.Contains(string(out), "Success") {
output := strings.ToLower(string(out))
// fail2ban-regex returns success (exit 0) if the line matches
// Look for "matched" or "success" in output
if err == nil {
if strings.Contains(output, "matched") ||
strings.Contains(output, "success") ||
strings.Contains(output, "1 matched") {
matches = append(matches, logLine)
}
}
}
return matches, nil
}

View File

@@ -248,7 +248,7 @@
<!-- Textarea for log lines to test -->
<div class="mb-4">
<label for="logLinesTextarea" class="block text-sm font-medium text-gray-700 mb-2" data-i18n="filter_debug.log_lines">Log Lines</label>
<textarea id="logLinesTextarea" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 h-40" disabled
<textarea id="logLinesTextarea" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 h-40"
data-i18n-placeholder="filter_debug.log_lines_placeholder" placeholder="Enter log lines here..."></textarea>
</div>

BIN
screenshots/.DS_Store vendored Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 872 KiB

After

Width:  |  Height:  |  Size: 857 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 970 KiB

After

Width:  |  Height:  |  Size: 870 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 813 KiB

After

Width:  |  Height:  |  Size: 828 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 899 KiB

After

Width:  |  Height:  |  Size: 885 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 848 KiB

After

Width:  |  Height:  |  Size: 911 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 400 KiB

After

Width:  |  Height:  |  Size: 506 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 643 KiB

After

Width:  |  Height:  |  Size: 745 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 571 KiB

After

Width:  |  Height:  |  Size: 592 KiB