Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7a1ac06d4 | ||
|
|
49f0acb777 | ||
|
|
a602207369 | ||
|
|
8c0cfcdbe7 | ||
|
|
35a1a89baf | ||
|
|
f3387b169a | ||
|
|
5782072f91 | ||
|
|
7918b3efe6 | ||
|
|
f628e24f58 | ||
|
|
7f54db0cd4 | ||
|
|
2e9b307194 | ||
|
|
726594a712 | ||
|
|
b27038a59c | ||
|
|
72025dab7d | ||
|
|
dd131477e2 | ||
|
|
670aec449a | ||
|
|
fc37e641be | ||
|
|
361de03208 | ||
|
|
a2268fda5d | ||
|
|
9dc0b6002e | ||
|
|
4953be3ef6 | ||
|
|
c386a2d6bc | ||
|
|
dea03a6f70 | ||
|
|
11f755c03c | ||
|
|
1c7a1c1778 | ||
|
|
411574cabe | ||
|
|
820c9410a1 | ||
|
|
6f261803a7 | ||
|
|
aacc98668f | ||
|
|
9519eedf4f |
@@ -30,6 +30,11 @@ var DaemonCmd = &cobra.Command{
|
|||||||
log.Error("Failed to create request writer", "error", err)
|
log.Error("Failed to create request writer", "error", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
reqDb_r, err := storage.NewRequestsRd()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to create request reader", "error", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
banDb_r, err := storage.NewBanReader()
|
banDb_r, err := storage.NewBanReader()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to create ban reader", "error", err)
|
log.Error("Failed to create ban reader", "error", err)
|
||||||
@@ -63,7 +68,7 @@ var DaemonCmd = &cobra.Command{
|
|||||||
log.Error("Failed to load rules", "error", err)
|
log.Error("Failed to load rules", "error", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
j := judge.New(banDb_r, banDb_w, b, resultCh, entryCh)
|
j := judge.New(banDb_r, banDb_w, reqDb_r, b, resultCh, entryCh)
|
||||||
j.LoadRules(r)
|
j.LoadRules(r)
|
||||||
go j.UnbanChecker()
|
go j.UnbanChecker()
|
||||||
go j.Tribunal()
|
go j.Tribunal()
|
||||||
|
|||||||
@@ -13,14 +13,17 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
ttl_fw string
|
ttl_fw string
|
||||||
|
port int
|
||||||
|
protocol string
|
||||||
)
|
)
|
||||||
|
|
||||||
var UnbanCmd = &cobra.Command{
|
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) {
|
||||||
|
err := func() error {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
fmt.Println("IP can't be empty")
|
return fmt.Errorf("IP can't be empty")
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
if ttl_fw == "" {
|
if ttl_fw == "" {
|
||||||
ttl_fw = "1y"
|
ttl_fw = "1y"
|
||||||
@@ -28,39 +31,38 @@ var UnbanCmd = &cobra.Command{
|
|||||||
ip := args[0]
|
ip := args[0]
|
||||||
db, err := storage.NewBanWriter()
|
db, err := storage.NewBanWriter()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
return err
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
cfg, err := config.LoadConfig()
|
cfg, err := config.LoadConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
return err
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
fw := cfg.Firewall.Name
|
fw := cfg.Firewall.Name
|
||||||
b := blocker.GetBlocker(fw, cfg.Firewall.Config)
|
b := blocker.GetBlocker(fw, cfg.Firewall.Config)
|
||||||
if ip == "" {
|
if ip == "" {
|
||||||
fmt.Println("IP can't be empty")
|
return fmt.Errorf("IP can't be empty")
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
if net.ParseIP(ip) == nil {
|
if net.ParseIP(ip) == nil {
|
||||||
fmt.Println("Invalid IP")
|
return fmt.Errorf("invalid IP")
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
return err
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
err = b.Unban(ip)
|
err = b.Unban(ip)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
return err
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
err = db.RemoveBan(ip)
|
err = db.RemoveBan(ip)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println("IP unblocked successfully!")
|
||||||
|
return nil
|
||||||
|
}()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
fmt.Println("IP unblocked successfully!")
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,19 +70,64 @@ 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) {
|
||||||
|
err := func() error {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
fmt.Println("IP can't be empty")
|
return fmt.Errorf("IP can't be empty")
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
if ttl_fw == "" {
|
if ttl_fw == "" {
|
||||||
ttl_fw = "1y"
|
ttl_fw = "1y"
|
||||||
}
|
}
|
||||||
ip := args[0]
|
ip := args[0]
|
||||||
db, err := storage.NewBanWriter()
|
db, err := storage.NewBanWriter()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cfg, err := config.LoadConfig()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fw := cfg.Firewall.Name
|
||||||
|
b := blocker.GetBlocker(fw, cfg.Firewall.Config)
|
||||||
|
if ip == "" {
|
||||||
|
return fmt.Errorf("IP can't be empty")
|
||||||
|
}
|
||||||
|
if net.ParseIP(ip) == nil {
|
||||||
|
return fmt.Errorf("invalid IP")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = b.Ban(ip)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = db.AddBan(ip, ttl_fw, "manual ban")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println("IP blocked successfully!")
|
||||||
|
return nil
|
||||||
|
}()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var PortCmd = &cobra.Command{
|
||||||
|
Use: "port",
|
||||||
|
Short: "Ports commands",
|
||||||
|
}
|
||||||
|
|
||||||
|
var PortOpenCmd = &cobra.Command{
|
||||||
|
Use: "open",
|
||||||
|
Short: "Open ports on firewall",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
if protocol == "" {
|
||||||
|
fmt.Println("Protocol can't be empty")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
cfg, err := config.LoadConfig()
|
cfg, err := config.LoadConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
@@ -88,32 +135,45 @@ var BanCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
fw := cfg.Firewall.Name
|
fw := cfg.Firewall.Name
|
||||||
b := blocker.GetBlocker(fw, cfg.Firewall.Config)
|
b := blocker.GetBlocker(fw, cfg.Firewall.Config)
|
||||||
if ip == "" {
|
err = b.PortOpen(port, protocol)
|
||||||
fmt.Println("IP can't be empty")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
if net.ParseIP(ip) == nil {
|
|
||||||
fmt.Println("Invalid IP")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
err = b.Ban(ip)
|
fmt.Println("Port opened successfully!")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var PortCloseCmd = &cobra.Command{
|
||||||
|
Use: "close",
|
||||||
|
Short: "Close ports on firewall",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
if protocol == "" {
|
||||||
|
fmt.Println("Protocol can't be empty")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
cfg, err := config.LoadConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
err = db.AddBan(ip, ttl_fw, "manual ban")
|
fw := cfg.Firewall.Name
|
||||||
|
b := blocker.GetBlocker(fw, cfg.Firewall.Config)
|
||||||
|
err = b.PortClose(port, protocol)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
fmt.Println("IP blocked successfully!")
|
fmt.Println("Port closed successfully!")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func FwRegister() {
|
func FwRegister() {
|
||||||
BanCmd.Flags().StringVarP(&ttl_fw, "ttl", "t", "", "ban time")
|
BanCmd.Flags().StringVarP(&ttl_fw, "ttl", "t", "", "ban time")
|
||||||
|
PortCmd.AddCommand(PortOpenCmd)
|
||||||
|
PortCmd.AddCommand(PortCloseCmd)
|
||||||
|
PortOpenCmd.Flags().IntVarP(&port, "port", "p", 0, "port number")
|
||||||
|
PortOpenCmd.Flags().StringVarP(&protocol, "protocol", "c", "", "protocol")
|
||||||
|
PortCloseCmd.Flags().IntVarP(&port, "port", "p", 0, "port number")
|
||||||
|
PortCloseCmd.Flags().StringVarP(&protocol, "protocol", "c", "", "protocol")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,11 +61,12 @@ var ListCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
for _, rule := range r {
|
for _, rule := range r {
|
||||||
fmt.Printf(
|
fmt.Printf(
|
||||||
"Name: %s\nService: %s\nPath: %s\nStatus: %s\nMethod: %s\n\n",
|
"Name: %s\nService: %s\nPath: %s\nStatus: %s\n MaxRetry: %d\nMethod: %s\n\n",
|
||||||
rule.Name,
|
rule.Name,
|
||||||
rule.ServiceName,
|
rule.ServiceName,
|
||||||
rule.Path,
|
rule.Path,
|
||||||
rule.Status,
|
rule.Status,
|
||||||
|
rule.MaxRetry,
|
||||||
rule.Method,
|
rule.Method,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
17
cmd/banforge/command/version.go
Normal file
17
cmd/banforge/command/version.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var version = "0.5.0"
|
||||||
|
|
||||||
|
var VersionCmd = &cobra.Command{
|
||||||
|
Use: "version",
|
||||||
|
Short: "BanForge version",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
fmt.Println("BanForge version:", version)
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -13,7 +13,6 @@ var rootCmd = &cobra.Command{
|
|||||||
Use: "banforge",
|
Use: "banforge",
|
||||||
Short: "IPS log-based written on Golang",
|
Short: "IPS log-based written on Golang",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,6 +27,8 @@ func Execute() {
|
|||||||
rootCmd.AddCommand(command.BanCmd)
|
rootCmd.AddCommand(command.BanCmd)
|
||||||
rootCmd.AddCommand(command.UnbanCmd)
|
rootCmd.AddCommand(command.UnbanCmd)
|
||||||
rootCmd.AddCommand(command.BanListCmd)
|
rootCmd.AddCommand(command.BanListCmd)
|
||||||
|
rootCmd.AddCommand(command.VersionCmd)
|
||||||
|
rootCmd.AddCommand(command.PortCmd)
|
||||||
command.RuleRegister()
|
command.RuleRegister()
|
||||||
command.FwRegister()
|
command.FwRegister()
|
||||||
if err := rootCmd.Execute(); err != nil {
|
if err := rootCmd.Execute(); err != nil {
|
||||||
|
|||||||
21
docs/cli.md
21
docs/cli.md
@@ -11,6 +11,16 @@ banforge init
|
|||||||
**Description**
|
**Description**
|
||||||
This command creates the necessary directories and base configuration files
|
This command creates the necessary directories and base configuration files
|
||||||
required for the daemon to operate.
|
required for the daemon to operate.
|
||||||
|
|
||||||
|
### version - Display BanForge version
|
||||||
|
|
||||||
|
```shell
|
||||||
|
banforge version
|
||||||
|
```
|
||||||
|
|
||||||
|
**Description**
|
||||||
|
This command displays the current version of the BanForge software.
|
||||||
|
|
||||||
### daemon - Starts the BanForge daemon process
|
### daemon - Starts the BanForge daemon process
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
@@ -32,6 +42,17 @@ banforge unban <ip>
|
|||||||
These commands provide an abstraction over your firewall. If you want to simplify the interface to your firewall, you can use these commands.
|
These commands provide an abstraction over your firewall. If you want to simplify the interface to your firewall, you can use these commands.
|
||||||
|
|
||||||
Flag -t or -ttl add bantime if not used default ban 1 year
|
Flag -t or -ttl add bantime if not used default ban 1 year
|
||||||
|
|
||||||
|
### ports - Open and Close ports on firewall
|
||||||
|
|
||||||
|
```shell
|
||||||
|
banforge open -port <port> -protocol <protocol>
|
||||||
|
banforge close -port <port> -protocol <protocol>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Description**
|
||||||
|
These commands provide an abstraction over your firewall. If you want to simplify the interface to your firewall, you can use these commands.
|
||||||
|
|
||||||
### list - Lists the IP addresses that are currently blocked
|
### list - Lists the IP addresses that are currently blocked
|
||||||
```shell
|
```shell
|
||||||
banforge list
|
banforge list
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ Example:
|
|||||||
service = "nginx"
|
service = "nginx"
|
||||||
path = ""
|
path = ""
|
||||||
status = "304"
|
status = "304"
|
||||||
|
max_retry = 3
|
||||||
method = ""
|
method = ""
|
||||||
ban_time = "1m"
|
ban_time = "1m"
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package blocker
|
package blocker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/d3m0k1d/BanForge/internal/logger"
|
"github.com/d3m0k1d/BanForge/internal/logger"
|
||||||
)
|
)
|
||||||
@@ -21,6 +23,7 @@ func (f *Firewalld) Ban(ip string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// #nosec G204 - ip is validated
|
||||||
cmd := exec.Command("firewall-cmd", "--zone=drop", "--add-source", ip, "--permanent")
|
cmd := exec.Command("firewall-cmd", "--zone=drop", "--add-source", ip, "--permanent")
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -42,6 +45,7 @@ func (f *Firewalld) Unban(ip string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// #nosec G204 - ip is validated
|
||||||
cmd := exec.Command("firewall-cmd", "--zone=drop", "--remove-source", ip, "--permanent")
|
cmd := exec.Command("firewall-cmd", "--zone=drop", "--remove-source", ip, "--permanent")
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -58,6 +62,63 @@ func (f *Firewalld) Unban(ip string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *Firewalld) PortOpen(port int, protocol string) error {
|
||||||
|
// #nosec G204 - handle is extracted from Firewalld output and validated
|
||||||
|
if port >= 0 && port <= 65535 {
|
||||||
|
if protocol != "tcp" && protocol != "udp" {
|
||||||
|
f.logger.Error("invalid protocol")
|
||||||
|
return fmt.Errorf("invalid protocol")
|
||||||
|
}
|
||||||
|
s := strconv.Itoa(port)
|
||||||
|
cmd := exec.Command(
|
||||||
|
"firewall-cmd",
|
||||||
|
"--zone=public",
|
||||||
|
"--add-port="+s+"/"+protocol,
|
||||||
|
"--permanent",
|
||||||
|
)
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
f.logger.Error(err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.logger.Info("Add port " + s + " " + string(output))
|
||||||
|
output, err = exec.Command("firewall-cmd", "--reload").CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
f.logger.Error(err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.logger.Info("Reload " + string(output))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Firewalld) PortClose(port int, protocol string) error {
|
||||||
|
// #nosec G204 - handle is extracted from nftables output and validated
|
||||||
|
if port >= 0 && port <= 65535 {
|
||||||
|
if protocol != "tcp" && protocol != "udp" {
|
||||||
|
return fmt.Errorf("invalid protocol")
|
||||||
|
}
|
||||||
|
s := strconv.Itoa(port)
|
||||||
|
cmd := exec.Command(
|
||||||
|
"firewall-cmd",
|
||||||
|
"--zone=public",
|
||||||
|
"--remove-port="+s+"/"+protocol,
|
||||||
|
"--permanent",
|
||||||
|
)
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.logger.Info("Remove port " + s + " " + string(output))
|
||||||
|
output, err = exec.Command("firewall-cmd", "--reload").CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.logger.Info("Reload " + string(output))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (f *Firewalld) Setup(config string) error {
|
func (f *Firewalld) Setup(config string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ type BlockerEngine interface {
|
|||||||
Ban(ip string) error
|
Ban(ip string) error
|
||||||
Unban(ip string) error
|
Unban(ip string) error
|
||||||
Setup(config string) error
|
Setup(config string) error
|
||||||
|
PortOpen(port int, protocol string) error
|
||||||
|
PortClose(port int, protocol string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetBlocker(fw string, config string) BlockerEngine {
|
func GetBlocker(fw string, config string) BlockerEngine {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package blocker
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/d3m0k1d/BanForge/internal/logger"
|
"github.com/d3m0k1d/BanForge/internal/logger"
|
||||||
)
|
)
|
||||||
@@ -27,6 +28,7 @@ func (f *Iptables) Ban(ip string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// #nosec G204 - f.config is validated above via validateConfigPath()
|
||||||
cmd := exec.Command("iptables", "-A", "INPUT", "-s", ip, "-j", "DROP")
|
cmd := exec.Command("iptables", "-A", "INPUT", "-s", ip, "-j", "DROP")
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -69,6 +71,7 @@ func (f *Iptables) Unban(ip string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// #nosec G204 - f.config is validated above via validateConfigPath()
|
||||||
cmd := exec.Command("iptables", "-D", "INPUT", "-s", ip, "-j", "DROP")
|
cmd := exec.Command("iptables", "-D", "INPUT", "-s", ip, "-j", "DROP")
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -102,6 +105,64 @@ func (f *Iptables) Unban(ip string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *Iptables) PortOpen(port int, protocol string) error {
|
||||||
|
if port >= 0 && port <= 65535 {
|
||||||
|
if protocol != "tcp" && protocol != "udp" {
|
||||||
|
f.logger.Error("invalid protocol")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
s := strconv.Itoa(port)
|
||||||
|
// #nosec G204 - managed by system adminstartor
|
||||||
|
cmd := exec.Command("iptables", "-A", "INPUT", "-p", protocol, "--dport", s, "-j", "ACCEPT")
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
f.logger.Error(err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.logger.Info("Add port " + s + " " + string(output))
|
||||||
|
// #nosec G204 - f.config is validated above via validateConfigPath()
|
||||||
|
cmd = exec.Command("iptables-save", "-f", f.config)
|
||||||
|
output, err = cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
f.logger.Error("failed to save config",
|
||||||
|
"config_path", f.config,
|
||||||
|
"error", err.Error(),
|
||||||
|
"output", string(output))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Iptables) PortClose(port int, protocol string) error {
|
||||||
|
if port >= 0 && port <= 65535 {
|
||||||
|
if protocol != "tcp" && protocol != "udp" {
|
||||||
|
f.logger.Error("invalid protocol")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
s := strconv.Itoa(port)
|
||||||
|
// #nosec G204 - managed by system adminstartor
|
||||||
|
cmd := exec.Command("iptables", "-D", "INPUT", "-p", protocol, "--dport", s, "-j", "ACCEPT")
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
f.logger.Error(err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.logger.Info("Add port " + s + " " + string(output))
|
||||||
|
// #nosec G204 - f.config is validated above via validateConfigPath()
|
||||||
|
cmd = exec.Command("iptables-save", "-f", f.config)
|
||||||
|
output, err = cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
f.logger.Error("failed to save config",
|
||||||
|
"config_path", f.config,
|
||||||
|
"error", err.Error(),
|
||||||
|
"output", string(output))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (f *Iptables) Setup(config string) error {
|
func (f *Iptables) Setup(config string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package blocker
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/d3m0k1d/BanForge/internal/logger"
|
"github.com/d3m0k1d/BanForge/internal/logger"
|
||||||
@@ -25,7 +26,7 @@ func (n *Nftables) Ban(ip string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// #nosec G204 - ip is validated
|
||||||
cmd := exec.Command("nft", "add", "rule", "inet", "banforge", "banned",
|
cmd := exec.Command("nft", "add", "rule", "inet", "banforge", "banned",
|
||||||
"ip", "saddr", ip, "drop")
|
"ip", "saddr", ip, "drop")
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
@@ -112,6 +113,7 @@ func (n *Nftables) Setup(config string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
// #nosec G204 - config is managed by adminstartor
|
||||||
cmd := exec.Command("tee", config)
|
cmd := exec.Command("tee", config)
|
||||||
stdin, err := cmd.StdinPipe()
|
stdin, err := cmd.StdinPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -134,7 +136,7 @@ func (n *Nftables) Setup(config string) error {
|
|||||||
if err = cmd.Wait(); err != nil {
|
if err = cmd.Wait(); err != nil {
|
||||||
return fmt.Errorf("failed to save config: %w", err)
|
return fmt.Errorf("failed to save config: %w", err)
|
||||||
}
|
}
|
||||||
|
// #nosec G204 - config is managed by adminstartor
|
||||||
cmd = exec.Command("nft", "-f", config)
|
cmd = exec.Command("nft", "-f", config)
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -166,6 +168,81 @@ func (n *Nftables) findRuleHandle(ip string) (string, error) {
|
|||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *Nftables) PortOpen(port int, protocol string) error {
|
||||||
|
if port >= 0 && port <= 65535 {
|
||||||
|
if protocol != "tcp" && protocol != "udp" {
|
||||||
|
n.logger.Error("invalid protocol")
|
||||||
|
return fmt.Errorf("invalid protocol")
|
||||||
|
}
|
||||||
|
s := strconv.Itoa(port)
|
||||||
|
// #nosec G204 - managed by system adminstartor
|
||||||
|
cmd := exec.Command(
|
||||||
|
"nft",
|
||||||
|
"add",
|
||||||
|
"rule",
|
||||||
|
"inet",
|
||||||
|
"banforge",
|
||||||
|
"input",
|
||||||
|
protocol,
|
||||||
|
"dport",
|
||||||
|
s,
|
||||||
|
"accept",
|
||||||
|
)
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
n.logger.Error(err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
n.logger.Info("Add port " + s + " " + string(output))
|
||||||
|
err = saveNftablesConfig(n.config)
|
||||||
|
if err != nil {
|
||||||
|
n.logger.Error("failed to save config",
|
||||||
|
"config_path", n.config,
|
||||||
|
"error", err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Nftables) PortClose(port int, protocol string) error {
|
||||||
|
if port >= 0 && port <= 65535 {
|
||||||
|
if protocol != "tcp" && protocol != "udp" {
|
||||||
|
n.logger.Error("invalid protocol")
|
||||||
|
return fmt.Errorf("invalid protocol")
|
||||||
|
}
|
||||||
|
s := strconv.Itoa(port)
|
||||||
|
// #nosec G204 - managed by system adminstartor
|
||||||
|
cmd := exec.Command(
|
||||||
|
"nft",
|
||||||
|
"add",
|
||||||
|
"rule",
|
||||||
|
"inet",
|
||||||
|
"banforge",
|
||||||
|
"input",
|
||||||
|
protocol,
|
||||||
|
"dport",
|
||||||
|
s,
|
||||||
|
"drop",
|
||||||
|
)
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
n.logger.Error(err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
n.logger.Info("Add port " + s + " " + string(output))
|
||||||
|
err = saveNftablesConfig(n.config)
|
||||||
|
if err != nil {
|
||||||
|
n.logger.Error("failed to save config",
|
||||||
|
"config_path", n.config,
|
||||||
|
"error", err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func saveNftablesConfig(configPath string) error {
|
func saveNftablesConfig(configPath string) error {
|
||||||
err := validateConfigPath(configPath)
|
err := validateConfigPath(configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -177,7 +254,7 @@ func saveNftablesConfig(configPath string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get nftables ruleset: %w", err)
|
return fmt.Errorf("failed to get nftables ruleset: %w", err)
|
||||||
}
|
}
|
||||||
|
// #nosec G204 - managed by system adminstartor
|
||||||
cmd = exec.Command("tee", configPath)
|
cmd = exec.Command("tee", configPath)
|
||||||
stdin, err := cmd.StdinPipe()
|
stdin, err := cmd.StdinPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package blocker
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/d3m0k1d/BanForge/internal/logger"
|
"github.com/d3m0k1d/BanForge/internal/logger"
|
||||||
)
|
)
|
||||||
@@ -22,7 +23,7 @@ func (u *Ufw) Ban(ip string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// #nosec G204 - ip is validated
|
||||||
cmd := exec.Command("ufw", "--force", "deny", "from", ip)
|
cmd := exec.Command("ufw", "--force", "deny", "from", ip)
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -41,7 +42,7 @@ func (u *Ufw) Unban(ip string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// #nosec G204 - ip is validated
|
||||||
cmd := exec.Command("ufw", "--force", "delete", "deny", "from", ip)
|
cmd := exec.Command("ufw", "--force", "delete", "deny", "from", ip)
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -56,6 +57,44 @@ func (u *Ufw) Unban(ip string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *Ufw) PortOpen(port int, protocol string) error {
|
||||||
|
if port >= 0 && port <= 65535 {
|
||||||
|
if protocol != "tcp" && protocol != "udp" {
|
||||||
|
u.logger.Error("invalid protocol")
|
||||||
|
return fmt.Errorf("invalid protocol")
|
||||||
|
}
|
||||||
|
s := strconv.Itoa(port)
|
||||||
|
// #nosec G204 - managed by system adminstartor
|
||||||
|
cmd := exec.Command("ufw", "allow", s+"/"+protocol)
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
u.logger.Error(err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
u.logger.Info("Add port " + s + " " + string(output))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Ufw) PortClose(port int, protocol string) error {
|
||||||
|
if port >= 0 && port <= 65535 {
|
||||||
|
if protocol != "tcp" && protocol != "udp" {
|
||||||
|
u.logger.Error("invalid protocol")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
s := strconv.Itoa(port)
|
||||||
|
// #nosec G204 - managed by system adminstartor
|
||||||
|
cmd := exec.Command("ufw", "deny", s+"/"+protocol)
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
u.logger.Error(err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
u.logger.Info("Add port " + s + " " + string(output))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (u *Ufw) Setup(config string) error {
|
func (u *Ufw) Setup(config string) error {
|
||||||
if config != "" {
|
if config != "" {
|
||||||
fmt.Printf("Ufw dont support config file\n")
|
fmt.Printf("Ufw dont support config file\n")
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -57,13 +58,9 @@ func NewRule(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
err = file.Close()
|
err = errors.Join(err, file.Close())
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
cfg := Rules{Rules: r}
|
cfg := Rules{Rules: r}
|
||||||
|
|
||||||
err = toml.NewEncoder(file).Encode(cfg)
|
err = toml.NewEncoder(file).Encode(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -126,24 +123,24 @@ func EditRule(Name string, ServiceName string, Path string, Status string, Metho
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ParseDurationWithYears(s string) (time.Duration, error) {
|
func ParseDurationWithYears(s string) (time.Duration, error) {
|
||||||
if strings.HasSuffix(s, "y") {
|
if ss, ok := strings.CutSuffix(s, "y"); ok {
|
||||||
years, err := strconv.Atoi(strings.TrimSuffix(s, "y"))
|
years, err := strconv.Atoi(ss)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
return time.Duration(years) * 365 * 24 * time.Hour, nil
|
return time.Duration(years) * 365 * 24 * time.Hour, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasSuffix(s, "M") {
|
if ss, ok := strings.CutSuffix(s, "M"); ok {
|
||||||
months, err := strconv.Atoi(strings.TrimSuffix(s, "M"))
|
months, err := strconv.Atoi(ss)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
return time.Duration(months) * 30 * 24 * time.Hour, nil
|
return time.Duration(months) * 30 * 24 * time.Hour, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasSuffix(s, "d") {
|
if ss, ok := strings.CutSuffix(s, "d"); ok {
|
||||||
days, err := strconv.Atoi(strings.TrimSuffix(s, "d"))
|
days, err := strconv.Atoi(ss)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,5 +28,6 @@ type Rule struct {
|
|||||||
Path string `toml:"path"`
|
Path string `toml:"path"`
|
||||||
Status string `toml:"status"`
|
Status string `toml:"status"`
|
||||||
Method string `toml:"method"`
|
Method string `toml:"method"`
|
||||||
|
MaxRetry int `toml:"max_retry"`
|
||||||
BanTime string `toml:"ban_time"`
|
BanTime string `toml:"ban_time"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
type Judge struct {
|
type Judge struct {
|
||||||
db_r *storage.BanReader
|
db_r *storage.BanReader
|
||||||
db_w *storage.BanWriter
|
db_w *storage.BanWriter
|
||||||
|
db_rq *storage.RequestReader
|
||||||
logger *logger.Logger
|
logger *logger.Logger
|
||||||
Blocker blocker.BlockerEngine
|
Blocker blocker.BlockerEngine
|
||||||
rulesByService map[string][]config.Rule
|
rulesByService map[string][]config.Rule
|
||||||
@@ -24,6 +25,7 @@ type Judge struct {
|
|||||||
func New(
|
func New(
|
||||||
db_r *storage.BanReader,
|
db_r *storage.BanReader,
|
||||||
db_w *storage.BanWriter,
|
db_w *storage.BanWriter,
|
||||||
|
db_rq *storage.RequestReader,
|
||||||
b blocker.BlockerEngine,
|
b blocker.BlockerEngine,
|
||||||
resultCh chan *storage.LogEntry,
|
resultCh chan *storage.LogEntry,
|
||||||
entryCh chan *storage.LogEntry,
|
entryCh chan *storage.LogEntry,
|
||||||
@@ -31,6 +33,7 @@ func New(
|
|||||||
return &Judge{
|
return &Judge{
|
||||||
db_w: db_w,
|
db_w: db_w,
|
||||||
db_r: db_r,
|
db_r: db_r,
|
||||||
|
db_rq: db_rq,
|
||||||
logger: logger.New(false),
|
logger: logger.New(false),
|
||||||
rulesByService: make(map[string][]config.Rule),
|
rulesByService: make(map[string][]config.Rule),
|
||||||
Blocker: b,
|
Blocker: b,
|
||||||
@@ -75,31 +78,28 @@ func (j *Judge) Tribunal() {
|
|||||||
methodMatch := rule.Method == "" || entry.Method == rule.Method
|
methodMatch := rule.Method == "" || entry.Method == rule.Method
|
||||||
statusMatch := rule.Status == "" || entry.Status == rule.Status
|
statusMatch := rule.Status == "" || entry.Status == rule.Status
|
||||||
pathMatch := matchPath(entry.Path, rule.Path)
|
pathMatch := matchPath(entry.Path, rule.Path)
|
||||||
|
|
||||||
j.logger.Debug(
|
|
||||||
"Testing rule",
|
|
||||||
"rule", rule.Name,
|
|
||||||
"method_match", methodMatch,
|
|
||||||
"status_match", statusMatch,
|
|
||||||
"path_match", pathMatch,
|
|
||||||
)
|
|
||||||
|
|
||||||
if methodMatch && statusMatch && pathMatch {
|
if methodMatch && statusMatch && pathMatch {
|
||||||
ruleMatched = true
|
ruleMatched = true
|
||||||
j.logger.Info("Rule matched", "rule", rule.Name, "ip", entry.IP)
|
j.logger.Info("Rule matched", "rule", rule.Name, "ip", entry.IP)
|
||||||
|
j.resultCh <- entry
|
||||||
banned, err := j.db_r.IsBanned(entry.IP)
|
banned, err := j.db_r.IsBanned(entry.IP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
j.logger.Error("Failed to check ban status", "ip", entry.IP, "error", err)
|
j.logger.Error("Failed to check ban status", "ip", entry.IP, "error", err)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if banned {
|
if banned {
|
||||||
j.logger.Info("IP already banned", "ip", entry.IP)
|
j.logger.Info("IP already banned", "ip", entry.IP)
|
||||||
j.resultCh <- entry
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
exceeded, err := j.db_rq.IsMaxRetryExceeded(entry.IP, rule.MaxRetry)
|
||||||
|
if err != nil {
|
||||||
|
j.logger.Error("Failed to check retry count", "ip", entry.IP, "error", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !exceeded {
|
||||||
|
j.logger.Info("Max retry not exceeded", "ip", entry.IP)
|
||||||
|
break
|
||||||
|
}
|
||||||
err = j.db_w.AddBan(entry.IP, rule.BanTime, rule.Name)
|
err = j.db_w.AddBan(entry.IP, rule.BanTime, rule.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
j.logger.Error(
|
j.logger.Error(
|
||||||
@@ -127,7 +127,6 @@ func (j *Judge) Tribunal() {
|
|||||||
"ban_time",
|
"ban_time",
|
||||||
rule.BanTime,
|
rule.BanTime,
|
||||||
)
|
)
|
||||||
j.resultCh <- entry
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ type Scanner struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewScannerTail(path string) (*Scanner, error) {
|
func NewScannerTail(path string) (*Scanner, error) {
|
||||||
|
// #nosec G204 - managed by system adminstartor
|
||||||
cmd := exec.Command("tail", "-F", "-n", "10", path)
|
cmd := exec.Command("tail", "-F", "-n", "10", path)
|
||||||
stdout, err := cmd.StdoutPipe()
|
stdout, err := cmd.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -46,6 +47,7 @@ func NewScannerTail(path string) (*Scanner, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewScannerJournald(unit string) (*Scanner, error) {
|
func NewScannerJournald(unit string) (*Scanner, error) {
|
||||||
|
// #nosec G204 - managed by system adminstartor
|
||||||
cmd := exec.Command("journalctl", "-u", unit, "-f", "-n", "0", "-o", "short", "--no-pager")
|
cmd := exec.Command("journalctl", "-u", unit, "-f", "-n", "0", "-o", "short", "--no-pager")
|
||||||
stdout, err := cmd.StdoutPipe()
|
stdout, err := cmd.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ type BanWriter struct {
|
|||||||
func NewBanWriter() (*BanWriter, error) {
|
func NewBanWriter() (*BanWriter, error) {
|
||||||
db, err := sql.Open(
|
db, err := sql.Open(
|
||||||
"sqlite",
|
"sqlite",
|
||||||
"/var/lib/banforge/bans.db?_pragma=journal_mode(WAL)&_pragma=busy_timeout(30000)&_pragma=synchronous(NORMAL)",
|
buildSqliteDsn(banDBPath, pragmas),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -175,7 +175,6 @@ func (d *BanReader) IsBanned(ip string) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *BanReader) BanList() error {
|
func (d *BanReader) BanList() error {
|
||||||
|
|
||||||
var count int
|
var count int
|
||||||
t := table.NewWriter()
|
t := table.NewWriter()
|
||||||
t.SetOutputMirror(os.Stdout)
|
t.SetOutputMirror(os.Stdout)
|
||||||
|
|||||||
@@ -2,55 +2,60 @@ package storage
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
_ "modernc.org/sqlite"
|
_ "modernc.org/sqlite"
|
||||||
)
|
)
|
||||||
|
|
||||||
func CreateTables() error {
|
const (
|
||||||
// Requests DB
|
DBDir = "/var/lib/banforge/"
|
||||||
db_r, err := sql.Open("sqlite",
|
ReqDBPath = DBDir + "requests.db"
|
||||||
"/var/lib/banforge/requests.db?"+
|
banDBPath = DBDir + "bans.db"
|
||||||
"mode=rwc&"+
|
)
|
||||||
"_pragma=journal_mode(WAL)&"+
|
|
||||||
"_pragma=busy_timeout(30000)&"+
|
|
||||||
"_pragma=synchronous(NORMAL)")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to open requests db: %w", err)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
err = db_r.Close()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
_, err = db_r.Exec(CreateRequestsTable)
|
var pragmas = map[string]string{
|
||||||
if err != nil {
|
`journal_mode`: `wal`,
|
||||||
return fmt.Errorf("failed to create requests table: %w", err)
|
`synchronous`: `normal`,
|
||||||
}
|
`busy_timeout`: `30000`,
|
||||||
|
// also consider these
|
||||||
// Bans DB
|
// `temp_store`: `memory`,
|
||||||
db_b, err := sql.Open("sqlite",
|
// `cache_size`: `1000000000`,
|
||||||
"/var/lib/banforge/bans.db?"+
|
}
|
||||||
"mode=rwc&"+
|
|
||||||
"_pragma=journal_mode(WAL)&"+
|
func buildSqliteDsn(path string, pragmas map[string]string) string {
|
||||||
"_pragma=busy_timeout(30000)&"+
|
pragmastrs := make([]string, len(pragmas))
|
||||||
"_pragma=synchronous(FULL)")
|
i := 0
|
||||||
if err != nil {
|
for k, v := range pragmas {
|
||||||
return fmt.Errorf("failed to open bans db: %w", err)
|
pragmastrs[i] = (fmt.Sprintf(`pragma=%s(%s)`, k, v))
|
||||||
}
|
i++
|
||||||
defer func() {
|
}
|
||||||
err = db_b.Close()
|
return path + "?" + "mode=rwc&" + strings.Join(pragmastrs, "&")
|
||||||
if err != nil {
|
}
|
||||||
fmt.Println(err)
|
|
||||||
}
|
func initDB(dsn, sqlstr string) (err error) {
|
||||||
}()
|
db, err := sql.Open("sqlite", dsn)
|
||||||
|
if err != nil {
|
||||||
_, err = db_b.Exec(CreateBansTable)
|
return fmt.Errorf("failed to open %q: %w", dsn, err)
|
||||||
if err != nil {
|
}
|
||||||
return fmt.Errorf("failed to create bans table: %w", err)
|
defer func() {
|
||||||
}
|
closeErr := db.Close()
|
||||||
fmt.Println("Tables created successfully!")
|
if closeErr != nil {
|
||||||
return nil
|
err = errors.Join(err, fmt.Errorf("failed to close %q: %w", dsn, closeErr))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
_, err = db.Exec(sqlstr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create table: %w", err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateTables() (err error) {
|
||||||
|
// Requests DB
|
||||||
|
err1 := initDB(buildSqliteDsn(ReqDBPath, pragmas), CreateRequestsTable)
|
||||||
|
err2 := initDB(buildSqliteDsn(banDBPath, pragmas), CreateBansTable)
|
||||||
|
|
||||||
|
return errors.Join(err1, err2)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,15 +7,15 @@ import (
|
|||||||
_ "modernc.org/sqlite"
|
_ "modernc.org/sqlite"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Request_Writer struct {
|
type RequestWriter struct {
|
||||||
logger *logger.Logger
|
logger *logger.Logger
|
||||||
db *sql.DB
|
db *sql.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRequestsWr() (*Request_Writer, error) {
|
func NewRequestsWr() (*RequestWriter, error) {
|
||||||
db, err := sql.Open(
|
db, err := sql.Open(
|
||||||
"sqlite",
|
"sqlite",
|
||||||
"/var/lib/banforge/requests.db?_pragma=journal_mode(WAL)&_pragma=busy_timeout(30000)&_pragma=synchronous(NORMAL)",
|
buildSqliteDsn(ReqDBPath, pragmas),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -23,8 +23,41 @@ func NewRequestsWr() (*Request_Writer, error) {
|
|||||||
db.SetMaxOpenConns(1)
|
db.SetMaxOpenConns(1)
|
||||||
db.SetMaxIdleConns(1)
|
db.SetMaxIdleConns(1)
|
||||||
db.SetConnMaxLifetime(0)
|
db.SetConnMaxLifetime(0)
|
||||||
return &Request_Writer{
|
return &RequestWriter{
|
||||||
logger: logger.New(false),
|
logger: logger.New(false),
|
||||||
db: db,
|
db: db,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RequestReader struct {
|
||||||
|
logger *logger.Logger
|
||||||
|
db *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRequestsRd() (*RequestReader, error) {
|
||||||
|
db, err := sql.Open(
|
||||||
|
"sqlite",
|
||||||
|
buildSqliteDsn(ReqDBPath, pragmas),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
db.SetMaxOpenConns(1)
|
||||||
|
db.SetMaxIdleConns(1)
|
||||||
|
db.SetConnMaxLifetime(0)
|
||||||
|
return &RequestReader{
|
||||||
|
logger: logger.New(false),
|
||||||
|
db: db,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RequestReader) IsMaxRetryExceeded(ip string, maxRetry int) (bool, error) {
|
||||||
|
var count int
|
||||||
|
err := r.db.QueryRow("SELECT COUNT(*) FROM requests WHERE ip = ?", ip).Scan(&count)
|
||||||
|
if err != nil {
|
||||||
|
r.logger.Error("error query count: " + err.Error())
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
r.logger.Info("Current request count for IP", "ip", ip, "count", count, "maxRetry", maxRetry)
|
||||||
|
return count >= maxRetry, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
package storage
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func WriteReq(db *Request_Writer, resultCh <-chan *LogEntry) {
|
func WriteReq(db *RequestWriter, resultCh <-chan *LogEntry) {
|
||||||
db.logger.Info("Starting log writer")
|
db.logger.Info("Starting log writer")
|
||||||
const batchSize = 100
|
const batchSize = 100
|
||||||
const flushInterval = 1 * time.Second
|
const flushInterval = 1 * time.Second
|
||||||
@@ -14,29 +17,36 @@ func WriteReq(db *Request_Writer, resultCh <-chan *LogEntry) {
|
|||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
flush := func() {
|
flush := func() {
|
||||||
|
defer db.logger.Debug("Flushed batch", "count", len(batch))
|
||||||
|
err := func() (err error) {
|
||||||
if len(batch) == 0 {
|
if len(batch) == 0 {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
tx, err := db.db.Begin()
|
tx, err := db.db.Begin()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
db.logger.Error("Failed to begin transaction", "error", err)
|
return fmt.Errorf("failed to begin transaction: %w", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
defer func() {
|
||||||
|
if rollbackErr := tx.Rollback(); rollbackErr != nil &&
|
||||||
|
!errors.Is(rollbackErr, sql.ErrTxDone) {
|
||||||
|
err = errors.Join(
|
||||||
|
err,
|
||||||
|
fmt.Errorf("failed to rollback transaction: %w", rollbackErr),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
stmt, err := tx.Prepare(
|
stmt, err := tx.Prepare(
|
||||||
"INSERT INTO requests (service, ip, path, method, status, created_at) VALUES (?, ?, ?, ?, ?, ?)",
|
"INSERT INTO requests (service, ip, path, method, status, created_at) VALUES (?, ?, ?, ?, ?, ?)",
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
db.logger.Error("Failed to prepare statement", "error", err)
|
err = fmt.Errorf("failed to prepare statement: %w", err)
|
||||||
if rollbackErr := tx.Rollback(); rollbackErr != nil {
|
return err
|
||||||
db.logger.Error("Failed to rollback transaction", "error", rollbackErr)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if closeErr := stmt.Close(); closeErr != nil {
|
if closeErr := stmt.Close(); closeErr != nil {
|
||||||
db.logger.Error("Failed to close statement", "error", closeErr)
|
err = errors.Join(err, fmt.Errorf("failed to close statement: %w", closeErr))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -50,17 +60,20 @@ func WriteReq(db *Request_Writer, resultCh <-chan *LogEntry) {
|
|||||||
time.Now().Format(time.RFC3339),
|
time.Now().Format(time.RFC3339),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
db.logger.Error("Failed to insert entry", "error", err)
|
db.logger.Error(fmt.Errorf("failed to insert entry: %w", err).Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tx.Commit(); err != nil {
|
if err := tx.Commit(); err != nil {
|
||||||
db.logger.Error("Failed to commit transaction", "error", err)
|
return fmt.Errorf("failed to commit transaction: %w", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
db.logger.Debug("Flushed batch", "count", len(batch))
|
|
||||||
batch = batch[:0]
|
batch = batch[:0]
|
||||||
|
return err
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
db.logger.Error(err.Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
@@ -81,3 +94,13 @@ func WriteReq(db *Request_Writer, resultCh <-chan *LogEntry) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *RequestWriter) GetRequestCount() (int, error) {
|
||||||
|
var count int
|
||||||
|
err := w.db.QueryRow("SELECT COUNT(*) FROM requests").Scan(&count)
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *RequestWriter) Close() error {
|
||||||
|
return w.db.Close()
|
||||||
|
}
|
||||||
|
|||||||
@@ -277,7 +277,7 @@ func TestWrite_ChannelClosed(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRequestWriterWithDBPath(dbPath string) (*Request_Writer, error) {
|
func NewRequestWriterWithDBPath(dbPath string) (*RequestWriter, error) {
|
||||||
db, err := sql.Open("sqlite", dbPath+"?_pragma=journal_mode(WAL)&_pragma=busy_timeout(30000)&_pragma=synchronous(NORMAL)")
|
db, err := sql.Open("sqlite", dbPath+"?_pragma=journal_mode(WAL)&_pragma=busy_timeout(30000)&_pragma=synchronous(NORMAL)")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -285,13 +285,13 @@ func NewRequestWriterWithDBPath(dbPath string) (*Request_Writer, error) {
|
|||||||
db.SetMaxOpenConns(1)
|
db.SetMaxOpenConns(1)
|
||||||
db.SetMaxIdleConns(1)
|
db.SetMaxIdleConns(1)
|
||||||
db.SetConnMaxLifetime(0)
|
db.SetConnMaxLifetime(0)
|
||||||
return &Request_Writer{
|
return &RequestWriter{
|
||||||
logger: logger.New(false),
|
logger: logger.New(false),
|
||||||
db: db,
|
db: db,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Request_Writer) CreateTable() error {
|
func (w *RequestWriter) CreateTable() error {
|
||||||
_, err := w.db.Exec(CreateRequestsTable)
|
_, err := w.db.Exec(CreateRequestsTable)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -299,21 +299,3 @@ func (w *Request_Writer) CreateTable() error {
|
|||||||
w.logger.Info("Created requests table")
|
w.logger.Info("Created requests table")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Request_Writer) Close() error {
|
|
||||||
w.logger.Info("Closing request database connection")
|
|
||||||
err := w.db.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Request_Writer) GetRequestCount() (int, error) {
|
|
||||||
var count int
|
|
||||||
err := w.db.QueryRow("SELECT COUNT(*) FROM requests").Scan(&count)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return count, nil
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user