Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
bin/
.idea/
.junie/
fail2ban-dashboard
*.sqlite3
generate*
Expand Down
4 changes: 2 additions & 2 deletions e2e/data/script/ban.sh
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#!/bin/bash

echo "will add bans to 'sshd' jail"
fail2ban-client set sshd banip 103.59.94.155 196.251.84.225 218.92.0.247 44.231.227.33 104.196.24.229 154.190.185.126 127.217.0.15 197.173.0.4 202.126.174.77 174.70.43.130 120.250.129.97 141.168.79.112 193.233.120.0 111.5.45.94 82.17.151.75 142.46.182.191 131.44.117.63 40.246.204.192 14.177.157.243 233.74.69.192 65.207.100.156 42.200.128.129 16.10.242.64 184.241.184.255 64.174.224.44 164.41.52.195 47.197.77.185 141.152.72.48 149.178.179.27 164.97.136.88 176.233.216.19 157.36.244.51 109.49.24.54
fail2ban-client set sshd banip 2602:fb54:1a00::10f 103.59.94.155 196.251.84.225 218.92.0.247 44.231.227.33 104.196.24.229 154.190.185.126 127.217.0.15 197.173.0.4 202.126.174.77 174.70.43.130 120.250.129.97 141.168.79.112 193.233.120.0 111.5.45.94 82.17.151.75 142.46.182.191 131.44.117.63 40.246.204.192 14.177.157.243 233.74.69.192 65.207.100.156 42.200.128.129 16.10.242.64 184.241.184.255 64.174.224.44 164.41.52.195 47.197.77.185 141.152.72.48 149.178.179.27 164.97.136.88 176.233.216.19 157.36.244.51 109.49.24.54
fail2ban-client status sshd

echo "will add bans to 'postfix' jail"
fail2ban-client set postfix banip 78.88.88.99 111.151.117.83 205.66.31.57 116.248.244.228 146.89.110.76 164.23.12.99 58.167.196.83 186.198.88.118 68.73.173.96 191.38.166.106 159.80.97.239 1.253.120.60 190.65.166.192
fail2ban-client set postfix banip 2001:db8::dead:beef 78.88.88.99 111.151.117.83 205.66.31.57 116.248.244.228 146.89.110.76 164.23.12.99 58.167.196.83 186.198.88.118 68.73.173.96 191.38.166.106 159.80.97.239 1.253.120.60 190.65.166.192
fail2ban-client status postfix
32 changes: 7 additions & 25 deletions fail2ban-client/fail2ban-client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"errors"
"fmt"
"net"
"regexp"
"strings"
"sync"
"time"
Expand Down Expand Up @@ -59,8 +58,6 @@ type Fail2BanClient struct {
encoder *ogórek.Encoder
}

var banRegex = regexp.MustCompile(`^(\d{1,3}(?:\.\d{1,3}){3})[ \t]+(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) \+ (-?\d+) = (\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})$`)

