mirror of
https://github.com/swissmakers/fail2ban-ui.git
synced 2026-04-17 14:03:15 +02:00
Consolidate JailInfo function to own connector_global.go and also remove old FetchBanEvents function
This commit is contained in:
@@ -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:
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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")
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user