Compare commits
10 Commits
v0.3.0
...
26f4f17760
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26f4f17760 | ||
|
|
3001282d88 | ||
|
|
9198f19805 | ||
|
|
b6e92a2a57 | ||
|
|
16a174cf56 | ||
|
|
e275a73460 | ||
|
|
2dcc3eaa7b | ||
|
|
322f5161cb | ||
|
|
4e80b5148d | ||
|
|
1d74c6142b |
@@ -13,7 +13,7 @@ gitea_urls:
|
|||||||
builds:
|
builds:
|
||||||
- id: banforge
|
- id: banforge
|
||||||
main: ./cmd/banforge/main.go
|
main: ./cmd/banforge/main.go
|
||||||
binary: banforge-{{ .Version }}-{{ .Os }}-{{ .Arch }}
|
binary: banforge
|
||||||
ignore:
|
ignore:
|
||||||
- goos: windows
|
- goos: windows
|
||||||
- goos: darwin
|
- goos: darwin
|
||||||
@@ -23,10 +23,10 @@ builds:
|
|||||||
goarch:
|
goarch:
|
||||||
- amd64
|
- amd64
|
||||||
- arm64
|
- arm64
|
||||||
env:
|
|
||||||
- CGO_ENABLED=0
|
|
||||||
ldflags:
|
ldflags:
|
||||||
- "-s -w"
|
- "-s -w"
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=0
|
||||||
archives:
|
archives:
|
||||||
- format: tar.gz
|
- format: tar.gz
|
||||||
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
|
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
|
||||||
@@ -45,7 +45,9 @@ nfpms:
|
|||||||
- rpm
|
- rpm
|
||||||
- archlinux
|
- archlinux
|
||||||
bindir: /usr/bin
|
bindir: /usr/bin
|
||||||
|
scripts:
|
||||||
|
postinstall: build/postinstall.sh
|
||||||
|
postremove: build/postremove.sh
|
||||||
release:
|
release:
|
||||||
gitea:
|
gitea:
|
||||||
owner: d3m0k1d
|
owner: d3m0k1d
|
||||||
|
|||||||
61
build/postinstall.sh
Normal file
61
build/postinstall.sh
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
if command -v systemctl >/dev/null 2>&1; then
|
||||||
|
# for systemd based systems
|
||||||
|
banforge init
|
||||||
|
cat > /etc/systemd/system/banforge.service << 'EOF'
|
||||||
|
[Unit]
|
||||||
|
Description=BanForge - IPS log based system
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
Documentation=https://github.com/d3m0k1d/BanForge
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/usr/local/bin/banforge daemon
|
||||||
|
User=root
|
||||||
|
Group=root
|
||||||
|
Restart=always
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
SyslogIdentifier=banforge
|
||||||
|
TimeoutStopSec=90
|
||||||
|
KillSignal=SIGTERM
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
chmod 644 /etc/systemd/system/banforge.service
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable banforge
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v rc-service >/dev/null 2>&1; then
|
||||||
|
# for openrc based systems
|
||||||
|
banforge init
|
||||||
|
cat > /etc/init.d/banforge << 'EOF'
|
||||||
|
#!/sbin/openrc-run
|
||||||
|
|
||||||
|
description="BanForge - IPS log based system"
|
||||||
|
command="/usr/bin/banforge"
|
||||||
|
command_args="daemon"
|
||||||
|
|
||||||
|
pidfile="/run/${RC_SVCNAME}.pid"
|
||||||
|
command_background="yes"
|
||||||
|
|
||||||
|
depend() {
|
||||||
|
need net
|
||||||
|
after network
|
||||||
|
}
|
||||||
|
|
||||||
|
start_post() {
|
||||||
|
einfo "BanForge is now running"
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_post() {
|
||||||
|
einfo "BanForge is now stopped"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
chmod 755 /etc/init.d/banforge
|
||||||
|
rc-update add banforge
|
||||||
|
fi
|
||||||
20
build/postremove.sh
Normal file
20
build/postremove.sh
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
if command -v systemctl >/dev/null 2>&1; then
|
||||||
|
# for systemd based systems
|
||||||
|
systemctl stop banforge 2>/dev/null || true
|
||||||
|
systemctl disable banforge 2>/dev/null || true
|
||||||
|
rm -f /etc/systemd/system/banforge.service
|
||||||
|
systemctl daemon-reload
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v rc-service >/dev/null 2>&1; then
|
||||||
|
# for openrc based systems
|
||||||
|
rc-service banforge stop 2>/dev/null || true
|
||||||
|
rc-update del banforge 2>/dev/null || true
|
||||||
|
rm -f /etc/init.d/banforge
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf /etc/banforge/
|
||||||
|
rm -rf /var/lib/banforge/
|
||||||
|
rm -rf /var/log/banforge/
|
||||||
@@ -61,15 +61,14 @@ var DaemonCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
var scanners []*parser.Scanner
|
||||||
|
|
||||||
for _, svc := range cfg.Service {
|
for _, svc := range cfg.Service {
|
||||||
log.Info(
|
log.Info(
|
||||||
"Processing service",
|
"Processing service",
|
||||||
"name",
|
"name", svc.Name,
|
||||||
svc.Name,
|
"enabled", svc.Enabled,
|
||||||
"enabled",
|
"path", svc.LogPath,
|
||||||
svc.Enabled,
|
|
||||||
"path",
|
|
||||||
svc.LogPath,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if !svc.Enabled {
|
if !svc.Enabled {
|
||||||
@@ -77,30 +76,80 @@ var DaemonCmd = &cobra.Command{
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if svc.Name != "nginx" {
|
log.Info("Starting parser for service", "name", svc.Name, "path", svc.LogPath)
|
||||||
log.Info("Only nginx supported, skipping", "name", svc.Name)
|
if svc.Logging != "file" && svc.Logging != "journald" {
|
||||||
|
log.Error("Invalid logging type", "type", svc.Logging)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("Starting parser for service", "name", svc.Name, "path", svc.LogPath)
|
if svc.Logging == "file" {
|
||||||
|
log.Info("Logging to file", "path", svc.LogPath)
|
||||||
pars, err := parser.NewScanner(svc.LogPath)
|
pars, err := parser.NewScannerTail(svc.LogPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to create scanner", "service", svc.Name, "error", err)
|
log.Error("Failed to create scanner", "service", svc.Name, "error", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scanners = append(scanners, pars)
|
||||||
|
|
||||||
go pars.Start()
|
go pars.Start()
|
||||||
defer pars.Stop()
|
|
||||||
go func(p *parser.Scanner, serviceName string) {
|
go func(p *parser.Scanner, serviceName string) {
|
||||||
|
if svc.Name == "nginx" {
|
||||||
log.Info("Starting nginx parser", "service", serviceName)
|
log.Info("Starting nginx parser", "service", serviceName)
|
||||||
ng := parser.NewNginxParser()
|
ng := parser.NewNginxParser()
|
||||||
resultCh := make(chan *storage.LogEntry, 100)
|
resultCh := make(chan *storage.LogEntry, 100)
|
||||||
ng.Parse(p.Events(), resultCh)
|
ng.Parse(p.Events(), resultCh)
|
||||||
go storage.Write(db, resultCh)
|
go storage.Write(db, resultCh)
|
||||||
}(pars, svc.Name)
|
|
||||||
}
|
}
|
||||||
|
if svc.Name == "ssh" {
|
||||||
|
log.Info("Starting ssh parser", "service", serviceName)
|
||||||
|
ssh := parser.NewSshdParser()
|
||||||
|
resultCh := make(chan *storage.LogEntry, 100)
|
||||||
|
ssh.Parse(p.Events(), resultCh)
|
||||||
|
go storage.Write(db, resultCh)
|
||||||
|
}
|
||||||
|
}(pars, svc.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if svc.Logging == "journald" {
|
||||||
|
log.Info("Logging to journald", "path", svc.LogPath)
|
||||||
|
pars, err := parser.NewScannerJournald(svc.LogPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to create scanner", "service", svc.Name, "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
scanners = append(scanners, pars)
|
||||||
|
|
||||||
|
go pars.Start()
|
||||||
|
go func(p *parser.Scanner, serviceName string) {
|
||||||
|
if svc.Name == "nginx" {
|
||||||
|
log.Info("Starting nginx parser", "service", serviceName)
|
||||||
|
ng := parser.NewNginxParser()
|
||||||
|
resultCh := make(chan *storage.LogEntry, 100)
|
||||||
|
ng.Parse(p.Events(), resultCh)
|
||||||
|
go storage.Write(db, resultCh)
|
||||||
|
}
|
||||||
|
if svc.Name == "ssh" {
|
||||||
|
log.Info("Starting ssh parser", "service", serviceName)
|
||||||
|
ssh := parser.NewSshdParser()
|
||||||
|
resultCh := make(chan *storage.LogEntry, 100)
|
||||||
|
ssh.Parse(p.Events(), resultCh)
|
||||||
|
go storage.Write(db, resultCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
}(pars, svc.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
log.Info("Shutdown signal received")
|
log.Info("Shutdown signal received")
|
||||||
|
|
||||||
|
for _, s := range scanners {
|
||||||
|
s.Stop()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/d3m0k1d/BanForge/internal/blocker"
|
"github.com/d3m0k1d/BanForge/internal/blocker"
|
||||||
"github.com/d3m0k1d/BanForge/internal/config"
|
"github.com/d3m0k1d/BanForge/internal/config"
|
||||||
|
"github.com/d3m0k1d/BanForge/internal/storage"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,6 +18,11 @@ var UnbanCmd = &cobra.Command{
|
|||||||
Use: "unban",
|
Use: "unban",
|
||||||
Short: "Unban IP",
|
Short: "Unban IP",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
db, err := storage.NewDB()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
cfg, err := config.LoadConfig()
|
cfg, err := config.LoadConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
@@ -41,6 +47,11 @@ var UnbanCmd = &cobra.Command{
|
|||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
err = db.RemoveBan(ip)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
fmt.Println("IP unblocked successfully!")
|
fmt.Println("IP unblocked successfully!")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -49,7 +60,11 @@ var BanCmd = &cobra.Command{
|
|||||||
Use: "ban",
|
Use: "ban",
|
||||||
Short: "Ban IP",
|
Short: "Ban IP",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
db, err := storage.NewDB()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
cfg, err := config.LoadConfig()
|
cfg, err := config.LoadConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
@@ -74,7 +89,12 @@ var BanCmd = &cobra.Command{
|
|||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
fmt.Println("IP unblocked successfully!")
|
err = db.AddBan(ip, "1y")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Println("IP blocked successfully!")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,19 +11,22 @@ Example:
|
|||||||
|
|
||||||
[[service]]
|
[[service]]
|
||||||
name = "nginx"
|
name = "nginx"
|
||||||
|
logging = "file"
|
||||||
log_path = "/home/d3m0k1d/test.log"
|
log_path = "/home/d3m0k1d/test.log"
|
||||||
enabled = true
|
enabled = true
|
||||||
|
|
||||||
[[service]]
|
[[service]]
|
||||||
name = "nginx"
|
name = "nginx"
|
||||||
log_path = "/var/log/nginx/access.log"
|
logging = "journald"
|
||||||
|
log_path = "nginx"
|
||||||
enabled = false
|
enabled = false
|
||||||
```
|
```
|
||||||
**Description**
|
**Description**
|
||||||
The [firewall] section defines firewall parameters. The banforge init command automatically detects your installed firewall (nftables, iptables, ufw, firewalld). For firewalls that require a configuration file, specify the path in the config parameter.
|
The [firewall] section defines firewall parameters. The banforge init command automatically detects your installed firewall (nftables, iptables, ufw, firewalld). For firewalls that require a configuration file, specify the path in the config parameter.
|
||||||
|
|
||||||
The [[service]] section is configured manually. Currently, only nginx is supported. To add a service, create a [[service]] block and specify the log_path to the nginx log file you want to monitor.
|
The [[service]] section is configured manually. Currently, only nginx is supported. To add a service, create a [[service]] block and specify the log_path to the nginx log file you want to monitor.
|
||||||
|
logging require in format "file" or "journald"
|
||||||
|
if you use journald logging, log_path require in format "service_name"
|
||||||
|
|
||||||
## rules.toml
|
## rules.toml
|
||||||
Rules configuration file for BanForge.
|
Rules configuration file for BanForge.
|
||||||
@@ -42,4 +45,5 @@ Example:
|
|||||||
```
|
```
|
||||||
**Description**
|
**Description**
|
||||||
The [[rule]] section require name and one of the following parameters: service, path, status, method. To add a rule, create a [[rule]] block and specify the parameters.
|
The [[rule]] section require name and one of the following parameters: service, path, status, method. To add a rule, create a [[rule]] block and specify the parameters.
|
||||||
ban_time require in format "1m", "1h", "1d", "1M", "1y"
|
ban_time require in format "1m", "1h", "1d", "1M", "1y".
|
||||||
|
If you want to ban all requests to PHP files (e.g., path = "*.php") or requests to the admin panel (e.g., path = "/admin/*")
|
||||||
|
|||||||
7
go.mod
7
go.mod
@@ -6,14 +6,17 @@ require (
|
|||||||
github.com/BurntSushi/toml v1.6.0
|
github.com/BurntSushi/toml v1.6.0
|
||||||
github.com/jedib0t/go-pretty/v6 v6.7.8
|
github.com/jedib0t/go-pretty/v6 v6.7.8
|
||||||
github.com/mattn/go-sqlite3 v1.14.33
|
github.com/mattn/go-sqlite3 v1.14.33
|
||||||
|
github.com/ncruces/go-sqlite3 v0.30.4
|
||||||
github.com/spf13/cobra v1.10.2
|
github.com/spf13/cobra v1.10.2
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||||
|
github.com/ncruces/julianday v1.0.0 // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/spf13/pflag v1.0.10 // indirect
|
github.com/spf13/pflag v1.0.10 // indirect
|
||||||
golang.org/x/sys v0.30.0 // indirect
|
github.com/tetratelabs/wazero v1.11.0 // indirect
|
||||||
golang.org/x/text v0.22.0 // indirect
|
golang.org/x/sys v0.40.0 // indirect
|
||||||
|
golang.org/x/text v0.32.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
14
go.sum
14
go.sum
@@ -11,6 +11,10 @@ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6T
|
|||||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-sqlite3 v1.14.33 h1:A5blZ5ulQo2AtayQ9/limgHEkFreKj1Dv226a1K73s0=
|
github.com/mattn/go-sqlite3 v1.14.33 h1:A5blZ5ulQo2AtayQ9/limgHEkFreKj1Dv226a1K73s0=
|
||||||
github.com/mattn/go-sqlite3 v1.14.33/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.33/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
|
github.com/ncruces/go-sqlite3 v0.30.4 h1:j9hEoOL7f9ZoXl8uqXVniaq1VNwlWAXihZbTvhqPPjA=
|
||||||
|
github.com/ncruces/go-sqlite3 v0.30.4/go.mod h1:7WR20VSC5IZusKhUdiR9y1NsUqnZgqIYCmKKoMEYg68=
|
||||||
|
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
|
||||||
|
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
@@ -24,11 +28,13 @@ github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
|||||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA=
|
||||||
|
github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU=
|
||||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -104,15 +104,14 @@ func (n *Nftables) Setup(config string) error {
|
|||||||
|
|
||||||
nftConfig := `table inet banforge {
|
nftConfig := `table inet banforge {
|
||||||
chain input {
|
chain input {
|
||||||
type filter hook input priority 0
|
type filter hook input priority filter; policy accept;
|
||||||
policy accept
|
jump banned
|
||||||
}
|
}
|
||||||
|
|
||||||
chain banned {
|
chain banned {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
cmd := exec.Command("sudo", "tee", config)
|
cmd := exec.Command("sudo", "tee", config)
|
||||||
stdin, err := cmd.StdinPipe()
|
stdin, err := cmd.StdinPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -10,11 +10,13 @@ config = "/etc/nftables.conf"
|
|||||||
|
|
||||||
[[service]]
|
[[service]]
|
||||||
name = "nginx"
|
name = "nginx"
|
||||||
|
logging = "file"
|
||||||
log_path = "/var/log/nginx/access.log"
|
log_path = "/var/log/nginx/access.log"
|
||||||
enabled = true
|
enabled = true
|
||||||
|
|
||||||
[[service]]
|
[[service]]
|
||||||
name = "nginx"
|
name = "nginx"
|
||||||
|
logging = "journald"
|
||||||
log_path = "/var/log/nginx/access.log"
|
log_path = "/var/log/nginx/access.log"
|
||||||
enabled = false
|
enabled = false
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ type Firewall struct {
|
|||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
Name string `toml:"name"`
|
Name string `toml:"name"`
|
||||||
|
Logging string `toml:"logging"`
|
||||||
LogPath string `toml:"log_path"`
|
LogPath string `toml:"log_path"`
|
||||||
Enabled bool `toml:"enabled"`
|
Enabled bool `toml:"enabled"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package judge
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/d3m0k1d/BanForge/internal/blocker"
|
"github.com/d3m0k1d/BanForge/internal/blocker"
|
||||||
@@ -71,7 +72,7 @@ func (j *Judge) ProcessUnviewed() error {
|
|||||||
for _, rule := range rules {
|
for _, rule := range rules {
|
||||||
if (rule.Method == "" || entry.Method == rule.Method) &&
|
if (rule.Method == "" || entry.Method == rule.Method) &&
|
||||||
(rule.Status == "" || entry.Status == rule.Status) &&
|
(rule.Status == "" || entry.Status == rule.Status) &&
|
||||||
(rule.Path == "" || entry.Path == rule.Path) {
|
matchPath(entry.Path, rule.Path) {
|
||||||
|
|
||||||
j.logger.Info(
|
j.logger.Info(
|
||||||
fmt.Sprintf(
|
fmt.Sprintf(
|
||||||
@@ -129,6 +130,10 @@ func (j *Judge) UnbanChecker() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, ip := range ips {
|
for _, ip := range ips {
|
||||||
|
err = j.db.RemoveBan(ip)
|
||||||
|
if err != nil {
|
||||||
|
j.logger.Error(fmt.Sprintf("Failed to remove ban: %v", err))
|
||||||
|
}
|
||||||
if err := j.Blocker.Unban(ip); err != nil {
|
if err := j.Blocker.Unban(ip); err != nil {
|
||||||
j.logger.Error(fmt.Sprintf("Failed to unban IP %s: %v", ip, err))
|
j.logger.Error(fmt.Sprintf("Failed to unban IP %s: %v", ip, err))
|
||||||
continue
|
continue
|
||||||
@@ -137,3 +142,18 @@ func (j *Judge) UnbanChecker() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func matchPath(path string, rulePath string) bool {
|
||||||
|
if rulePath == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(rulePath, "/*") {
|
||||||
|
prefix := strings.TrimPrefix(rulePath, "*")
|
||||||
|
return strings.HasPrefix(path, prefix)
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(rulePath, "*") {
|
||||||
|
suffix := strings.TrimSuffix(rulePath, "*")
|
||||||
|
return strings.HasSuffix(path, suffix)
|
||||||
|
}
|
||||||
|
return path == rulePath
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package parser
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/d3m0k1d/BanForge/internal/logger"
|
"github.com/d3m0k1d/BanForge/internal/logger"
|
||||||
@@ -17,24 +18,51 @@ type Scanner struct {
|
|||||||
ch chan Event
|
ch chan Event
|
||||||
stopCh chan struct{}
|
stopCh chan struct{}
|
||||||
logger *logger.Logger
|
logger *logger.Logger
|
||||||
|
cmd *exec.Cmd
|
||||||
file *os.File
|
file *os.File
|
||||||
pollDelay time.Duration
|
pollDelay time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewScanner(path string) (*Scanner, error) {
|
func NewScannerTail(path string) (*Scanner, error) {
|
||||||
file, err := os.Open(
|
cmd := exec.Command("tail", "-F", "-n", "10", path)
|
||||||
path,
|
stdout, err := cmd.StdoutPipe()
|
||||||
) // #nosec G304 -- admin tool, runs as root, path controlled by operator
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return &Scanner{
|
return &Scanner{
|
||||||
scanner: bufio.NewScanner(file),
|
scanner: bufio.NewScanner(stdout),
|
||||||
ch: make(chan Event, 100),
|
ch: make(chan Event, 100),
|
||||||
stopCh: make(chan struct{}),
|
stopCh: make(chan struct{}),
|
||||||
logger: logger.New(false),
|
logger: logger.New(false),
|
||||||
file: file,
|
file: nil,
|
||||||
|
cmd: cmd,
|
||||||
|
pollDelay: 100 * time.Millisecond,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewScannerJournald(unit string) (*Scanner, error) {
|
||||||
|
cmd := exec.Command("journalctl", "-u", unit, "-f", "-n", "0", "-o", "short", "--no-pager")
|
||||||
|
stdout, err := cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Scanner{
|
||||||
|
scanner: bufio.NewScanner(stdout),
|
||||||
|
ch: make(chan Event, 100),
|
||||||
|
stopCh: make(chan struct{}),
|
||||||
|
logger: logger.New(false),
|
||||||
|
cmd: cmd,
|
||||||
|
file: nil,
|
||||||
pollDelay: 100 * time.Millisecond,
|
pollDelay: 100 * time.Millisecond,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -60,7 +88,6 @@ func (s *Scanner) Start() {
|
|||||||
s.logger.Error("Scanner error")
|
s.logger.Error("Scanner error")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
time.Sleep(s.pollDelay)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -69,11 +96,26 @@ func (s *Scanner) Start() {
|
|||||||
|
|
||||||
func (s *Scanner) Stop() {
|
func (s *Scanner) Stop() {
|
||||||
close(s.stopCh)
|
close(s.stopCh)
|
||||||
time.Sleep(150 * time.Millisecond)
|
|
||||||
err := s.file.Close()
|
if s.cmd != nil && s.cmd.Process != nil {
|
||||||
|
s.logger.Info("Stopping process", "pid", s.cmd.Process.Pid)
|
||||||
|
err := s.cmd.Process.Kill()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to close file")
|
s.logger.Error("Failed to kill process", "err", err)
|
||||||
}
|
}
|
||||||
|
err = s.cmd.Wait()
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to wait process", "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.file != nil {
|
||||||
|
if err := s.file.Close(); err != nil {
|
||||||
|
s.logger.Error("Failed to close file", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
time.Sleep(150 * time.Millisecond)
|
||||||
close(s.ch)
|
close(s.ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,48 +6,67 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewScanner(t *testing.T) {
|
func TestNewScannerTail(t *testing.T) {
|
||||||
file, err := os.CreateTemp("", "test.log")
|
|
||||||
|
file, err := os.CreateTemp("", "test-*.log")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer file.Close()
|
|
||||||
defer os.Remove(file.Name())
|
defer os.Remove(file.Name())
|
||||||
s, err := NewScanner(file.Name())
|
file.Close()
|
||||||
|
|
||||||
|
scanner, err := NewScannerTail(file.Name())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatalf("NewScannerTail() error = %v", err)
|
||||||
}
|
}
|
||||||
if s == nil {
|
|
||||||
|
if scanner == nil {
|
||||||
t.Fatal("Scanner is nil")
|
t.Fatal("Scanner is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if scanner.cmd == nil {
|
||||||
|
t.Fatal("cmd is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if scanner.cmd.Process == nil {
|
||||||
|
t.Fatal("process is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestScannerStart(t *testing.T) {
|
func TestScannerTailEvents(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
lines []string
|
||||||
wantErr bool
|
|
||||||
wantLines int
|
wantLines int
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "correct file",
|
name: "multiple lines",
|
||||||
input: `Failed password for root from 192.168.1.1
|
lines: []string{
|
||||||
Invalid user admin from 192.168.1.1
|
"Failed password for root from 192.168.1.1",
|
||||||
Accepted publickey for user from 192.168.1.2`,
|
"Invalid user admin from 192.168.1.2",
|
||||||
wantErr: false,
|
"Accepted publickey for user from 192.168.1.3",
|
||||||
|
},
|
||||||
wantLines: 3,
|
wantLines: 3,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "empty file",
|
name: "single line",
|
||||||
input: "",
|
lines: []string{
|
||||||
wantErr: false,
|
"Failed password for root",
|
||||||
wantLines: 0,
|
},
|
||||||
|
wantLines: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "single line",
|
name: "many lines",
|
||||||
input: `Failed password for root`,
|
lines: []string{
|
||||||
wantErr: false,
|
"line 1",
|
||||||
wantLines: 1,
|
"line 2",
|
||||||
|
"line 3",
|
||||||
|
"line 4",
|
||||||
|
"line 5",
|
||||||
|
},
|
||||||
|
wantLines: 5,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,41 +78,206 @@ Accepted publickey for user from 192.168.1.2`,
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
filePath := file.Name()
|
filePath := file.Name()
|
||||||
|
|
||||||
if _, err := file.WriteString(tt.input); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
file.Close()
|
file.Close()
|
||||||
defer os.Remove(filePath)
|
defer os.Remove(filePath)
|
||||||
|
|
||||||
scanner, err := NewScanner(filePath)
|
scanner, err := NewScannerTail(filePath)
|
||||||
if (err != nil) != tt.wantErr {
|
if err != nil {
|
||||||
t.Errorf("NewScanner() error = %v, wantErr %v", err, tt.wantErr)
|
t.Fatalf("NewScannerTail() error = %v", err)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if tt.wantErr {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
defer scanner.Stop()
|
defer scanner.Stop()
|
||||||
|
|
||||||
scanner.Start()
|
scanner.Start()
|
||||||
|
|
||||||
timeout := time.After(500 * time.Millisecond)
|
time.Sleep(200 * time.Millisecond)
|
||||||
linesRead := 0
|
|
||||||
|
|
||||||
|
file, err = os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY, 0644)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, line := range tt.lines {
|
||||||
|
if _, err := file.WriteString(line + "\n"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := file.Sync(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
file.Close()
|
||||||
|
|
||||||
|
// 5. Собираем события
|
||||||
|
timeout := time.After(1 * time.Second)
|
||||||
|
var events []Event
|
||||||
|
|
||||||
|
eventLoop:
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case event := <-scanner.Events():
|
case event := <-scanner.Events():
|
||||||
linesRead++
|
events = append(events, event)
|
||||||
t.Logf("Read: %s", event.Data)
|
t.Logf("Read: %s", event.Data)
|
||||||
case <-timeout:
|
|
||||||
if linesRead != tt.wantLines {
|
if len(events) == tt.wantLines {
|
||||||
t.Errorf("got %d lines, want %d", linesRead, tt.wantLines)
|
break eventLoop
|
||||||
}
|
}
|
||||||
return
|
|
||||||
|
case <-timeout:
|
||||||
|
break eventLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(events) != tt.wantLines {
|
||||||
|
t.Errorf("got %d lines, want %d", len(events), tt.wantLines)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, event := range events {
|
||||||
|
if event.Data != tt.lines[i] {
|
||||||
|
t.Errorf("line %d: got %q, want %q", i, event.Data, tt.lines[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestScannerStop(t *testing.T) {
|
||||||
|
|
||||||
|
file, err := os.CreateTemp("", "test-*.log")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
filePath := file.Name()
|
||||||
|
file.Close()
|
||||||
|
defer os.Remove(filePath)
|
||||||
|
|
||||||
|
scanner, err := NewScannerTail(filePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner.Start()
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
scanner.Stop()
|
||||||
|
|
||||||
|
err = scanner.cmd.Process.Signal(os.Signal(nil))
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Process still alive after Stop()")
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case _, ok := <-scanner.Events():
|
||||||
|
if ok {
|
||||||
|
t.Error("Channel still open after Stop()")
|
||||||
|
}
|
||||||
|
case <-time.After(100 * time.Millisecond):
|
||||||
|
t.Error("Channel not closed after Stop()")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestMultipleScanners(t *testing.T) {
|
||||||
|
|
||||||
|
file1, err := os.CreateTemp("", "test1-*.log")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
path1 := file1.Name()
|
||||||
|
file1.Close()
|
||||||
|
defer os.Remove(path1)
|
||||||
|
|
||||||
|
file2, err := os.CreateTemp("", "test2-*.log")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
path2 := file2.Name()
|
||||||
|
file2.Close()
|
||||||
|
defer os.Remove(path2)
|
||||||
|
|
||||||
|
scanner1, err := NewScannerTail(path1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer scanner1.Stop()
|
||||||
|
|
||||||
|
scanner2, err := NewScannerTail(path2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer scanner2.Stop()
|
||||||
|
|
||||||
|
scanner1.Start()
|
||||||
|
scanner2.Start()
|
||||||
|
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
|
f1, _ := os.OpenFile(path1, os.O_APPEND|os.O_WRONLY, 0644)
|
||||||
|
f1.WriteString("scanner1 line\n")
|
||||||
|
f1.Sync()
|
||||||
|
f1.Close()
|
||||||
|
|
||||||
|
f2, _ := os.OpenFile(path2, os.O_APPEND|os.O_WRONLY, 0644)
|
||||||
|
f2.WriteString("scanner2 line\n")
|
||||||
|
f2.Sync()
|
||||||
|
f2.Close()
|
||||||
|
|
||||||
|
timeout := time.After(1 * time.Second)
|
||||||
|
|
||||||
|
var event1, event2 Event
|
||||||
|
got1, got2 := false, false
|
||||||
|
|
||||||
|
for !got1 || !got2 {
|
||||||
|
select {
|
||||||
|
case event1 = <-scanner1.Events():
|
||||||
|
got1 = true
|
||||||
|
t.Logf("Scanner1: %s", event1.Data)
|
||||||
|
|
||||||
|
case event2 = <-scanner2.Events():
|
||||||
|
got2 = true
|
||||||
|
t.Logf("Scanner2: %s", event2.Data)
|
||||||
|
|
||||||
|
case <-timeout:
|
||||||
|
if !got1 {
|
||||||
|
t.Error("Scanner1 did not receive event")
|
||||||
|
}
|
||||||
|
if !got2 {
|
||||||
|
t.Error("Scanner2 did not receive event")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if event1.Data != "scanner1 line" {
|
||||||
|
t.Errorf("Scanner1 got wrong data: %q", event1.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
if event2.Data != "scanner2 line" {
|
||||||
|
t.Errorf("Scanner2 got wrong data: %q", event2.Data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func BenchmarkScanner(b *testing.B) {
|
||||||
|
file, err := os.CreateTemp("", "bench-*.log")
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
filePath := file.Name()
|
||||||
|
file.Close()
|
||||||
|
defer os.Remove(filePath)
|
||||||
|
|
||||||
|
scanner, err := NewScannerTail(filePath)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
defer scanner.Stop()
|
||||||
|
|
||||||
|
scanner.Start()
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
f, _ := os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY, 0644)
|
||||||
|
f.WriteString("benchmark line\n")
|
||||||
|
f.Sync()
|
||||||
|
f.Close()
|
||||||
|
<-scanner.Events()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
54
internal/parser/sshd.go
Normal file
54
internal/parser/sshd.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/d3m0k1d/BanForge/internal/logger"
|
||||||
|
"github.com/d3m0k1d/BanForge/internal/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SshdParser struct {
|
||||||
|
pattern *regexp.Regexp
|
||||||
|
logger *logger.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSshdParser() *SshdParser {
|
||||||
|
pattern := regexp.MustCompile(
|
||||||
|
`^([A-Za-z]{3}\s+\d{1,2}\s+\d{2}:\d{2}:\d{2})\s+(\S+)\s+sshd(?:-session)?\[(\d+)\]:\s+Failed\s+(\w+)\s+for\s+(?:invalid\s+user\s+)?(\S+)\s+from\s+(\S+)\s+port\s+(\d+)`,
|
||||||
|
)
|
||||||
|
return &SshdParser{
|
||||||
|
pattern: pattern,
|
||||||
|
logger: logger.New(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *SshdParser) Parse(eventCh <-chan Event, resultCh chan<- *storage.LogEntry) {
|
||||||
|
// Group 1: Timestamp, Group 2: hostame, Group 3: pid, Group 4: Method auth, Group 5: User, Group 6: IP, Group 7: port
|
||||||
|
go func() {
|
||||||
|
for event := range eventCh {
|
||||||
|
matches := p.pattern.FindStringSubmatch(event.Data)
|
||||||
|
if matches == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
resultCh <- &storage.LogEntry{
|
||||||
|
Service: "ssh",
|
||||||
|
IP: matches[6],
|
||||||
|
Path: matches[5], // user
|
||||||
|
Status: "Failed",
|
||||||
|
Method: matches[4], // method auth
|
||||||
|
IsViewed: false,
|
||||||
|
}
|
||||||
|
p.logger.Info(
|
||||||
|
"Parsed ssh log entry",
|
||||||
|
"ip",
|
||||||
|
matches[6],
|
||||||
|
"user",
|
||||||
|
matches[5],
|
||||||
|
"method",
|
||||||
|
matches[4],
|
||||||
|
"status",
|
||||||
|
"Failed",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
@@ -10,7 +10,8 @@ import (
|
|||||||
"github.com/d3m0k1d/BanForge/internal/config"
|
"github.com/d3m0k1d/BanForge/internal/config"
|
||||||
"github.com/d3m0k1d/BanForge/internal/logger"
|
"github.com/d3m0k1d/BanForge/internal/logger"
|
||||||
"github.com/jedib0t/go-pretty/v6/table"
|
"github.com/jedib0t/go-pretty/v6/table"
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/ncruces/go-sqlite3/driver"
|
||||||
|
_ "github.com/ncruces/go-sqlite3/embed"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DB struct {
|
type DB struct {
|
||||||
@@ -111,6 +112,15 @@ func (d *DB) AddBan(ip string, ttl string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *DB) RemoveBan(ip string) error {
|
||||||
|
_, err := d.db.Exec("DELETE FROM bans WHERE ip = ?", ip)
|
||||||
|
if err != nil {
|
||||||
|
d.logger.Error("Failed to remove ban", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (d *DB) BanList() error {
|
func (d *DB) BanList() error {
|
||||||
|
|
||||||
var count int
|
var count int
|
||||||
|
|||||||
Reference in New Issue
Block a user