func NewFail2BanClient(address string) (*Fail2BanClient, error) {
log.Tracef("Attempting to connect to fail2ban socket at %s", address)
socket, err := net.Dial("unix", address)
Expand Down Expand Up @@ -268,32 +265,17 @@ func (f2bc *Fail2BanClient) GetBanned(jailName string) (*JailEntry, error) {
func (f2bc *Fail2BanClient) parseEntry(jailName string, listEntry string) (*BanEntry, error) {
log.Tracef("GetBanned: Parsing ban entry: %s", listEntry)

matches := banRegex.FindStringSubmatch(listEntry)
if matches == nil {
parsed, err := parse(listEntry)
if err != nil {
log.Errorf("GetBanned: Failed to parse ban entry: %s", listEntry)
return nil, errors.New("could not parse banned IPs entry")
}

layout := "2006-01-02 15:04:05" // reference layout

bannedAt, bannedAtErr := time.Parse(layout, matches[2])
if bannedAtErr != nil {
return nil, bannedAtErr
}

banEndsAt, banEndsAtErr := time.Parse(layout, matches[4])
if banEndsAtErr != nil {
return nil, banEndsAtErr
return nil, err
}

ipAddress := matches[1]
currenPenalty := matches[3]

banEntry := &BanEntry{
Address: ipAddress,
BannedAt: bannedAt,
CurrenPenalty: currenPenalty,
BanEndsAt: banEndsAt,
Address: parsed.ipAddress,
BannedAt: parsed.bannedAt,
CurrenPenalty: parsed.currentPenalty,
BanEndsAt: parsed.banEndsAt,
JailName: jailName,
}

Expand Down
39 changes: 38 additions & 1 deletion fail2ban-client/fail2ban-client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -535,9 +535,12 @@ func TestFail2BanClient_GetBanned(t *testing.T) {
&mockList{
items: []interface{}{
"192.168.1.100 \t2023-08-29 10:30:00 + 600 = 2023-08-29 10:40:00",
"2602:fb54:1a00::10f \t2023-08-29 10:30:00 + 600 = 2023-08-29 10:40:00",
"fd12:3456:789a::25 \t2024-08-29 10:30:00 + 600 = 2024-08-29 10:40:00",
"10.0.0.50 \t2023-08-29 11:00:00 + 1800 = 2023-08-29 11:30:00",
"78.88.88.99 \t2026-04-18 20:00:26 + -1 = 9999-12-31 23:59:59",
"13.12.5.54 \t \t2023-08-29 11:00:00 + 1800 = 2023-08-29 11:30:00",
"2001:db8::dead:beef \t \t2023-08-29 11:00:00 + 1800 = 2023-08-29 11:30:00",
},
},
},
Expand All @@ -552,6 +555,20 @@ func TestFail2BanClient_GetBanned(t *testing.T) {
BanEndsAt: time.Date(2023, 8, 29, 10, 40, 0, 0, time.UTC),
JailName: "test-jail",
},
{
Address: "2602:fb54:1a00::10f",
BannedAt: time.Date(2023, 8, 29, 10, 30, 0, 0, time.UTC),
CurrenPenalty: "600",
BanEndsAt: time.Date(2023, 8, 29, 10, 40, 0, 0, time.UTC),
JailName: "test-jail",
},
{
Address: "fd12:3456:789a::25",
BannedAt: time.Date(2024, 8, 29, 10, 30, 0, 0, time.UTC),
CurrenPenalty: "600",
BanEndsAt: time.Date(2024, 8, 29, 10, 40, 0, 0, time.UTC),
JailName: "test-jail",
},
{
Address: "10.0.0.50",
BannedAt: time.Date(2023, 8, 29, 11, 0, 0, 0, time.UTC),
Expand All @@ -573,6 +590,13 @@ func TestFail2BanClient_GetBanned(t *testing.T) {
BanEndsAt: time.Date(2023, 8, 29, 11, 30, 0, 0, time.UTC),
JailName: "test-jail",
},
{
Address: "2001:db8::dead:beef",
BannedAt: time.Date(2023, 8, 29, 11, 0, 0, 0, time.UTC),
CurrenPenalty: "1800",
BanEndsAt: time.Date(2023, 8, 29, 11, 30, 0, 0, time.UTC),
JailName: "test-jail",
},
},
},
wantErr: false,
Expand Down Expand Up @@ -794,7 +818,7 @@ func TestFail2BanClient_parseEntry(t *testing.T) {
wantErr bool
}{
{
name: "valid entry",
name: "valid IPv4 entry",
jailName: "test-jail",
listEntry: "192.168.1.100 \t2023-08-29 10:30:00 + 600 = 2023-08-29 10:40:00",
want: &BanEntry{
Expand All @@ -806,6 +830,19 @@ func TestFail2BanClient_parseEntry(t *testing.T) {
},
wantErr: false,
},
{
name: "valid IPv6 entry",
jailName: "test-jail",
listEntry: "2001:db8::dead:beef \t2023-08-29 10:30:00 + 600 = 2023-08-29 10:40:00",
want: &BanEntry{
Address: "2001:db8::dead:beef",
BannedAt: time.Date(2023, 8, 29, 10, 30, 0, 0, time.UTC),
CurrenPenalty: "600",
BanEndsAt: time.Date(2023, 8, 29, 10, 40, 0, 0, time.UTC),
JailName: "test-jail",
},
wantErr: false,
},
{
name: "valid entry with negative penalty and far future date",
jailName: "sshd",
Expand Down
54 changes: 54 additions & 0 deletions fail2ban-client/parse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package fail2ban_client

import (
"errors"
"net"
"regexp"
"time"

"github.com/gofiber/fiber/v2/log"
)

var banRegex = regexp.MustCompile(`^(\S+)[ \t]+(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) \+ (-?\d+) = (\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})$`)

type parsedEntry struct {
ipAddress string
currentPenalty string
bannedAt time.Time
banEndsAt time.Time
}

func parse(entry string) (*parsedEntry, error) {
matches := banRegex.FindStringSubmatch(entry)
if matches == nil {
log.Errorf("GetBanned: Failed to parse ban entry: %s", entry)
return nil, errors.New("could not parse banned IPs entry")
}

layout := "2006-01-02 15:04:05" // reference layout

bannedAt, bannedAtErr := time.Parse(layout, matches[2])
if bannedAtErr != nil {
return nil, bannedAtErr
}

banEndsAt, banEndsAtErr := time.Parse(layout, matches[4])
if banEndsAtErr != nil {
return nil, banEndsAtErr
}

ipAddress := matches[1]

if net.ParseIP(ipAddress) == nil {
return nil, errors.New("invalid IP address in banned IPs entry")
}

result := &parsedEntry{
ipAddress: ipAddress,
currentPenalty: matches[3],
bannedAt: bannedAt,
banEndsAt: banEndsAt,
}

return result, nil
}
124 changes: 124 additions & 0 deletions fail2ban-client/parse_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package fail2ban_client

import (
"reflect"
"testing"
"time"
)

func Test_parse(t *testing.T) {
layout := "2006-01-02 15:04:05"

t1, _ := time.Parse(layout, "2023-10-27 10:00:00")
t2, _ := time.Parse(layout, "2023-10-27 11:00:00")

tests := []struct {
name string
entry string
want *parsedEntry
wantErr bool
}{
{
name: "valid IPv4",
entry: "1.2.3.4 2023-10-27 10:00:00 + 3600 = 2023-10-27 11:00:00",
want: &parsedEntry{
ipAddress: "1.2.3.4",
currentPenalty: "3600",
bannedAt: t1,
banEndsAt: t2,
},
wantErr: false,
},
{
name: "valid IPv6",
entry: "2001:db8::1 2023-10-27 10:00:00 + 3600 = 2023-10-27 11:00:00",
want: &parsedEntry{
ipAddress: "2001:db8::1",
currentPenalty: "3600",
bannedAt: t1,
banEndsAt: t2,
},
wantErr: false,
},
{
name: "valid IPv6 localhost",
entry: "::1 2023-10-27 10:00:00 + 3600 = 2023-10-27 11:00:00",
want: &parsedEntry{
ipAddress: "::1",
currentPenalty: "3600",
bannedAt: t1,
banEndsAt: t2,
},
wantErr: false,
},
{
name: "valid IPv4",
entry: "5.5.5.5 2023-10-27 10:00:00 + 3600 = 2023-10-27 11:00:00",
want: &parsedEntry{
ipAddress: "5.5.5.5",
currentPenalty: "3600",
bannedAt: t1,
banEndsAt: t2,
},
wantErr: false,
},
{
name: "invalid IP",
entry: "invalid-ip 2023-10-27 10:00:00 + 3600 = 2023-10-27 11:00:00",
want: nil,
wantErr: true,
},
{
name: "invalid bannedAt date",
entry: "1.2.3.4 2023-13-27 10:00:00 + 3600 = 2023-10-27 11:00:00",
want: nil,
wantErr: true,
},
{
name: "invalid banEndsAt date",
entry: "1.2.3.4 2023-10-27 10:00:00 + 3600 = 2023-10-32 11:00:00",
want: nil,
wantErr: true,
},
{
name: "malformed entry",
entry: "1.2.3.4 2023-10-27 10:00:00 + 3600",
want: nil,
wantErr: true,
},
{
name: "valid IPv4 with tabs",
entry: "1.2.3.4\t2023-10-27 10:00:00 + 3600 = 2023-10-27 11:00:00",
want: &parsedEntry{
ipAddress: "1.2.3.4",
currentPenalty: "3600",
bannedAt: t1,
banEndsAt: t2,
},
wantErr: false,
},
{
name: "valid IPv6 with tabs",
entry: "2001:db8::1\t2023-10-27 10:00:00 + 3600 = 2023-10-27 11:00:00",
want: &parsedEntry{
ipAddress: "2001:db8::1",
currentPenalty: "3600",
bannedAt: t1,
banEndsAt: t2,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := parse(tt.entry)
if (err != nil) != tt.wantErr {
t.Errorf("parse() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("parse() got = %v, want %v", got, tt.want)
}
})
}
}
Loading
Loading