Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1603fbee35 | ||
|
|
bbb152dfb8 | ||
|
|
a7b79d0e27 | ||
|
|
eaf276bd3f | ||
|
|
14c6c64989 | ||
|
|
623bd87b4c | ||
|
|
7d9645b3e3 | ||
|
|
bf6ff50da8 | ||
|
|
85f6919bda | ||
|
|
7a7f57f5ae | ||
|
|
36508201ad | ||
|
|
3cb9bcbcf3 |
3
Makefile
3
Makefile
@@ -25,3 +25,6 @@ clean:
|
|||||||
|
|
||||||
test:
|
test:
|
||||||
go test ./...
|
go test ./...
|
||||||
|
|
||||||
|
test-cover:
|
||||||
|
go test -cover ./...
|
||||||
|
|||||||
28
README.md
28
README.md
@@ -1,7 +1,9 @@
|
|||||||
# BanForge
|
# BanForge
|
||||||
|
|
||||||
Log-based IPS system written in Go for Linux based system.
|
Log-based IPS system written in Go for Linux-based system.
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/github.com/d3m0k1d/BanForge)
|
||||||
|
[](https://github.com/d3m0k1d/BanForge/blob/master/LICENSE)
|
||||||
# Table of contents
|
# Table of contents
|
||||||
1. [Overview](#overview)
|
1. [Overview](#overview)
|
||||||
2. [Requirements](#requirements)
|
2. [Requirements](#requirements)
|
||||||
@@ -12,24 +14,40 @@ Log-based IPS system written in Go for Linux based system.
|
|||||||
# Overview
|
# Overview
|
||||||
BanForge is a simple IPS for replacement fail2ban in Linux system.
|
BanForge is a simple IPS for replacement fail2ban in Linux system.
|
||||||
The project is currently in its early stages of development.
|
The project is currently in its early stages of development.
|
||||||
All release are available on my self-hosted [Gitea](https://gitea.d3m0k1d.ru/d3m0k1d/BanForge) because Github have limit for Actions.
|
All release are available on my self-hosted [Gitea](https://gitea.d3m0k1d.ru/d3m0k1d/BanForge) because Github has limits for Actions.
|
||||||
If you have any questions or suggestions, create issue on [Github](https://github.com/d3m0k1d/BanForge/issues).
|
If you have any questions or suggestions, create issue on [Github](https://github.com/d3m0k1d/BanForge/issues).
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
- [ ] Real-time Nginx log monitoring
|
- [x] Real-time Nginx log monitoring
|
||||||
- [ ] Add support for other service
|
- [ ] Add support for other service
|
||||||
- [ ] Add support for user service with regular expressions
|
- [ ] Add support for user service with regular expressions
|
||||||
- [ ] TUI interface
|
- [ ] TUI interface
|
||||||
|
|
||||||
# Requirements
|
# Requirements
|
||||||
|
|
||||||
- Go 1.21+
|
- Go 1.25+
|
||||||
- ufw/iptables/nftables/firewalld
|
- ufw/iptables/nftables/firewalld
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
currently no binary file if you wanna build the project yourself, you can use [Makefile](https://github.com/d3m0k1d/BanForge/blob/master/Makefile)
|
Search for a release on the [Gitea](https://gitea.d3m0k1d.ru/d3m0k1d/BanForge/releases) releases page and download it. Then create or copy a systemd unit file.
|
||||||
|
Or clone the repo and use the Makefile.
|
||||||
|
```
|
||||||
|
git clone https://gitea.d3m0k1d.ru/d3m0k1d/BanForge.git
|
||||||
|
cd BanForge
|
||||||
|
sudo make build-daemon
|
||||||
|
cd bin
|
||||||
|
```
|
||||||
|
|
||||||
# Usage
|
# Usage
|
||||||
|
For first steps use this commands
|
||||||
|
```bash
|
||||||
|
banforge init # Create config files and database
|
||||||
|
banforge daemon # Start BanForge daemon (use systemd or another init system to create a service)
|
||||||
|
```
|
||||||
|
You can edit the config file with examples in
|
||||||
|
- `/etc/banforge/config.toml` main config file
|
||||||
|
- `/etc/banforge/rules.toml` ban rules
|
||||||
|
For more information see the [docs](https://github.com/d3m0k1d/BanForge/docs).
|
||||||
|
|
||||||
# License
|
# License
|
||||||
The project is licensed under the [GPL-3.0](https://github.com/d3m0k1d/BanForge/blob/master/LICENSE)
|
The project is licensed under the [GPL-3.0](https://github.com/d3m0k1d/BanForge/blob/master/LICENSE)
|
||||||
|
|||||||
91
cmd/banforge/command/daemon.go
Normal file
91
cmd/banforge/command/daemon.go
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/d3m0k1d/BanForge/internal/blocker"
|
||||||
|
"github.com/d3m0k1d/BanForge/internal/config"
|
||||||
|
"github.com/d3m0k1d/BanForge/internal/judge"
|
||||||
|
"github.com/d3m0k1d/BanForge/internal/logger"
|
||||||
|
"github.com/d3m0k1d/BanForge/internal/parser"
|
||||||
|
"github.com/d3m0k1d/BanForge/internal/storage"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var DaemonCmd = &cobra.Command{
|
||||||
|
Use: "daemon",
|
||||||
|
Short: "Run BanForge daemon process",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
log := logger.New(false)
|
||||||
|
log.Info("Starting BanForge daemon")
|
||||||
|
db, err := storage.NewDB()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to create database", "error", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err = db.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to close database connection", "error", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
cfg, err := config.LoadConfig()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to load config", "error", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
var b blocker.BlockerEngine
|
||||||
|
fw := cfg.Firewall.Name
|
||||||
|
b = blocker.GetBlocker(fw, cfg.Firewall.Config)
|
||||||
|
r, err := config.LoadRuleConfig()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to load rules", "error", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
j := judge.New(db, b)
|
||||||
|
j.LoadRules(r)
|
||||||
|
go func() {
|
||||||
|
ticker := time.NewTicker(5 * time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
|
for range ticker.C {
|
||||||
|
if err := j.ProcessUnviewed(); err != nil {
|
||||||
|
log.Error("Failed to process unviewed", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for _, svc := range cfg.Service {
|
||||||
|
log.Info("Processing service", "name", svc.Name, "enabled", svc.Enabled, "path", svc.LogPath)
|
||||||
|
|
||||||
|
if !svc.Enabled {
|
||||||
|
log.Info("Service disabled, skipping", "name", svc.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if svc.Name != "nginx" {
|
||||||
|
log.Info("Only nginx supported, skipping", "name", svc.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Starting parser for service", "name", svc.Name, "path", svc.LogPath)
|
||||||
|
|
||||||
|
pars, err := parser.NewScanner(svc.LogPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to create scanner", "service", svc.Name, "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
go pars.Start()
|
||||||
|
go func(p *parser.Scanner, serviceName string) {
|
||||||
|
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)
|
||||||
|
}(pars, svc.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {}
|
||||||
|
},
|
||||||
|
}
|
||||||
84
cmd/banforge/command/fw.go
Normal file
84
cmd/banforge/command/fw.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/d3m0k1d/BanForge/internal/blocker"
|
||||||
|
"github.com/d3m0k1d/BanForge/internal/config"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ip string
|
||||||
|
)
|
||||||
|
var UnbanCmd = &cobra.Command{
|
||||||
|
Use: "unban",
|
||||||
|
Short: "Unban IP",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
cfg, err := config.LoadConfig()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fw := cfg.Firewall.Name
|
||||||
|
b := blocker.GetBlocker(fw, cfg.Firewall.Config)
|
||||||
|
if ip == "" {
|
||||||
|
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 {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
err = b.Unban(ip)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Println("IP unblocked successfully!")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var BanCmd = &cobra.Command{
|
||||||
|
Use: "ban",
|
||||||
|
Short: "Ban IP",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
|
||||||
|
cfg, err := config.LoadConfig()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fw := cfg.Firewall.Name
|
||||||
|
b := blocker.GetBlocker(fw, cfg.Firewall.Config)
|
||||||
|
if ip == "" {
|
||||||
|
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 {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
err = b.Ban(ip)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Println("IP unblocked successfully!")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func FwRegister() {
|
||||||
|
BanCmd.Flags().StringVarP(&ip, "ip", "i", "", "ip to ban")
|
||||||
|
UnbanCmd.Flags().StringVarP(&ip, "ip", "i", "", "ip to unban")
|
||||||
|
}
|
||||||
106
cmd/banforge/command/init.go
Normal file
106
cmd/banforge/command/init.go
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/d3m0k1d/BanForge/internal/blocker"
|
||||||
|
"github.com/d3m0k1d/BanForge/internal/config"
|
||||||
|
"github.com/d3m0k1d/BanForge/internal/storage"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var InitCmd = &cobra.Command{
|
||||||
|
Use: "init",
|
||||||
|
Short: "Initialize BanForge",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
fmt.Println("Initializing BanForge...")
|
||||||
|
|
||||||
|
if _, err := os.Stat("/var/log/banforge"); err == nil {
|
||||||
|
fmt.Println("/var/log/banforge already exists, skipping...")
|
||||||
|
} else if os.IsNotExist(err) {
|
||||||
|
err := os.Mkdir("/var/log/banforge", 0750)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Println("Created /var/log/banforge")
|
||||||
|
} else {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat("/var/lib/banforge"); err == nil {
|
||||||
|
fmt.Println("/var/lib/banforge already exists, skipping...")
|
||||||
|
} else if os.IsNotExist(err) {
|
||||||
|
err := os.Mkdir("/var/lib/banforge", 0750)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Println("Created /var/lib/banforge")
|
||||||
|
} else {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat("/etc/banforge"); err == nil {
|
||||||
|
fmt.Println("/etc/banforge already exists, skipping...")
|
||||||
|
} else if os.IsNotExist(err) {
|
||||||
|
err := os.Mkdir("/etc/banforge", 0750)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Println("Created /etc/banforge")
|
||||||
|
} else {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := config.CreateConf()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Println("Config created")
|
||||||
|
|
||||||
|
err = config.FindFirewall()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
cfg, err := config.LoadConfig()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
b := blocker.GetBlocker(cfg.Firewall.Name, cfg.Firewall.Config)
|
||||||
|
err = b.Setup(cfg.Firewall.Config)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Println("Firewall configured")
|
||||||
|
|
||||||
|
db, err := storage.NewDB()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
err = db.CreateTable()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err = db.Close()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
fmt.Println("Firewall detected and configured")
|
||||||
|
|
||||||
|
fmt.Println("BanForge initialized successfully!")
|
||||||
|
},
|
||||||
|
}
|
||||||
73
cmd/banforge/command/rule.go
Normal file
73
cmd/banforge/command/rule.go
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/d3m0k1d/BanForge/internal/config"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
name string
|
||||||
|
service string
|
||||||
|
path string
|
||||||
|
status string
|
||||||
|
method string
|
||||||
|
)
|
||||||
|
|
||||||
|
var RuleCmd = &cobra.Command{
|
||||||
|
Use: "rule",
|
||||||
|
Short: "Manage rules",
|
||||||
|
}
|
||||||
|
|
||||||
|
var AddCmd = &cobra.Command{
|
||||||
|
Use: "add",
|
||||||
|
Short: "CLI interface for add new rule to file /etc/banforge/rules.toml",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
if name == "" {
|
||||||
|
fmt.Printf("Rule name can't be empty\n")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if service == "" {
|
||||||
|
fmt.Printf("Service name can't be empty\n")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if path == "" && status == "" && method == "" {
|
||||||
|
fmt.Printf("At least 1 rule field must be filled in.")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := config.NewRule(name, service, path, status, method)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Println("Rule added successfully!")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var ListCmd = &cobra.Command{
|
||||||
|
Use: "list",
|
||||||
|
Short: "List rules",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
r, err := config.LoadRuleConfig()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
for _, rule := range r {
|
||||||
|
fmt.Printf("Name: %s\nService: %s\nPath: %s\nStatus: %s\nMethod: %s\n\n", rule.Name, rule.ServiceName, rule.Path, rule.Status, rule.Method)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func RuleRegister() {
|
||||||
|
RuleCmd.AddCommand(AddCmd)
|
||||||
|
RuleCmd.AddCommand(ListCmd)
|
||||||
|
AddCmd.Flags().StringVarP(&name, "name", "n", "", "rule name (required)")
|
||||||
|
AddCmd.Flags().StringVarP(&service, "service", "s", "", "service name")
|
||||||
|
AddCmd.Flags().StringVarP(&path, "path", "p", "", "request path")
|
||||||
|
AddCmd.Flags().StringVarP(&status, "status", "c", "", "HTTP status code")
|
||||||
|
AddCmd.Flags().StringVarP(&method, "method", "m", "", "HTTP method")
|
||||||
|
}
|
||||||
@@ -3,14 +3,9 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/d3m0k1d/BanForge/internal/blocker"
|
"github.com/d3m0k1d/BanForge/cmd/banforge/command"
|
||||||
"github.com/d3m0k1d/BanForge/internal/config"
|
|
||||||
"github.com/d3m0k1d/BanForge/internal/judge"
|
|
||||||
"github.com/d3m0k1d/BanForge/internal/logger"
|
|
||||||
"github.com/d3m0k1d/BanForge/internal/parser"
|
|
||||||
"github.com/d3m0k1d/BanForge/internal/storage"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -22,184 +17,18 @@ var rootCmd = &cobra.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var initCmd = &cobra.Command{
|
|
||||||
Use: "init",
|
|
||||||
Short: "Initialize BanForge",
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
fmt.Println("Initializing BanForge...")
|
|
||||||
|
|
||||||
if _, err := os.Stat("/var/log/banforge"); err == nil {
|
|
||||||
fmt.Println("/var/log/banforge already exists, skipping...")
|
|
||||||
} else if os.IsNotExist(err) {
|
|
||||||
err := os.Mkdir("/var/log/banforge", 0750)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
fmt.Println("Created /var/log/banforge")
|
|
||||||
} else {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
if _, err := os.Stat("/var/lib/banforge"); err == nil {
|
|
||||||
fmt.Println("/var/lib/banforge already exists, skipping...")
|
|
||||||
} else if os.IsNotExist(err) {
|
|
||||||
err := os.Mkdir("/var/lib/banforge", 0750)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
fmt.Println("Created /var/lib/banforge")
|
|
||||||
} else {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := os.Stat("/etc/banforge"); err == nil {
|
|
||||||
fmt.Println("/etc/banforge already exists, skipping...")
|
|
||||||
} else if os.IsNotExist(err) {
|
|
||||||
err := os.Mkdir("/etc/banforge", 0750)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
fmt.Println("Created /etc/banforge")
|
|
||||||
} else {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := config.CreateConf()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
fmt.Println("Config created")
|
|
||||||
|
|
||||||
err = config.FindFirewall()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
db, err := storage.NewDB()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
err = db.CreateTable()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
err = db.Close()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
fmt.Println("Firewall detected and configured")
|
|
||||||
|
|
||||||
fmt.Println("BanForge initialized successfully!")
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var daemonCmd = &cobra.Command{
|
|
||||||
Use: "daemon",
|
|
||||||
Short: "Run BanForge daemon process",
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
log := logger.New(false)
|
|
||||||
log.Info("Starting BanForge daemon")
|
|
||||||
db, err := storage.NewDB()
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Failed to create database", "error", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
err = db.Close()
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Failed to close database connection", "error", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
cfg, err := config.LoadConfig()
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Failed to load config", "error", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
var b blocker.BlockerEngine
|
|
||||||
fw := cfg.Firewall.Name
|
|
||||||
switch fw {
|
|
||||||
case "ufw":
|
|
||||||
b = blocker.NewUfw(log)
|
|
||||||
case "iptables":
|
|
||||||
b = blocker.NewIptables(log, cfg.Firewall.Config)
|
|
||||||
case "nftables":
|
|
||||||
b = blocker.NewNftables(log, cfg.Firewall.Config)
|
|
||||||
case "firewalld":
|
|
||||||
b = blocker.NewFirewalld(log)
|
|
||||||
default:
|
|
||||||
log.Error("Unknown firewall", "firewall", fw)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
r, err := config.LoadRuleConfig()
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Failed to load rules", "error", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
j := judge.New(db, b)
|
|
||||||
j.LoadRules(r)
|
|
||||||
go func() {
|
|
||||||
ticker := time.NewTicker(5 * time.Second)
|
|
||||||
defer ticker.Stop()
|
|
||||||
for range ticker.C {
|
|
||||||
if err := j.ProcessUnviewed(); err != nil {
|
|
||||||
log.Error("Failed to process unviewed", "error", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
for _, svc := range cfg.Service {
|
|
||||||
log.Info("Processing service", "name", svc.Name, "enabled", svc.Enabled, "path", svc.LogPath)
|
|
||||||
|
|
||||||
if !svc.Enabled {
|
|
||||||
log.Info("Service disabled, skipping", "name", svc.Name)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if svc.Name != "nginx" {
|
|
||||||
log.Info("Only nginx supported, skipping", "name", svc.Name)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Starting parser for service", "name", svc.Name, "path", svc.LogPath)
|
|
||||||
|
|
||||||
pars, err := parser.NewScanner(svc.LogPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Failed to create scanner", "service", svc.Name, "error", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
go pars.Start()
|
|
||||||
go func(p *parser.Scanner, serviceName string) {
|
|
||||||
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)
|
|
||||||
}(pars, svc.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
select {}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func Init() {
|
func Init() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Execute() {
|
func Execute() {
|
||||||
rootCmd.AddCommand(daemonCmd)
|
rootCmd.AddCommand(command.DaemonCmd)
|
||||||
rootCmd.AddCommand(initCmd)
|
rootCmd.AddCommand(command.InitCmd)
|
||||||
|
rootCmd.AddCommand(command.RuleCmd)
|
||||||
|
rootCmd.AddCommand(command.BanCmd)
|
||||||
|
rootCmd.AddCommand(command.UnbanCmd)
|
||||||
|
command.RuleRegister()
|
||||||
|
command.FwRegister()
|
||||||
if err := rootCmd.Execute(); err != nil {
|
if err := rootCmd.Execute(); err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|||||||
51
docs/cli.md
Normal file
51
docs/cli.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# CLI commands BanForge
|
||||||
|
BanForge provides a command-line interface (CLI) to manage IP blocking,
|
||||||
|
configure detection rules, and control the daemon process.
|
||||||
|
## Commands
|
||||||
|
### init - create a deps file
|
||||||
|
|
||||||
|
```shell
|
||||||
|
banforge init
|
||||||
|
```
|
||||||
|
|
||||||
|
**Description**
|
||||||
|
This command creates the necessary directories and base configuration files
|
||||||
|
required for the daemon to operate.
|
||||||
|
### daemon - Starts the BanForge daemon process
|
||||||
|
|
||||||
|
```shell
|
||||||
|
banforge daemon
|
||||||
|
```
|
||||||
|
|
||||||
|
**Description**
|
||||||
|
This command starts the BanForge daemon process in the background.
|
||||||
|
The daemon continuously monitors incoming requests, detects anomalies,
|
||||||
|
and applies firewall rules in real-time.
|
||||||
|
|
||||||
|
### firewall - Manages firewall rules
|
||||||
|
```shell
|
||||||
|
banforge ban <ip>
|
||||||
|
banforge unban <ip>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Description**
|
||||||
|
These commands provide an abstraction over your firewall. If you want to simplify the interface to your firewall, you can use these commands.
|
||||||
|
|
||||||
|
### rule - Manages detection rules
|
||||||
|
|
||||||
|
```shell
|
||||||
|
banforge rule add -n rule.name -c 403
|
||||||
|
banforge rule list
|
||||||
|
```
|
||||||
|
|
||||||
|
**Description**
|
||||||
|
These command help you to create and manage detection rules in CLI interface.
|
||||||
|
|
||||||
|
| Flag | Required |
|
||||||
|
| ----------- | -------- |
|
||||||
|
| -n -name | + |
|
||||||
|
| -s -service | + |
|
||||||
|
| -p -path | - |
|
||||||
|
| -m -method | - |
|
||||||
|
| -c -status | - |
|
||||||
|
You must specify at least 1 of the optional flags to create a rule.
|
||||||
0
docs/config.md
Normal file
0
docs/config.md
Normal file
@@ -57,3 +57,7 @@ func (f *Firewalld) Unban(ip string) error {
|
|||||||
f.logger.Info("Reload " + string(output))
|
f.logger.Info("Reload " + string(output))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *Firewalld) Setup(config string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,28 @@
|
|||||||
package blocker
|
package blocker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/d3m0k1d/BanForge/internal/logger"
|
||||||
|
)
|
||||||
|
|
||||||
type BlockerEngine interface {
|
type BlockerEngine interface {
|
||||||
Ban(ip string) error
|
Ban(ip string) error
|
||||||
Unban(ip string) error
|
Unban(ip string) error
|
||||||
|
Setup(config string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetBlocker(fw string, config string) BlockerEngine {
|
||||||
|
switch fw {
|
||||||
|
case "ufw":
|
||||||
|
return NewUfw(logger.New(false))
|
||||||
|
case "iptables":
|
||||||
|
return NewIptables(logger.New(false), config)
|
||||||
|
case "nftables":
|
||||||
|
return NewNftables(logger.New(false), config)
|
||||||
|
case "firewalld":
|
||||||
|
return NewFirewalld(logger.New(false))
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("Unknown firewall: %s", fw))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,3 +101,7 @@ func (f *Iptables) Unban(ip string) error {
|
|||||||
"output", string(output))
|
"output", string(output))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *Iptables) Setup(config string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -55,3 +55,28 @@ func (u *Ufw) Unban(ip string) error {
|
|||||||
u.logger.Info("IP unbanned", "ip", ip, "output", string(output))
|
u.logger.Info("IP unbanned", "ip", ip, "output", string(output))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *Ufw) Setup(config string) error {
|
||||||
|
if config != "" {
|
||||||
|
fmt.Printf("Ufw dont support config file\n")
|
||||||
|
cmd := exec.Command("sudo", "ufw", "enable")
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
u.logger.Error("failed to enable ufw",
|
||||||
|
"error", err.Error(),
|
||||||
|
"output", string(output))
|
||||||
|
return fmt.Errorf("failed to enable ufw: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if config == "" {
|
||||||
|
cmd := exec.Command("sudo", "ufw", "enable")
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
u.logger.Error("failed to enable ufw",
|
||||||
|
"error", err.Error(),
|
||||||
|
"output", string(output))
|
||||||
|
return fmt.Errorf("failed to enable ufw: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
47
internal/blocker/validators_test.go
Normal file
47
internal/blocker/validators_test.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package blocker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestValidateConfigPath(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{name: "empty", input: "", wantErr: true},
|
||||||
|
{name: "valid path", input: "/path/to/config", wantErr: false},
|
||||||
|
{name: "invalid path", input: "path/to/config", wantErr: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := validateConfigPath(tt.input)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("validateConfigPath(%q) error = %v, wantErr %v", tt.input, err, tt.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateIP(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{name: "empty", input: "", wantErr: true},
|
||||||
|
{name: "invalid IP", input: "1.1.1", wantErr: true},
|
||||||
|
{name: "valid IP", input: "1.1.1.1", wantErr: false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := validateIP(tt.input)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("validateIP(%q) error = %v, wantErr %v", tt.input, err, tt.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package config
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
"github.com/d3m0k1d/BanForge/internal/logger"
|
"github.com/d3m0k1d/BanForge/internal/logger"
|
||||||
@@ -20,3 +21,86 @@ func LoadRuleConfig() ([]Rule, error) {
|
|||||||
log.Info(fmt.Sprintf("loaded %d rules", len(cfg.Rules)))
|
log.Info(fmt.Sprintf("loaded %d rules", len(cfg.Rules)))
|
||||||
return cfg.Rules, nil
|
return cfg.Rules, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewRule(Name string, ServiceName string, Path string, Status string, Method string) error {
|
||||||
|
r, err := LoadRuleConfig()
|
||||||
|
if err != nil {
|
||||||
|
r = []Rule{}
|
||||||
|
}
|
||||||
|
if Name == "" {
|
||||||
|
fmt.Printf("Rule name can't be empty\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
r = append(r, Rule{Name: Name, ServiceName: ServiceName, Path: Path, Status: Status, Method: Method})
|
||||||
|
file, err := os.Create("/etc/banforge/rules.toml")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err = file.Close()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
cfg := Rules{Rules: r}
|
||||||
|
|
||||||
|
err = toml.NewEncoder(file).Encode(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func EditRule(Name string, ServiceName string, Path string, Status string, Method string) error {
|
||||||
|
if Name == "" {
|
||||||
|
return fmt.Errorf("Rule name can't be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := LoadRuleConfig()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("rules is empty, please use 'banforge add rule' or create rules.toml")
|
||||||
|
}
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for i, rule := range r {
|
||||||
|
if rule.Name == Name {
|
||||||
|
found = true
|
||||||
|
|
||||||
|
if ServiceName != "" {
|
||||||
|
r[i].ServiceName = ServiceName
|
||||||
|
}
|
||||||
|
if Path != "" {
|
||||||
|
r[i].Path = Path
|
||||||
|
}
|
||||||
|
if Status != "" {
|
||||||
|
r[i].Status = Status
|
||||||
|
}
|
||||||
|
if Method != "" {
|
||||||
|
r[i].Method = Method
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return fmt.Errorf("rule '%s' not found", Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Create("/etc/banforge/rules.toml")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err = file.Close()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
cfg := Rules{Rules: r}
|
||||||
|
if err := toml.NewEncoder(file).Encode(cfg); err != nil {
|
||||||
|
return fmt.Errorf("failed to encode config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,10 +13,14 @@ type DB struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewDB() (*DB, error) {
|
func NewDB() (*DB, error) {
|
||||||
db, err := sql.Open("sqlite3", "/var/lib/banforge/storage.db")
|
db, err := sql.Open("sqlite3", "/var/lib/banforge/storage.db?mode=rwc&_journal_mode=WAL&_busy_timeout=10000&cache=shared")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := db.Ping(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return &DB{
|
return &DB{
|
||||||
logger: logger.New(false),
|
logger: logger.New(false),
|
||||||
db: db,
|
db: db,
|
||||||
|
|||||||
177
internal/storage/db_test.go
Normal file
177
internal/storage/db_test.go
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"github.com/d3m0k1d/BanForge/internal/logger"
|
||||||
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func createTestDB(t *testing.T) *sql.DB {
|
||||||
|
tmpDir, err := os.MkdirTemp("", "banforge-test-*")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
filePath := filepath.Join(tmpDir, "test.db")
|
||||||
|
db, err := sql.Open("sqlite3", filePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
db.Close()
|
||||||
|
os.RemoveAll(tmpDir)
|
||||||
|
})
|
||||||
|
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTestDBStruct(t *testing.T) *DB {
|
||||||
|
tmpDir, err := os.MkdirTemp("", "banforge-test-*")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
filePath := filepath.Join(tmpDir, "test.db")
|
||||||
|
sqlDB, err := sql.Open("sqlite3", filePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
sqlDB.Close()
|
||||||
|
os.RemoveAll(tmpDir)
|
||||||
|
})
|
||||||
|
|
||||||
|
return &DB{
|
||||||
|
logger: logger.New(false),
|
||||||
|
db: sqlDB,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateTable(t *testing.T) {
|
||||||
|
d := createTestDBStruct(t)
|
||||||
|
|
||||||
|
err := d.CreateTable()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := d.db.Query("SELECT 1 FROM requests LIMIT 1")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("requests table should exist:", err)
|
||||||
|
}
|
||||||
|
rows.Close()
|
||||||
|
|
||||||
|
rows, err = d.db.Query("SELECT 1 FROM bans LIMIT 1")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("bans table should exist:", err)
|
||||||
|
}
|
||||||
|
rows.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarkAsViewed(t *testing.T) {
|
||||||
|
d := createTestDBStruct(t)
|
||||||
|
|
||||||
|
err := d.CreateTable()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = d.db.Exec(
|
||||||
|
"INSERT INTO requests (service, ip, path, method, status, created_at) VALUES (?, ?, ?, ?, ?, ?)",
|
||||||
|
"test",
|
||||||
|
"127.0.0.1",
|
||||||
|
"/test",
|
||||||
|
"GET",
|
||||||
|
"200",
|
||||||
|
time.Now().Format(time.RFC3339),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = d.MarkAsViewed(1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var isViewed bool
|
||||||
|
err = d.db.QueryRow("SELECT viewed FROM requests WHERE id = 1").Scan(&isViewed)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !isViewed {
|
||||||
|
t.Fatal("viewed should be true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSearchUnViewed(t *testing.T) {
|
||||||
|
d := createTestDBStruct(t)
|
||||||
|
|
||||||
|
err := d.CreateTable()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
_, err := d.db.Exec(
|
||||||
|
"INSERT INTO requests (service, ip, path, method, status, created_at) VALUES (?, ?, ?, ?, ?, ?)",
|
||||||
|
"test",
|
||||||
|
"127.0.0.1",
|
||||||
|
"/test",
|
||||||
|
"GET",
|
||||||
|
"200",
|
||||||
|
time.Now().Format(time.RFC3339),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := d.SearchUnViewed()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
count := 0
|
||||||
|
for rows.Next() {
|
||||||
|
var id int
|
||||||
|
var service, ip, path, status, method string
|
||||||
|
var viewed bool
|
||||||
|
var createdAt string
|
||||||
|
|
||||||
|
err := rows.Scan(&id, &service, &ip, &path, &status, &method, &viewed, &createdAt)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if viewed {
|
||||||
|
t.Fatal("should be unviewed")
|
||||||
|
}
|
||||||
|
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if count != 2 {
|
||||||
|
t.Fatalf("expected 2 unviewed requests, got %d", count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClose(t *testing.T) {
|
||||||
|
d := createTestDBStruct(t)
|
||||||
|
|
||||||
|
err := d.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
40
internal/storage/writer_test.go
Normal file
40
internal/storage/writer_test.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWrite(t *testing.T) {
|
||||||
|
var ip string
|
||||||
|
d := createTestDBStruct(t)
|
||||||
|
|
||||||
|
err := d.CreateTable()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resultCh := make(chan *LogEntry)
|
||||||
|
|
||||||
|
go Write(d, resultCh)
|
||||||
|
|
||||||
|
resultCh <- &LogEntry{
|
||||||
|
Service: "test",
|
||||||
|
IP: "127.0.0.1",
|
||||||
|
Path: "/test",
|
||||||
|
Method: "GET",
|
||||||
|
Status: "200",
|
||||||
|
}
|
||||||
|
|
||||||
|
close(resultCh)
|
||||||
|
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
err = d.db.QueryRow("SELECT ip FROM requests LIMIT 1").Scan(&ip)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if ip != "127.0.0.1" {
|
||||||
|
t.Fatal("ip should be 127.0.0.1")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user