From 24fe951e49d4e994e4e10acfce4a59469bd06339 Mon Sep 17 00:00:00 2001 From: d3m0k1d Date: Tue, 13 Jan 2026 21:53:33 +0300 Subject: [PATCH] fix: judge creator, daemon logic feat: first version for alpha test daemon on server fix: add second template for fix bug with slice Fix: add chek if path exists Fix: template one more time feat: Add file db on init command feat: add create dit feat: Add to init command create table to db feat: Add new logs for debug on server feat: Add CD, first release version chore:fix cd fix: change artifact ver from v4->v2 fix: ci one more time fix: ci --- .gitea/workflows/CD.yml | 82 ++++++++++++++++ cmd/banforge/main.go | 166 ++++++++++++++++++++++++++++----- go.mod | 2 +- internal/config/sysconf.go | 8 ++ internal/config/template.go | 10 +- internal/judge/judge.go | 5 +- internal/parser/NginxParser.go | 1 + internal/parser/parser.go | 1 + 8 files changed, 248 insertions(+), 27 deletions(-) create mode 100644 .gitea/workflows/CD.yml diff --git a/.gitea/workflows/CD.yml b/.gitea/workflows/CD.yml new file mode 100644 index 0000000..cafde55 --- /dev/null +++ b/.gitea/workflows/CD.yml @@ -0,0 +1,82 @@ +name: CD - BanForge Release + +on: + push: + tags: + - 'v*' + workflow_dispatch: + +permissions: + contents: write + +jobs: + release: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - name: Create Release + env: + TOKEN: ${{ secrets.TOKEN }} + run: | + TAG="${{ gitea.ref_name }}" + REPO="${{ gitea.repository }}" + SERVER="${{ gitea.server_url }}" + + curl -X POST \ + -H "Authorization: token $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "tag_name": "'$TAG'", + "name": "Release '$TAG'", + "body": "# BanForge '$TAG'\n\nIntrusion Prevention System", + "draft": false, + "prerelease": false + }' \ + "$SERVER/api/v1/repos/$REPO/releases" + + build: + needs: release + strategy: + matrix: + include: + - goos: linux + arch: amd64 + - goos: linux + arch: arm64 + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-go@v6 + with: + go-version: '1.25' + cache: false + + - run: go mod tidy + + - run: go test ./... + + - name: Build ${{ matrix.goos }}-${{ matrix.arch }} + env: + GOOS: ${{ matrix.goos }} + GOARCH: ${{ matrix.arch }} + run: go build -o banforge-${{ matrix.goos }}-${{ matrix.arch }} ./cmd/banforge + + - name: Upload to release + env: + TOKEN: ${{ secrets.TOKEN }} + run: | + TAG="${{ gitea.ref_name }}" + REPO="${{ gitea.repository }}" + SERVER="${{ gitea.server_url }}" + + curl -X POST \ + -H "Authorization: token $TOKEN" \ + -H "Content-Type: application/octet-stream" \ + --data-binary "@banforge-${{ matrix.goos }}-${{ matrix.arch }}" \ + "$SERVER/api/v1/repos/$REPO/releases/tags/$TAG/assets?name=banforge-${{ matrix.goos }}-${{ matrix.arch }}" + diff --git a/cmd/banforge/main.go b/cmd/banforge/main.go index 4b56402..3894a30 100644 --- a/cmd/banforge/main.go +++ b/cmd/banforge/main.go @@ -3,12 +3,14 @@ package main import ( "fmt" "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/judge" "github.com/d3m0k1d/BanForge/internal/logger" "github.com/d3m0k1d/BanForge/internal/parser" - _ "github.com/d3m0k1d/BanForge/internal/storage" + "github.com/d3m0k1d/BanForge/internal/storage" "github.com/spf13/cobra" ) @@ -25,26 +27,80 @@ var initCmd = &cobra.Command{ Short: "Initialize BanForge", Run: func(cmd *cobra.Command, args []string) { fmt.Println("Initializing BanForge...") - err := os.Mkdir("/var/log/banforge", 0750) - if err != nil { - fmt.Println(err) - os.Exit(1) - } - err = os.Mkdir("/etc/banforge", 0750) - if err != nil { - fmt.Println(err) - os.Exit(1) - } - err = config.CreateConf() + + 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!") }, } @@ -54,23 +110,86 @@ var daemonCmd = &cobra.Command{ 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) - //} + 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) } - for service := range cfg.Service { - if cfg.Service[service].Enabled && cfg.Service[service].Name != "nginx" { - pars, err := parser.NewScanner(cfg.Service[service].LogPath) - if err != nil { - log.Error("Failed to create parser", "error", err) + 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) } - go pars.Start() } + }() + + 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 {} }, } @@ -79,6 +198,7 @@ func Init() { } func Execute() { + rootCmd.AddCommand(daemonCmd) rootCmd.AddCommand(initCmd) if err := rootCmd.Execute(); err != nil { fmt.Println(err) diff --git a/go.mod b/go.mod index 181cfb2..44b3b62 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,11 @@ go 1.25.5 require ( github.com/BurntSushi/toml v1.6.0 + github.com/mattn/go-sqlite3 v1.14.33 github.com/spf13/cobra v1.10.2 ) require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/mattn/go-sqlite3 v1.14.33 // indirect github.com/spf13/pflag v1.0.10 // indirect ) diff --git a/internal/config/sysconf.go b/internal/config/sysconf.go index d43668f..d65ceb5 100644 --- a/internal/config/sysconf.go +++ b/internal/config/sysconf.go @@ -50,6 +50,14 @@ func CreateConf() error { if err != nil { return fmt.Errorf("failed to create rules file: %w", err) } + file, err = os.Create("/var/lib/banforge/storage.db") + if err != nil { + return fmt.Errorf("failed to create database file: %w", err) + } + err = os.Chmod("/var/lib/banforge/storage.db", 0600) + if err != nil { + return fmt.Errorf("failed to set permissions: %w", err) + } defer func() { err = file.Close() if err != nil { diff --git a/internal/config/template.go b/internal/config/template.go index 1e2ac46..dc0fd0d 100644 --- a/internal/config/template.go +++ b/internal/config/template.go @@ -9,8 +9,16 @@ name = "" config = "/etc/nftables.conf" ban_time = 1200 -[service] +[[service]] name = "nginx" log_path = "/var/log/nginx/access.log" enabled = true + +[[service]] +name = "nginx" +log_path = "/var/log/nginx/access.log" +enabled = false + ` + +// TODO: fix types for use 1 or any services" diff --git a/internal/judge/judge.go b/internal/judge/judge.go index 7a985b1..666436d 100644 --- a/internal/judge/judge.go +++ b/internal/judge/judge.go @@ -1,4 +1,4 @@ -package Judge +package judge import ( "fmt" @@ -16,11 +16,12 @@ type Judge struct { rulesByService map[string][]config.Rule } -func New(db *storage.DB) *Judge { +func New(db *storage.DB, b blocker.BlockerEngine) *Judge { return &Judge{ db: db, logger: logger.New(false), rulesByService: make(map[string][]config.Rule), + Blocker: b, } } diff --git a/internal/parser/NginxParser.go b/internal/parser/NginxParser.go index 26e6ecf..e652d3c 100644 --- a/internal/parser/NginxParser.go +++ b/internal/parser/NginxParser.go @@ -42,6 +42,7 @@ func (p *NginxParser) Parse(eventCh <-chan Event, resultCh chan<- *storage.LogEn Method: method, IsViewed: false, } + p.logger.Info("Parsed nginx log entry", "ip", matches[1], "path", path, "status", status, "method", method) } }() } diff --git a/internal/parser/parser.go b/internal/parser/parser.go index 974515a..7f37c5b 100644 --- a/internal/parser/parser.go +++ b/internal/parser/parser.go @@ -52,6 +52,7 @@ func (s *Scanner) Start() { s.ch <- Event{ Data: s.scanner.Text(), } + s.logger.Info("Scanner event", "data", s.scanner.Text()) } else { if err := s.scanner.Err(); err != nil { s.logger.Error("Scanner error")