Consolidate JailInfo function to own connector_global.go and also remove old FetchBanEvents function

This commit is contained in:
2026-02-20 00:02:06 +01:00
parent efd00b2a17
commit a770fccbae
17 changed files with 79 additions and 387 deletions

View File

@@ -61,7 +61,7 @@ podman run -d --name fail2ban-ui --network=host \
Verification: Verification:
* Open `http://localhost:8080` * Open `http://localhost:8080`
* In the UI: Settings → Manage Servers → enable Local connector” and run Test connection” * In the UI: Settings → Manage Servers → enable "Local connector” and run "Test connection”
Next steps: Next steps:

View File

@@ -9,7 +9,6 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"path" "path"
"strconv"
"strings" "strings"
"time" "time"
@@ -157,45 +156,6 @@ func (ac *AgentConnector) SetFilterConfig(ctx context.Context, jail, content str
return ac.put(ctx, fmt.Sprintf("/v1/filters/%s", url.PathEscape(jail)), payload, nil) return ac.put(ctx, fmt.Sprintf("/v1/filters/%s", url.PathEscape(jail)), payload, nil)
} }
func (ac *AgentConnector) FetchBanEvents(ctx context.Context, limit int) ([]BanEvent, error) {
query := url.Values{}
if limit > 0 {
query.Set("limit", strconv.Itoa(limit))
}
var resp struct {
Events []struct {
IP string `json:"ip"`
Jail string `json:"jail"`
Hostname string `json:"hostname"`
Failures string `json:"failures"`
Whois string `json:"whois"`
Logs string `json:"logs"`
Timestamp string `json:"timestamp"`
} `json:"events"`
}
endpoint := "/v1/events"
if encoded := query.Encode(); encoded != "" {
endpoint += "?" + encoded
}
if err := ac.get(ctx, endpoint, &resp); err != nil {
return nil, err
}
result := make([]BanEvent, 0, len(resp.Events))
for _, evt := range resp.Events {
ts, err := time.Parse(time.RFC3339, evt.Timestamp)
if err != nil {
ts = time.Now()
}
result = append(result, BanEvent{
Time: ts,
Jail: evt.Jail,
IP: evt.IP,
LogLine: fmt.Sprintf("%s %s", evt.Hostname, evt.Failures),
})
}
return result, nil
}
// ========================================================================= // =========================================================================
// HTTP Helpers // HTTP Helpers
// ========================================================================= // =========================================================================

View File

