mirror of
https://github.com/swissmakers/fail2ban-ui.git
synced 2026-04-11 13:47:05 +02:00
Fix the manage jails functions over ssh, also improve the speed or remote connections
This commit is contained in:
@@ -249,3 +249,45 @@ func (ac *AgentConnector) do(req *http.Request, out any) error {
|
||||
}
|
||||
return json.Unmarshal(data, out)
|
||||
}
|
||||
|
||||
// GetAllJails implements Connector.
|
||||
func (ac *AgentConnector) GetAllJails(ctx context.Context) ([]JailInfo, error) {
|
||||
var resp struct {
|
||||
Jails []JailInfo `json:"jails"`
|
||||
}
|
||||
if err := ac.get(ctx, "/v1/jails/all", &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.Jails, nil
|
||||
}
|
||||
|
||||
// UpdateJailEnabledStates implements Connector.
|
||||
func (ac *AgentConnector) UpdateJailEnabledStates(ctx context.Context, updates map[string]bool) error {
|
||||
return ac.post(ctx, "/v1/jails/update-enabled", updates, nil)
|
||||
}
|
||||
|
||||
// GetFilters implements Connector.
|
||||
func (ac *AgentConnector) GetFilters(ctx context.Context) ([]string, error) {
|
||||
var resp struct {
|
||||
Filters []string `json:"filters"`
|
||||
}
|
||||
if err := ac.get(ctx, "/v1/filters", &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.Filters, nil
|
||||
}
|
||||
|
||||
// TestFilter implements Connector.
|
||||
func (ac *AgentConnector) TestFilter(ctx context.Context, filterName string, logLines []string) ([]string, error) {
|
||||
payload := map[string]any{
|
||||
"filterName": filterName,
|
||||
"logLines": logLines,
|
||||
}
|
||||
var resp struct {
|
||||
Matches []string `json:"matches"`
|
||||
}
|
||||
if err := ac.post(ctx, "/v1/filters/test", payload, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.Matches, nil
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"os/exec"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/swissmakers/fail2ban-ui/internal/config"
|
||||
@@ -51,37 +52,61 @@ func (lc *LocalConnector) GetJailInfos(ctx context.Context) ([]JailInfo, error)
|
||||
}
|
||||
|
||||
oneHourAgo := time.Now().Add(-1 * time.Hour)
|
||||
var results []JailInfo
|
||||
for _, jail := range jails {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
// Use parallel execution for better performance
|
||||
type jailResult struct {
|
||||
jail JailInfo
|
||||
err error
|
||||
}
|
||||
results := make(chan jailResult, len(jails))
|
||||
var wg sync.WaitGroup
|
||||
|
||||
bannedIPs, err := lc.GetBannedIPs(ctx, jail)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
newInLastHour := 0
|
||||
if events, ok := banHistory[jail]; ok {
|
||||
for _, e := range events {
|
||||
if e.Time.After(oneHourAgo) {
|
||||
newInLastHour++
|
||||
for _, jail := range jails {
|
||||
wg.Add(1)
|
||||
go func(j string) {
|
||||
defer wg.Done()
|
||||
bannedIPs, err := lc.GetBannedIPs(ctx, j)
|
||||
if err != nil {
|
||||
results <- jailResult{err: err}
|
||||
return
|
||||
}
|
||||
newInLastHour := 0
|
||||
if events, ok := banHistory[j]; ok {
|
||||
for _, e := range events {
|
||||
if e.Time.After(oneHourAgo) {
|
||||
newInLastHour++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
results = append(results, JailInfo{
|
||||
JailName: jail,
|
||||
TotalBanned: len(bannedIPs),
|
||||
NewInLastHour: newInLastHour,
|
||||
BannedIPs: bannedIPs,
|
||||
Enabled: true,
|
||||
})
|
||||
results <- jailResult{
|
||||
jail: JailInfo{
|
||||
JailName: j,
|
||||
TotalBanned: len(bannedIPs),
|
||||
NewInLastHour: newInLastHour,
|
||||
BannedIPs: bannedIPs,
|
||||
Enabled: true,
|
||||
},
|
||||
}
|
||||
}(jail)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(results)
|
||||
}()
|
||||
|
||||
var finalResults []JailInfo
|
||||
for result := range results {
|
||||
if result.err != nil {
|
||||
continue
|
||||
}
|
||||
finalResults = append(finalResults, result.jail)
|
||||
}
|
||||
|
||||
sort.SliceStable(finalResults, func(i, j int) bool {
|
||||
return finalResults[i].JailName < finalResults[j].JailName
|
||||
})
|
||||
return finalResults, nil
|
||||
}
|
||||
|
||||
// GetBannedIPs implements Connector.
|
||||
@@ -207,6 +232,26 @@ func (lc *LocalConnector) buildFail2banArgs(args ...string) []string {
|
||||
return append(base, args...)
|
||||
}
|
||||
|
||||
// GetAllJails implements Connector.
|
||||
func (lc *LocalConnector) GetAllJails(ctx context.Context) ([]JailInfo, error) {
|
||||
return GetAllJails()
|
||||
}
|
||||
|
||||
// UpdateJailEnabledStates implements Connector.
|
||||
func (lc *LocalConnector) UpdateJailEnabledStates(ctx context.Context, updates map[string]bool) error {
|
||||
return UpdateJailEnabledStates(updates)
|
||||
}
|
||||
|
||||
// GetFilters implements Connector.
|
||||
func (lc *LocalConnector) GetFilters(ctx context.Context) ([]string, error) {
|
||||
return GetFiltersLocal()
|
||||
}
|
||||
|
||||
// TestFilter implements Connector.
|
||||
func (lc *LocalConnector) TestFilter(ctx context.Context, filterName string, logLines []string) ([]string, error) {
|
||||
return TestFilterLocal(filterName, logLines)
|
||||
}
|
||||
|
||||
func executeShellCommand(ctx context.Context, command string) (string, error) {
|
||||
parts := strings.Fields(command)
|
||||
if len(parts) == 0 {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package fail2ban
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
@@ -8,11 +9,12 @@ import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/swissmakers/fail2ban-ui/internal/config"
|
||||
)
|
||||
|
||||
const sshEnsureActionScript = `sudo python3 - <<'PY'
|
||||
const sshEnsureActionScript = `python3 - <<'PY'
|
||||
import base64
|
||||
import pathlib
|
||||
|
||||
@@ -90,24 +92,46 @@ func (sc *SSHConnector) GetJailInfos(ctx context.Context) ([]JailInfo, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var infos []JailInfo
|
||||
// Use parallel execution for better performance
|
||||
type jailResult struct {
|
||||
jail JailInfo
|
||||
err error
|
||||
}
|
||||
results := make(chan jailResult, len(jails))
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for _, jail := range jails {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
default:
|
||||
}
|
||||
ips, err := sc.GetBannedIPs(ctx, jail)
|
||||
if err != nil {
|
||||
wg.Add(1)
|
||||
go func(j string) {
|
||||
defer wg.Done()
|
||||
ips, err := sc.GetBannedIPs(ctx, j)
|
||||
if err != nil {
|
||||
results <- jailResult{err: err}
|
||||
return
|
||||
}
|
||||
results <- jailResult{
|
||||
jail: JailInfo{
|
||||
JailName: j,
|
||||
TotalBanned: len(ips),
|
||||
NewInLastHour: 0,
|
||||
BannedIPs: ips,
|
||||
Enabled: true,
|
||||
},
|
||||
}
|
||||
}(jail)
|
||||
}
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(results)
|
||||
}()
|
||||
|
||||
var infos []JailInfo
|
||||
for result := range results {
|
||||
if result.err != nil {
|
||||
continue
|
||||
}
|
||||
infos = append(infos, JailInfo{
|
||||
JailName: jail,
|
||||
TotalBanned: len(ips),
|
||||
NewInLastHour: 0,
|
||||
BannedIPs: ips,
|
||||
Enabled: true,
|
||||
})
|
||||
infos = append(infos, result.jail)
|
||||
}
|
||||
|
||||
sort.SliceStable(infos, func(i, j int) bool {
|
||||
@@ -177,7 +201,10 @@ func (sc *SSHConnector) ensureAction(ctx context.Context) error {
|
||||
actionConfig := config.BuildFail2banActionConfig(callbackURL)
|
||||
payload := base64.StdEncoding.EncodeToString([]byte(actionConfig))
|
||||
script := strings.ReplaceAll(sshEnsureActionScript, "__PAYLOAD__", payload)
|
||||
_, err := sc.runRemoteCommand(ctx, []string{"bash", "-lc", script})
|
||||
// Base64 encode the entire script to avoid shell escaping issues
|
||||
scriptB64 := base64.StdEncoding.EncodeToString([]byte(script))
|
||||
cmd := fmt.Sprintf("echo %s | base64 -d | sudo bash", scriptB64)
|
||||
_, err := sc.runRemoteCommand(ctx, []string{"bash", "-lc", cmd})
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -254,3 +281,176 @@ func (sc *SSHConnector) buildSSHArgs(command []string) []string {
|
||||
args = append(args, command...)
|
||||
return args
|
||||
}
|
||||
|
||||
// GetAllJails implements Connector.
|
||||
func (sc *SSHConnector) GetAllJails(ctx context.Context) ([]JailInfo, error) {
|
||||
// Read jail.local and jail.d files remotely
|
||||
var allJails []JailInfo
|
||||
|
||||
// Parse jail.local
|
||||
jailLocalContent, err := sc.runRemoteCommand(ctx, []string{"sudo", "cat", "/etc/fail2ban/jail.local"})
|
||||
if err == nil {
|
||||
jails := parseJailConfigContent(jailLocalContent)
|
||||
allJails = append(allJails, jails...)
|
||||
}
|
||||
|
||||
// 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"})
|
||||
if err == nil && jailDList != "" {
|
||||
for _, file := range strings.Split(jailDList, "\n") {
|
||||
file = strings.TrimSpace(file)
|
||||
if file == "" {
|
||||
continue
|
||||
}
|
||||
content, err := sc.runRemoteCommand(ctx, []string{"sudo", "cat", file})
|
||||
if err == nil {
|
||||
jails := parseJailConfigContent(content)
|
||||
allJails = append(allJails, jails...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allJails, nil
|
||||
}
|
||||
|
||||
// UpdateJailEnabledStates implements Connector.
|
||||
func (sc *SSHConnector) UpdateJailEnabledStates(ctx context.Context, updates map[string]bool) error {
|
||||
// Read current jail.local
|
||||
content, err := sc.runRemoteCommand(ctx, []string{"sudo", "cat", "/etc/fail2ban/jail.local"})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read jail.local: %w", err)
|
||||
}
|
||||
|
||||
// Update enabled states
|
||||
lines := strings.Split(content, "\n")
|
||||
var outputLines []string
|
||||
var currentJail string
|
||||
for _, line := range lines {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if strings.HasPrefix(trimmed, "[") && strings.HasSuffix(trimmed, "]") {
|
||||
currentJail = strings.Trim(trimmed, "[]")
|
||||
outputLines = append(outputLines, line)
|
||||
} else if strings.HasPrefix(trimmed, "enabled") {
|
||||
if val, ok := updates[currentJail]; ok {
|
||||
outputLines = append(outputLines, fmt.Sprintf("enabled = %t", val))
|
||||
delete(updates, currentJail)
|
||||
} else {
|
||||
outputLines = append(outputLines, line)
|
||||
}
|
||||
} else {
|
||||
outputLines = append(outputLines, line)
|
||||
}
|
||||
}
|
||||
|
||||
// Write back
|
||||
newContent := strings.Join(outputLines, "\n")
|
||||
cmd := fmt.Sprintf("cat <<'EOF' | sudo tee /etc/fail2ban/jail.local >/dev/null\n%s\nEOF", newContent)
|
||||
_, err = sc.runRemoteCommand(ctx, []string{"bash", "-lc", cmd})
|
||||
return err
|
||||
}
|
||||
|
||||
// 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"})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list filters: %w", err)
|
||||
}
|
||||
var filters []string
|
||||
for _, line := range strings.Split(list, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line != "" {
|
||||
filters = append(filters, line)
|
||||
}
|
||||
}
|
||||
return filters, nil
|
||||
}
|
||||
|
||||
// TestFilter implements Connector.
|
||||
func (sc *SSHConnector) TestFilter(ctx context.Context, filterName string, logLines []string) ([]string, error) {
|
||||
if len(logLines) == 0 {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
// Read filter config remotely
|
||||
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 {
|
||||
if logLine == "" {
|
||||
continue
|
||||
}
|
||||
// Escape the log line and regex for shell
|
||||
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") {
|
||||
matches = append(matches, logLine)
|
||||
}
|
||||
}
|
||||
return matches, nil
|
||||
}
|
||||
|
||||
// parseJailConfigContent parses jail configuration content and returns JailInfo slice.
|
||||
func parseJailConfigContent(content string) []JailInfo {
|
||||
var jails []JailInfo
|
||||
scanner := bufio.NewScanner(strings.NewReader(content))
|
||||
var currentJail string
|
||||
enabled := true
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
|
||||
if currentJail != "" && currentJail != "DEFAULT" {
|
||||
jails = append(jails, JailInfo{
|
||||
JailName: currentJail,
|
||||
Enabled: enabled,
|
||||
})
|
||||
}
|
||||
currentJail = strings.Trim(line, "[]")
|
||||
enabled = true
|
||||
} else if strings.HasPrefix(strings.ToLower(line), "enabled") {
|
||||
parts := strings.Split(line, "=")
|
||||
if len(parts) == 2 {
|
||||
value := strings.TrimSpace(parts[1])
|
||||
enabled = strings.EqualFold(value, "true")
|
||||
}
|
||||
}
|
||||
}
|
||||
if currentJail != "" && currentJail != "DEFAULT" {
|
||||
jails = append(jails, JailInfo{
|
||||
JailName: currentJail,
|
||||
Enabled: enabled,
|
||||
})
|
||||
}
|
||||
return jails
|
||||
}
|
||||
|
||||
@@ -17,10 +17,13 @@
|
||||
package fail2ban
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetFilterConfig returns the filter configuration using the default connector.
|
||||
@@ -59,3 +62,73 @@ func SetFilterConfigLocal(jail, newContent string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetFiltersLocal returns a list of filter names from /etc/fail2ban/filter.d
|
||||
func GetFiltersLocal() ([]string, error) {
|
||||
dir := "/etc/fail2ban/filter.d"
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read filter directory: %w", err)
|
||||
}
|
||||
var filters []string
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".conf") {
|
||||
name := strings.TrimSuffix(entry.Name(), ".conf")
|
||||
filters = append(filters, name)
|
||||
}
|
||||
}
|
||||
return filters, nil
|
||||
}
|
||||
|
||||
// TestFilterLocal tests a filter against log lines using fail2ban-regex
|
||||
func TestFilterLocal(filterName string, logLines []string) ([]string, error) {
|
||||
if len(logLines) == 0 {
|
||||
return []string{}, nil
|
||||
}
|
||||
filterPath := filepath.Join("/etc/fail2ban/filter.d", filterName+".conf")
|
||||
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
|
||||
var matches []string
|
||||
for _, logLine := range logLines {
|
||||
if logLine == "" {
|
||||
continue
|
||||
}
|
||||
cmd := exec.Command("fail2ban-regex", logLine, failregex)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err == nil && strings.Contains(string(out), "Success") {
|
||||
matches = append(matches, logLine)
|
||||
}
|
||||
}
|
||||
return matches, nil
|
||||
}
|
||||
|
||||
@@ -21,6 +21,14 @@ type Connector interface {
|
||||
GetFilterConfig(ctx context.Context, jail string) (string, error)
|
||||
SetFilterConfig(ctx context.Context, jail, content string) error
|
||||
FetchBanEvents(ctx context.Context, limit int) ([]BanEvent, error)
|
||||
|
||||
// Jail management
|
||||
GetAllJails(ctx context.Context) ([]JailInfo, error)
|
||||
UpdateJailEnabledStates(ctx context.Context, updates map[string]bool) error
|
||||
|
||||
// Filter operations
|
||||
GetFilters(ctx context.Context) ([]string, error)
|
||||
TestFilter(ctx context.Context, filterName string, logLines []string) ([]string, error)
|
||||
}
|
||||
|
||||
// Manager orchestrates all connectors for configured Fail2ban servers.
|
||||
|
||||
@@ -578,9 +578,12 @@ func SetJailFilterConfigHandler(c *gin.Context) {
|
||||
func ManageJailsHandler(c *gin.Context) {
|
||||
config.DebugLog("----------------------------")
|
||||
config.DebugLog("ManageJailsHandler called (handlers.go)") // entry point
|
||||
// Get all jails from jail.local and jail.d directories.
|
||||
// This helper should parse both files and return []fail2ban.JailInfo.
|
||||
jails, err := fail2ban.GetAllJails()
|
||||
conn, err := resolveConnector(c)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
jails, err := conn.GetAllJails(c.Request.Context())
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to load jails: " + err.Error()})
|
||||
return
|
||||
@@ -594,21 +597,21 @@ func ManageJailsHandler(c *gin.Context) {
|
||||
func UpdateJailManagementHandler(c *gin.Context) {
|
||||
config.DebugLog("----------------------------")
|
||||
config.DebugLog("UpdateJailManagementHandler called (handlers.go)") // entry point
|
||||
conn, err := resolveConnector(c)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
var updates map[string]bool
|
||||
if err := c.ShouldBindJSON(&updates); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid JSON: " + err.Error()})
|
||||
return
|
||||
}
|
||||
// Update jail configuration file(s) with the new enabled states.
|
||||
if err := fail2ban.UpdateJailEnabledStates(updates); err != nil {
|
||||
if err := conn.UpdateJailEnabledStates(c.Request.Context(), updates); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update jail settings: " + err.Error()})
|
||||
return
|
||||
}
|
||||
// Restart the Fail2ban service.
|
||||
//if err := fail2ban.RestartFail2ban(); err != nil {
|
||||
// c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to reload fail2ban: " + err.Error()})
|
||||
// return
|
||||
//}
|
||||
if err := config.MarkRestartNeeded(); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
@@ -669,43 +672,35 @@ func ListFiltersHandler(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
server := conn.Server()
|
||||
if server.Type != "local" {
|
||||
c.JSON(http.StatusOK, gin.H{"filters": []string{}, "messageKey": "filter_debug.not_available"})
|
||||
return
|
||||
}
|
||||
|
||||
dir := "/etc/fail2ban/filter.d"
|
||||
if _, statErr := os.Stat(dir); statErr != nil {
|
||||
if os.IsNotExist(statErr) {
|
||||
c.JSON(http.StatusOK, gin.H{"filters": []string{}, "messageKey": "filter_debug.local_missing"})
|
||||
if server.Type == "local" {
|
||||
// For local, check if directory exists first
|
||||
dir := "/etc/fail2ban/filter.d"
|
||||
if _, statErr := os.Stat(dir); statErr != nil {
|
||||
if os.IsNotExist(statErr) {
|
||||
c.JSON(http.StatusOK, gin.H{"filters": []string{}, "messageKey": "filter_debug.local_missing"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read filter directory: " + statErr.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read filter directory: " + statErr.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
files, err := os.ReadDir(dir)
|
||||
filters, err := conn.GetFilters(c.Request.Context())
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "Failed to read filter directory: " + err.Error(),
|
||||
})
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to list filters: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
var filters []string
|
||||
for _, f := range files {
|
||||
if !f.IsDir() && strings.HasSuffix(f.Name(), ".conf") {
|
||||
name := strings.TrimSuffix(f.Name(), ".conf")
|
||||
filters = append(filters, name)
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"filters": filters})
|
||||
}
|
||||
|
||||
func TestFilterHandler(c *gin.Context) {
|
||||
config.DebugLog("----------------------------")
|
||||
config.DebugLog("TestFilterHandler called (handlers.go)") // entry point
|
||||
conn, err := resolveConnector(c)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
var req struct {
|
||||
FilterName string `json:"filterName"`
|
||||
LogLines []string `json:"logLines"`
|
||||
@@ -715,8 +710,12 @@ func TestFilterHandler(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// For now, just pretend nothing matches
|
||||
c.JSON(http.StatusOK, gin.H{"matches": []string{}})
|
||||
matches, err := conn.TestFilter(c.Request.Context(), req.FilterName, req.LogLines)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to test filter: " + err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"matches": matches})
|
||||
}
|
||||
|
||||
// ApplyFail2banSettings updates /etc/fail2ban/jail.local [DEFAULT] with our JSON
|
||||
|
||||
Reference in New Issue
Block a user