@@ -1,6 +1,6 @@
// Fail2ban UI - A Swiss made, management interface for Fail2ban. // Fail2ban UI - A Swiss made, management interface for Fail2ban.
// //
// Copyright (C) 2025 Swissmakers GmbH (https://swissmakers.ch) // Copyright (C) 2026 Swissmakers GmbH (https://swissmakers.ch)
// //
// Licensed under the GNU General Public License, Version 3 (GPL-3.0) // Licensed under the GNU General Public License, Version 3 (GPL-3.0)
// You may not use this file except in compliance with the License. // You may not use this file except in compliance with the License.
@@ -14,14 +14,20 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// Shared types, helpers, and high-level functions used across all connectors.
package fail2ban package fail2ban
import "context" import (
"context"
"sort"
"sync"
)
// ========================================================================= // =========================================================================
// Types // Types
// ========================================================================= // =========================================================================
// JailInfo holds summary data for a single Fail2ban jail.
type JailInfo struct { type JailInfo struct {
JailName string `json:"jailName"` JailName string `json:"jailName"`
TotalBanned int `json:"totalBanned"` TotalBanned int `json:"totalBanned"`
@@ -31,55 +37,10 @@ type JailInfo struct {
} }
// ========================================================================= // =========================================================================
// Client Functions // Service Control
// ========================================================================= // =========================================================================
// Returns jail names from the default server. // RestartFail2ban restarts (or reloads) the Fail2ban service on the given server.
func GetJails() ([]string, error) {
conn, err := GetManager().DefaultConnector()
if err != nil {
return nil, err
}
infos, err := conn.GetJailInfos(context.Background())
if err != nil {
return nil, err
}
names := make([]string, 0, len(infos))
for _, info := range infos {
names = append(names, info.JailName)
}
return names, nil
}
// Returns a slice of currently banned IPs for a specific jail.
func GetBannedIPs(jail string) ([]string, error) {
conn, err := GetManager().DefaultConnector()
if err != nil {
return nil, err
}
return conn.GetBannedIPs(context.Background(), jail)
}
// Unbans an IP from the given jail.
func UnbanIP(jail, ip string) error {
conn, err := GetManager().DefaultConnector()
if err != nil {
return err
}
return conn.UnbanIP(context.Background(), jail, ip)
}
// Returns extended info for each jail on the default server.
func BuildJailInfos(_ string) ([]JailInfo, error) {
conn, err := GetManager().DefaultConnector()
if err != nil {
return nil, err
}
return conn.GetJailInfos(context.Background())
}
// Restarts (or reloads) the Fail2ban service.
func RestartFail2ban(serverID string) (string, error) { func RestartFail2ban(serverID string) (string, error) {
manager := GetManager() manager := GetManager()
var ( var (
@@ -104,3 +65,61 @@ func RestartFail2ban(serverID string) (string, error) {
} }
return "restart", nil return "restart", nil
} }
// =========================================================================
// Jail Info Collection
// =========================================================================
// bannedIPsFn is the signature used by any connector's GetBannedIPs method.
type bannedIPsFn func(ctx context.Context, jail string) ([]string, error)
// collectJailInfos fans out to fetch banned IPs for each jail concurrently,
// then returns the results sorted alphabetically. Both the local and SSH
// connectors delegate to this function from their GetJailInfos methods.
func collectJailInfos(ctx context.Context, jails []string, getBannedIPs bannedIPsFn) ([]JailInfo, error) {
type jailResult struct {
jail JailInfo
err error
}
results := make(chan jailResult, len(jails))
var wg sync.WaitGroup
for _, jail := range jails {
wg.Add(1)
go func(j string) {
defer wg.Done()
ips, err := getBannedIPs(ctx, j)
if err != nil {
results <- jailResult{err: err}
return
}
results <- jailResult{
jail: JailInfo{
JailName: j,
TotalBanned: len(ips),
BannedIPs: ips,
Enabled: true,
},
}
}(jail)
}
go func() {
wg.Wait()
close(results)
}()
var infos []JailInfo
for r := range results {
if r.err != nil {
continue
}
infos = append(infos, r.jail)
}
sort.SliceStable(infos, func(i, j int) bool {
return infos[i].JailName < infos[j].JailName
})
return infos, nil
}

View File

@@ -6,10 +6,7 @@ import (
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
"sort"
"strings" "strings"
"sync"
"time"
"github.com/swissmakers/fail2ban-ui/internal/config" "github.com/swissmakers/fail2ban-ui/internal/config"
) )
@@ -36,71 +33,13 @@ func (lc *LocalConnector) Server() config.Fail2banServer {
return lc.server return lc.server
} }
// Get jail information. // Collects jail status for every active local jail.
func (lc *LocalConnector) GetJailInfos(ctx context.Context) ([]JailInfo, error) { func (lc *LocalConnector) GetJailInfos(ctx context.Context) ([]JailInfo, error) {
jails, err := lc.getJails(ctx) jails, err := lc.getJails(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
logPath := lc.server.LogPath // LEGACY, WILL BE REMOVED IN FUTURE VERSIONS. return collectJailInfos(ctx, jails, lc.GetBannedIPs)
if logPath == "" {
logPath = "/var/log/fail2ban.log"
}
banHistory, err := ParseBanLog(logPath) // LEGACY, WILL BE REMOVED IN FUTURE VERSIONS.
if err != nil {
banHistory = make(map[string][]BanEvent)
}
oneHourAgo := time.Now().Add(-1 * time.Hour)
type jailResult struct {
jail JailInfo
err error
}
results := make(chan jailResult, len(jails))
var wg sync.WaitGroup
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 <- jailResult{
jail: JailInfo{
JailName: j,
TotalBanned: len(bannedIPs),
NewInLastHour: newInLastHour,
BannedIPs: bannedIPs,
Enabled: true,
},
}
}(jail)
}
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
} }
// Get banned IPs for a given jail. // Get banned IPs for a given jail.
@@ -196,29 +135,6 @@ func (lc *LocalConnector) SetFilterConfig(ctx context.Context, jail, content str
return SetFilterConfigLocal(jail, content) return SetFilterConfigLocal(jail, content)
} }
// REMOVE THIS FUNCTION
func (lc *LocalConnector) FetchBanEvents(ctx context.Context, limit int) ([]BanEvent, error) {
logPath := lc.server.LogPath
if logPath == "" {
logPath = "/var/log/fail2ban.log"
}
eventsByJail, err := ParseBanLog(logPath)
if err != nil {
return nil, err
}
var all []BanEvent
for _, evs := range eventsByJail {
all = append(all, evs...)
}
sort.SliceStable(all, func(i, j int) bool {
return all[i].Time.After(all[j].Time)
})
if limit > 0 && len(all) > limit {
all = all[:limit]
}
return all, nil
}
// Get all jails. // Get all jails.
func (lc *LocalConnector) getJails(ctx context.Context) ([]string, error) { func (lc *LocalConnector) getJails(ctx context.Context) ([]string, error) {
out, err := lc.runFail2banClient(ctx, "status") out, err := lc.runFail2banClient(ctx, "status")

View File

@@ -84,55 +84,13 @@ func (sc *SSHConnector) Server() config.Fail2banServer {
return sc.server return sc.server
} }
// Get jail infos for all jails. // Collects jail status for every active remote jail.
func (sc *SSHConnector) GetJailInfos(ctx context.Context) ([]JailInfo, error) { func (sc *SSHConnector) GetJailInfos(ctx context.Context) ([]JailInfo, error) {
jails, err := sc.getJails(ctx) jails, err := sc.getJails(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return collectJailInfos(ctx, jails, sc.GetBannedIPs)
type jailResult struct {
jail JailInfo
err error
}
results := make(chan jailResult, len(jails))
var wg sync.WaitGroup
for _, jail := range jails {
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, result.jail)
}
sort.SliceStable(infos, func(i, j int) bool {
return infos[i].JailName < infos[j].JailName
})
return infos, nil
} }
// Get banned IPs for a given jail. // Get banned IPs for a given jail.
@@ -252,10 +210,6 @@ func (sc *SSHConnector) SetFilterConfig(ctx context.Context, filterName, content
return nil return nil
} }
func (sc *SSHConnector) FetchBanEvents(ctx context.Context, limit int) ([]BanEvent, error) {
return []BanEvent{}, nil
}
func (sc *SSHConnector) ensureAction(ctx context.Context) error { func (sc *SSHConnector) ensureAction(ctx context.Context) error {
callbackURL := config.GetCallbackURL() callbackURL := config.GetCallbackURL()
settings := config.GetSettings() settings := config.GetSettings()

View File

@@ -1,86 +0,0 @@
// Fail2ban UI - A Swiss made, management interface for Fail2ban.
//
// Copyright (C) 2025 Swissmakers GmbH (https://swissmakers.ch)
//
// Licensed under the GNU General Public License, Version 3 (GPL-3.0)
// You may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.gnu.org/licenses/gpl-3.0.en.html
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// LEGACY, WILL BE REMOVED IN FUTURE VERSIONS.
package fail2ban
import (
"bufio"
"fmt"
"os"
"regexp"
"time"
)
// =========================================================================
// Types
// =========================================================================
var logRegex = regexp.MustCompile(`^(\S+\s+\S+) fail2ban\.actions.*?\[\d+\]: NOTICE\s+\[(\S+)\]\s+Ban\s+(\S+)`)
// This is a single ban event from the fail2ban log. REMOVE THIS TYPE.
type BanEvent struct {
Time time.Time
Jail string
IP string
LogLine string
}
// =========================================================================
// Log Parsing
// =========================================================================
// ParseBanLog reads the fail2ban log and returns events grouped by jail.
func ParseBanLog(logPath string) (map[string][]BanEvent, error) {
file, err := os.Open(logPath)
if err != nil {
return nil, fmt.Errorf("failed to open fail2ban log: %v", err)
}
defer file.Close()
eventsByJail := make(map[string][]BanEvent)
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
matches := logRegex.FindStringSubmatch(line)
if len(matches) == 4 {
timestampStr := matches[1]
jail := matches[2]
ip := matches[3]
parsedTime, err := time.Parse("2006-01-02 15:04:05,000", timestampStr)
if err != nil {
continue
}
ev := BanEvent{
Time: parsedTime,
Jail: jail,
IP: ip,
LogLine: line,
}
eventsByJail[jail] = append(eventsByJail[jail], ev)
}
}
if err := scanner.Err(); err != nil {
return nil, err
}
return eventsByJail, nil
}

View File

@@ -25,7 +25,6 @@ type Connector interface {
Restart(ctx context.Context) error Restart(ctx context.Context) error
GetFilterConfig(ctx context.Context, jail string) (string, string, error) GetFilterConfig(ctx context.Context, jail string) (string, string, error)
SetFilterConfig(ctx context.Context, jail, content string) error SetFilterConfig(ctx context.Context, jail, content string) error
FetchBanEvents(ctx context.Context, limit int) ([]BanEvent, error)
// Jail management // Jail management
GetAllJails(ctx context.Context) ([]JailInfo, error) GetAllJails(ctx context.Context) ([]JailInfo, error)

View File

@@ -309,7 +309,6 @@
"servers.form.socket_path": "Fail2ban-Socket-Pfad", "servers.form.socket_path": "Fail2ban-Socket-Pfad",
"servers.form.socket_path_placeholder": "/var/run/fail2ban/fail2ban.sock", "servers.form.socket_path_placeholder": "/var/run/fail2ban/fail2ban.sock",
"servers.form.log_path": "Fail2ban-Logpfad", "servers.form.log_path": "Fail2ban-Logpfad",
"servers.form.log_path_placeholder": "/var/log/fail2ban.log",
"servers.form.hostname": "Server-Hostname", "servers.form.hostname": "Server-Hostname",
"servers.form.hostname_placeholder": "optional", "servers.form.hostname_placeholder": "optional",
"servers.form.ssh_user": "SSH-Benutzer", "servers.form.ssh_user": "SSH-Benutzer",

View File

@@ -309,7 +309,6 @@
"servers.form.socket_path": "Fail2ban-Socket-Pfad", "servers.form.socket_path": "Fail2ban-Socket-Pfad",
"servers.form.socket_path_placeholder": "/var/run/fail2ban/fail2ban.sock", "servers.form.socket_path_placeholder": "/var/run/fail2ban/fail2ban.sock",
"servers.form.log_path": "Fail2ban-Logpfad", "servers.form.log_path": "Fail2ban-Logpfad",
"servers.form.log_path_placeholder": "/var/log/fail2ban.log",
"servers.form.hostname": "Server-Hostname", "servers.form.hostname": "Server-Hostname",
"servers.form.hostname_placeholder": "optional", "servers.form.hostname_placeholder": "optional",
"servers.form.ssh_user": "SSH-Benutzer", "servers.form.ssh_user": "SSH-Benutzer",

View File

@@ -309,7 +309,6 @@
"servers.form.socket_path": "Fail2ban Socket Path", "servers.form.socket_path": "Fail2ban Socket Path",
"servers.form.socket_path_placeholder": "/var/run/fail2ban/fail2ban.sock", "servers.form.socket_path_placeholder": "/var/run/fail2ban/fail2ban.sock",
"servers.form.log_path": "Fail2ban Log Path", "servers.form.log_path": "Fail2ban Log Path",
"servers.form.log_path_placeholder": "/var/log/fail2ban.log",
"servers.form.hostname": "Server Hostname", "servers.form.hostname": "Server Hostname",
"servers.form.hostname_placeholder": "optional", "servers.form.hostname_placeholder": "optional",
"servers.form.ssh_user": "SSH User", "servers.form.ssh_user": "SSH User",

View File

@@ -309,7 +309,6 @@
"servers.form.socket_path": "Ruta del socket de Fail2ban", "servers.form.socket_path": "Ruta del socket de Fail2ban",
"servers.form.socket_path_placeholder": "/var/run/fail2ban/fail2ban.sock", "servers.form.socket_path_placeholder": "/var/run/fail2ban/fail2ban.sock",
"servers.form.log_path": "Ruta del log de Fail2ban", "servers.form.log_path": "Ruta del log de Fail2ban",
"servers.form.log_path_placeholder": "/var/log/fail2ban.log",
"servers.form.hostname": "Nombre de host del servidor", "servers.form.hostname": "Nombre de host del servidor",
"servers.form.hostname_placeholder": "opcional", "servers.form.hostname_placeholder": "opcional",
"servers.form.ssh_user": "Usuario SSH", "servers.form.ssh_user": "Usuario SSH",

View File

@@ -309,7 +309,6 @@
"servers.form.socket_path": "Chemin du socket Fail2ban", "servers.form.socket_path": "Chemin du socket Fail2ban",
"servers.form.socket_path_placeholder": "/var/run/fail2ban/fail2ban.sock", "servers.form.socket_path_placeholder": "/var/run/fail2ban/fail2ban.sock",
"servers.form.log_path": "Chemin du log Fail2ban", "servers.form.log_path": "Chemin du log Fail2ban",
"servers.form.log_path_placeholder": "/var/log/fail2ban.log",
"servers.form.hostname": "Nom d'hôte du serveur", "servers.form.hostname": "Nom d'hôte du serveur",
"servers.form.hostname_placeholder": "optionnel", "servers.form.hostname_placeholder": "optionnel",
"servers.form.ssh_user": "Utilisateur SSH", "servers.form.ssh_user": "Utilisateur SSH",

View File

@@ -309,7 +309,6 @@
"servers.form.socket_path": "Percorso del socket Fail2ban", "servers.form.socket_path": "Percorso del socket Fail2ban",
"servers.form.socket_path_placeholder": "/var/run/fail2ban/fail2ban.sock", "servers.form.socket_path_placeholder": "/var/run/fail2ban/fail2ban.sock",
"servers.form.log_path": "Percorso del log Fail2ban", "servers.form.log_path": "Percorso del log Fail2ban",
"servers.form.log_path_placeholder": "/var/log/fail2ban.log",
"servers.form.hostname": "Nome host del server", "servers.form.hostname": "Nome host del server",
"servers.form.hostname_placeholder": "opzionale", "servers.form.hostname_placeholder": "opzionale",
"servers.form.ssh_user": "Utente SSH", "servers.form.ssh_user": "Utente SSH",

View File

@@ -102,7 +102,6 @@ type ServerRecord struct {
Host string Host string
Port int Port int
SocketPath string SocketPath string
LogPath string
SSHUser string SSHUser string
SSHKeyPath string SSHKeyPath string
AgentURL string AgentURL string
@@ -344,7 +343,7 @@ func ListServers(ctx context.Context) ([]ServerRecord, error) {
} }
rows, err := db.QueryContext(ctx, ` rows, err := db.QueryContext(ctx, `
SELECT id, name, type, host, port, socket_path, log_path, ssh_user, ssh_key_path, agent_url, agent_secret, hostname, tags, is_default, enabled, needs_restart, created_at, updated_at SELECT id, name, type, host, port, socket_path, ssh_user, ssh_key_path, agent_url, agent_secret, hostname, tags, is_default, enabled, needs_restart, created_at, updated_at
FROM servers FROM servers
ORDER BY created_at`) ORDER BY created_at`)
if err != nil { if err != nil {
@@ -355,7 +354,7 @@ ORDER BY created_at`)
var records []ServerRecord var records []ServerRecord
for rows.Next() { for rows.Next() {
var rec ServerRecord var rec ServerRecord
var host, socket, logPath, sshUser, sshKey, agentURL, agentSecret, hostname, tags sql.NullString var host, socket, sshUser, sshKey, agentURL, agentSecret, hostname, tags sql.NullString
var name, serverType sql.NullString var name, serverType sql.NullString
var created, updated sql.NullString var created, updated sql.NullString
var port sql.NullInt64 var port sql.NullInt64
@@ -368,7 +367,6 @@ ORDER BY created_at`)
&host, &host,
&port, &port,
&socket, &socket,
&logPath,
&sshUser, &sshUser,
&sshKey, &sshKey,
&agentURL, &agentURL,
@@ -389,7 +387,6 @@ ORDER BY created_at`)
rec.Host = stringFromNull(host) rec.Host = stringFromNull(host)
rec.Port = intFromNull(port) rec.Port = intFromNull(port)
rec.SocketPath = stringFromNull(socket) rec.SocketPath = stringFromNull(socket)
rec.LogPath = stringFromNull(logPath)
rec.SSHUser = stringFromNull(sshUser) rec.SSHUser = stringFromNull(sshUser)
rec.SSHKeyPath = stringFromNull(sshKey) rec.SSHKeyPath = stringFromNull(sshKey)
rec.AgentURL = stringFromNull(agentURL) rec.AgentURL = stringFromNull(agentURL)
@@ -438,9 +435,9 @@ func ReplaceServers(ctx context.Context, servers []ServerRecord) error {
stmt, err := tx.PrepareContext(ctx, ` stmt, err := tx.PrepareContext(ctx, `
INSERT INTO servers ( INSERT INTO servers (
id, name, type, host, port, socket_path, log_path, ssh_user, ssh_key_path, agent_url, agent_secret, hostname, tags, is_default, enabled, needs_restart, created_at, updated_at id, name, type, host, port, socket_path, ssh_user, ssh_key_path, agent_url, agent_secret, hostname, tags, is_default, enabled, needs_restart, created_at, updated_at
) VALUES ( ) VALUES (
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
)`) )`)
if err != nil { if err != nil {
return err return err
@@ -463,7 +460,6 @@ INSERT INTO servers (
srv.Host, srv.Host,
srv.Port, srv.Port,
srv.SocketPath, srv.SocketPath,
srv.LogPath,
srv.SSHUser, srv.SSHUser,
srv.SSHKeyPath, srv.SSHKeyPath,
srv.AgentURL, srv.AgentURL,
@@ -1028,7 +1024,6 @@ CREATE TABLE IF NOT EXISTS servers (
host TEXT, host TEXT,
port INTEGER, port INTEGER,
socket_path TEXT, socket_path TEXT,
log_path TEXT,
ssh_user TEXT, ssh_user TEXT,
ssh_key_path TEXT, ssh_key_path TEXT,
agent_url TEXT, agent_url TEXT,

View File

@@ -2313,42 +2313,6 @@ func DeleteFilterHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("Filter '%s' deleted successfully", filterName)}) c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("Filter '%s' deleted successfully", filterName)})
} }
// Updates /etc/fail2ban/jail.local [DEFAULT] with our JSON
// TODO: @matthias we need to further enhance this
func ApplyFail2banSettings(jailLocalPath string) error {
config.DebugLog("----------------------------")
config.DebugLog("ApplyFail2banSettings called (handlers.go)")
s := config.GetSettings()
// open /etc/fail2ban/jail.local, parse or do a simplistic approach:
// TODO: -> maybe we store [DEFAULT] block in memory, replace lines
// or do a line-based approach. Example is simplistic:
chain := s.Chain
if chain == "" {
chain = "INPUT"
}
newLines := []string{
"[DEFAULT]",
fmt.Sprintf("enabled = %t", s.DefaultJailEnable),
fmt.Sprintf("bantime.increment = %t", s.BantimeIncrement),
fmt.Sprintf("ignoreip = %s", strings.Join(s.IgnoreIPs, " ")),
fmt.Sprintf("bantime = %s", s.Bantime),
fmt.Sprintf("findtime = %s", s.Findtime),
fmt.Sprintf("maxretry = %d", s.Maxretry),
fmt.Sprintf("banaction = %s", s.Banaction),
fmt.Sprintf("banaction_allports = %s", s.BanactionAllports),
fmt.Sprintf("chain = %s", chain),
}
if s.BantimeRndtime != "" {
newLines = append(newLines, fmt.Sprintf("bantime.rndtime = %s", s.BantimeRndtime))
}
newLines = append(newLines, "")
content := strings.Join(newLines, "\n")
return os.WriteFile(jailLocalPath, []byte(content), 0644)
}
// ========================================================================= // =========================================================================
// Restart // Restart
// ========================================================================= // =========================================================================
@@ -2484,18 +2448,6 @@ func getEmailStyle() string {
return "modern" return "modern"
} }
// Checks whether "LOTR" is among the configured alert countries.
func isLOTRModeActive(alertCountries []string) bool {
if len(alertCountries) == 0 {
return false
}
for _, country := range alertCountries {
if strings.EqualFold(country, "LOTR") {
return true
}
}
return false
}
// Connects to the SMTP server and delivers a single HTML message. // Connects to the SMTP server and delivers a single HTML message.
func sendEmail(to, subject, body string, settings config.AppSettings) error { func sendEmail(to, subject, body string, settings config.AppSettings) error {
@@ -3005,7 +2957,7 @@ func sendBanAlert(ip, jail, hostname, failures, whois, logs, country string, set
if lang == "" { if lang == "" {
lang = "en" lang = "en"
} }
isLOTRMode := isLOTRModeActive(settings.AlertCountries) isLOTRMode := config.IsLOTRModeActive(settings.AlertCountries)
var subject string var subject string
if isLOTRMode { if isLOTRMode {
subject = fmt.Sprintf("[Middle-earth] %s: %s %s %s", subject = fmt.Sprintf("[Middle-earth] %s: %s %s %s",
@@ -3092,7 +3044,7 @@ func sendUnbanAlert(ip, jail, hostname, whois, country string, settings config.A
if lang == "" { if lang == "" {
lang = "en" lang = "en"
} }
isLOTRMode := isLOTRModeActive(settings.AlertCountries) isLOTRMode := config.IsLOTRModeActive(settings.AlertCountries)
var subject string var subject string
if isLOTRMode { if isLOTRMode {
subject = fmt.Sprintf("[Middle-earth] %s: %s %s %s", subject = fmt.Sprintf("[Middle-earth] %s: %s %s %s",

View File

@@ -218,7 +218,6 @@ function resetServerForm() {
document.getElementById('serverHost').value = ''; document.getElementById('serverHost').value = '';
document.getElementById('serverPort').value = '22'; document.getElementById('serverPort').value = '22';
document.getElementById('serverSocket').value = '/var/run/fail2ban/fail2ban.sock'; document.getElementById('serverSocket').value = '/var/run/fail2ban/fail2ban.sock';
document.getElementById('serverLogPath').value = '/var/log/fail2ban.log';
document.getElementById('serverHostname').value = ''; document.getElementById('serverHostname').value = '';
document.getElementById('serverSSHUser').value = ''; document.getElementById('serverSSHUser').value = '';
document.getElementById('serverSSHKey').value = ''; document.getElementById('serverSSHKey').value = '';
@@ -240,7 +239,6 @@ function editServer(serverId) {
document.getElementById('serverHost').value = server.host || ''; document.getElementById('serverHost').value = server.host || '';
document.getElementById('serverPort').value = server.port || ''; document.getElementById('serverPort').value = server.port || '';
document.getElementById('serverSocket').value = server.socketPath || '/var/run/fail2ban/fail2ban.sock'; document.getElementById('serverSocket').value = server.socketPath || '/var/run/fail2ban/fail2ban.sock';
document.getElementById('serverLogPath').value = server.logPath || '/var/log/fail2ban.log';
document.getElementById('serverHostname').value = server.hostname || ''; document.getElementById('serverHostname').value = server.hostname || '';
document.getElementById('serverSSHUser').value = server.sshUser || ''; document.getElementById('serverSSHUser').value = server.sshUser || '';
document.getElementById('serverSSHKey').value = server.sshKeyPath || ''; document.getElementById('serverSSHKey').value = server.sshKeyPath || '';
@@ -299,7 +297,6 @@ function submitServerForm(event) {
host: document.getElementById('serverHost').value.trim(), host: document.getElementById('serverHost').value.trim(),
port: document.getElementById('serverPort').value ? parseInt(document.getElementById('serverPort').value, 10) : undefined, port: document.getElementById('serverPort').value ? parseInt(document.getElementById('serverPort').value, 10) : undefined,
socketPath: document.getElementById('serverSocket').value.trim(), socketPath: document.getElementById('serverSocket').value.trim(),
logPath: document.getElementById('serverLogPath').value.trim(),
hostname: document.getElementById('serverHostname').value.trim(), hostname: document.getElementById('serverHostname').value.trim(),
sshUser: document.getElementById('serverSSHUser').value.trim(), sshUser: document.getElementById('serverSSHUser').value.trim(),
sshKeyPath: document.getElementById('serverSSHKey').value.trim(), sshKeyPath: document.getElementById('serverSSHKey').value.trim(),
@@ -311,7 +308,6 @@ function submitServerForm(event) {
enabled: document.getElementById('serverEnabled').checked enabled: document.getElementById('serverEnabled').checked
}; };
if (!payload.socketPath) delete payload.socketPath; if (!payload.socketPath) delete payload.socketPath;
if (!payload.logPath) delete payload.logPath;
if (!payload.hostname) delete payload.hostname; if (!payload.hostname) delete payload.hostname;
if (!payload.agentUrl) delete payload.agentUrl; if (!payload.agentUrl) delete payload.agentUrl;
if (!payload.agentSecret) delete payload.agentSecret; if (!payload.agentSecret) delete payload.agentSecret;
@@ -324,9 +320,6 @@ function submitServerForm(event) {
if (payload.type !== 'local' && payload.type !== 'ssh') { if (payload.type !== 'local' && payload.type !== 'ssh') {
delete payload.socketPath; delete payload.socketPath;
} }
if (payload.type !== 'local') {
delete payload.logPath;
}
if (payload.type !== 'ssh') { if (payload.type !== 'ssh') {
delete payload.sshUser; delete payload.sshUser;
delete payload.sshKeyPath; delete payload.sshKeyPath;

View File

@@ -1210,10 +1210,6 @@
<label for="serverSocket" class="block text-sm font-medium text-gray-700 mb-1" data-i18n="servers.form.socket_path">Fail2ban Socket Path</label> <label for="serverSocket" class="block text-sm font-medium text-gray-700 mb-1" data-i18n="servers.form.socket_path">Fail2ban Socket Path</label>
<input type="text" id="serverSocket" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" data-i18n-placeholder="servers.form.socket_path_placeholder" placeholder="/var/run/fail2ban/fail2ban.sock"> <input type="text" id="serverSocket" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" data-i18n-placeholder="servers.form.socket_path_placeholder" placeholder="/var/run/fail2ban/fail2ban.sock">
</div> </div>
<div data-server-fields="local">
<label for="serverLogPath" class="block text-sm font-medium text-gray-700 mb-1" data-i18n="servers.form.log_path">Fail2ban Log Path</label>
<input type="text" id="serverLogPath" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" data-i18n-placeholder="servers.form.log_path_placeholder" placeholder="/var/log/fail2ban.log">
</div>
<div data-server-fields="local"> <div data-server-fields="local">
<label for="serverHostname" class="block text-sm font-medium text-gray-700 mb-1" data-i18n="servers.form.hostname">Server Hostname</label> <label for="serverHostname" class="block text-sm font-medium text-gray-700 mb-1" data-i18n="servers.form.hostname">Server Hostname</label>
<input type="text" id="serverHostname" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" data-i18n-placeholder="servers.form.hostname_placeholder" placeholder="optional"> <input type="text" id="serverHostname" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" data-i18n-placeholder="servers.form.hostname_placeholder" placeholder="optional">