7 Commits

Author SHA1 Message Date
d3m0k1d
914168f80f chore: add skip tlas false
All checks were successful
CD - BanForge Release / release (push) Successful in 3m27s
CI.yml / build (push) Successful in 2m6s
2026-01-16 01:31:53 +03:00
d3m0k1d
3a61371e58 chore: Add gitea urls 2026-01-16 01:31:26 +03:00
d3m0k1d
d7d49ec0ed chore: delete gpg on release
Some checks failed
CI.yml / build (push) Successful in 2m37s
CD - BanForge Release / release (push) Has been cancelled
2026-01-16 01:25:19 +03:00
d3m0k1d
59e4393e82 fix: fix release
Some checks failed
CI.yml / build (push) Successful in 2m33s
CD - BanForge Release / release (push) Failing after 2m1s
2026-01-16 01:21:07 +03:00
d3m0k1d
bd73ba24e8 chore: fix cd
Some checks failed
CI.yml / build (push) Successful in 2m18s
CD - BanForge Release / release (push) Failing after 1m39s
2026-01-16 01:10:26 +03:00
d3m0k1d
28d1410d62 chore: upd gitignore add goreleaser and openrc script
Some checks failed
CI.yml / build (push) Successful in 2m40s
CD - BanForge Release / release (push) Failing after 3m53s
2026-01-16 00:53:20 +03:00
d3m0k1d
680973df3d feat: daemon add ctx and done signal, judge fix problem with double ban ip, db add new methods
All checks were successful
CI.yml / build (push) Successful in 1m58s
2026-01-15 22:32:03 +03:00
9 changed files with 173 additions and 54 deletions

View File

@@ -13,55 +13,33 @@ jobs:
release: release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - name: Install syft
- name: Create Release run: curl -sSfL https://get.anchore.io/syft | sudo sh -s -- -b /usr/local/bin
env: - name: Checkout
TOKEN: ${{ secrets.TOKEN }} uses: actions/checkout@v6
run: | - name: Go setup
TAG="${{ gitea.ref_name }}" uses: actions/setup-go@v6
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: with:
go-version: '1.25' go-version: '1.25'
cache: false cache: false
- run: go mod tidy - name: Install deps
- run: go test ./... run: go mod tidy
- name: Build ${{ matrix.goos }}-${{ matrix.arch }} - name: Golangci-lint
uses: golangci/golangci-lint-action@v9.2.0
with:
args: --timeout=5m
skip-cache: true
- name: Run tests
run: go test ./...
- name: GoReleaser
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser
version: latest
args: release --clean
env: env:
GOOS: ${{ matrix.goos }} GPG_FINGERPRINT: ${{ secrets.GPG_FINGERPRINT }}
GOARCH: ${{ matrix.arch }} GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
run: go build -o banforge-${{ matrix.goos }}-${{ matrix.arch }} ./cmd/banforge GITEA_TOKEN: ${{ secrets.TOKEN }}
- name: Upload ${{ matrix.goos }}-${{ matrix.arch }}
env:
TOKEN: ${{ secrets.TOKEN }}
run: |
TAG="${{ gitea.ref_name }}"
FILE="banforge-${{ matrix.goos }}-${{ matrix.arch }}"
curl --user d3m0k1d:$TOKEN \
--upload-file $FILE \
https://gitea.d3m0k1d.ru/api/packages/d3m0k1d/generic/banforge/$TAG/$FILE

1
.gitignore vendored
View File

@@ -1 +1,2 @@
bin/ bin/
dist/

72
.goreleaser.yml Normal file
View File

@@ -0,0 +1,72 @@
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
version: 2
project_name: BanForge
gitea_urls:
api: https://gitea.d3m0k1d.ru/api/v1
download: https://gitea.d3m0k1d.ru/d3m0k1d/BanForge/releases/download
skip_tls_verify: false
builds:
- id: banforge
main: ./cmd/banforge/main.go
binary: banforge-{{ .Version }}-{{ .Os }}-{{ .Arch }}
ignore:
- goos: windows
- goos: darwin
- goos: freebsd
goos:
- linux
goarch:
- amd64
- arm64
env:
- CGO_ENABLED=0
ldflags:
- "-s -w"
archives:
- format: tar.gz
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
nfpms:
- id: banforge
package_name: banforge
file_name_template: "{{ .PackageName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
homepage: https://gitea.d3m0k1d.ru/d3m0k1d/BanForge
description: BanForge IPS log-based system
maintainer: d3m0k1d <contact@d3m0k1d.ru>
license: GPLv3.0
formats:
- apk
- deb
- rpm
- archlinux
bindir: /usr/bin
release:
gitea:
owner: d3m0k1d
name: BanForge
mode: keep-existing
changelog:
sort: asc
filters:
exclude:
- "^docs:"
- "^test:"
checksum:
name_template: "{{ .ProjectName }}_{{ .Version }}_checksums.txt"
algorithm: sha256
sboms:
- artifacts: archive
documents:
- "{{ .ArtifactName }}.spdx.json"
cmd: syft
args: ["$artifact", "--output", "spdx-json=$document"]

View File

@@ -28,3 +28,6 @@ test:
test-cover: test-cover:
go test -cover ./... go test -cover ./...
lint:
golangci-lint run --fix

21
build/banforge Normal file
View File

@@ -0,0 +1,21 @@
#!/sbin/openrc-run
description="BanForge - IPS log based system"
command="/usr/bin/banforge"
command_args="daemon"
pidfile="/run/${RC_SVCNAME}.pid"
command_background="yes"
depend() {
need net
after network
}
start_post() {
einfo "BanForge is now running"
}
stop_post() {
einfo "BanForge is now stopped"
}

View File

@@ -13,5 +13,9 @@ Restart=always
StandardOutput=journal StandardOutput=journal
StandardError=journal StandardError=journal
SyslogIdentifier=banforge SyslogIdentifier=banforge
TimeoutStopSec=90
KillSignal=SIGTERM
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View File

@@ -1,7 +1,10 @@
package command package command
import ( import (
"context"
"os" "os"
"os/signal"
"syscall"
"time" "time"
"github.com/d3m0k1d/BanForge/internal/blocker" "github.com/d3m0k1d/BanForge/internal/blocker"
@@ -17,6 +20,8 @@ var DaemonCmd = &cobra.Command{
Use: "daemon", Use: "daemon",
Short: "Run BanForge daemon process", Short: "Run BanForge daemon process",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT)
defer stop()
log := logger.New(false) log := logger.New(false)
log.Info("Starting BanForge daemon") log.Info("Starting BanForge daemon")
db, err := storage.NewDB() db, err := storage.NewDB()
@@ -77,6 +82,7 @@ var DaemonCmd = &cobra.Command{
} }
go pars.Start() go pars.Start()
defer pars.Stop()
go func(p *parser.Scanner, serviceName string) { go func(p *parser.Scanner, serviceName string) {
log.Info("Starting nginx parser", "service", serviceName) log.Info("Starting nginx parser", "service", serviceName)
ng := parser.NewNginxParser() ng := parser.NewNginxParser()
@@ -85,7 +91,7 @@ var DaemonCmd = &cobra.Command{
go storage.Write(db, resultCh) go storage.Write(db, resultCh)
}(pars, svc.Name) }(pars, svc.Name)
} }
<-ctx.Done()
select {} log.Info("Shutdown signal received")
}, },
} }

View File

@@ -48,7 +48,6 @@ func (j *Judge) ProcessUnviewed() error {
j.logger.Error(fmt.Sprintf("Failed to close database connection: %v", err)) j.logger.Error(fmt.Sprintf("Failed to close database connection: %v", err))
} }
}() }()
for rows.Next() { for rows.Next() {
var entry storage.LogEntry var entry storage.LogEntry
err = rows.Scan(&entry.ID, &entry.Service, &entry.IP, &entry.Path, &entry.Status, &entry.Method, &entry.IsViewed, &entry.CreatedAt) err = rows.Scan(&entry.ID, &entry.Service, &entry.IP, &entry.Path, &entry.Status, &entry.Method, &entry.IsViewed, &entry.CreatedAt)
@@ -65,11 +64,22 @@ func (j *Judge) ProcessUnviewed() error {
(rule.Path == "" || entry.Path == rule.Path) { (rule.Path == "" || entry.Path == rule.Path) {
j.logger.Info(fmt.Sprintf("Rule matched for IP: %s, Service: %s", entry.IP, entry.Service)) j.logger.Info(fmt.Sprintf("Rule matched for IP: %s, Service: %s", entry.IP, entry.Service))
ban_status, err := j.db.IsBanned(entry.IP)
if err != nil {
j.logger.Error(fmt.Sprintf("Failed to check ban status: %v", err))
return err
}
if !ban_status {
err = j.Blocker.Ban(entry.IP) err = j.Blocker.Ban(entry.IP)
if err != nil { if err != nil {
j.logger.Error(fmt.Sprintf("Failed to ban IP: %v", err)) j.logger.Error(fmt.Sprintf("Failed to ban IP: %v", err))
} }
j.logger.Info(fmt.Sprintf("IP banned: %s", entry.IP)) j.logger.Info(fmt.Sprintf("IP banned: %s", entry.IP))
err = j.db.AddBan(entry.IP)
if err != nil {
j.logger.Error(fmt.Sprintf("Failed to add ban: %v", err))
}
}
break break
} }
} }

View File

@@ -3,6 +3,9 @@ package storage
import ( import (
"database/sql" "database/sql"
"fmt"
"time"
"github.com/d3m0k1d/BanForge/internal/logger" "github.com/d3m0k1d/BanForge/internal/logger"
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
) )
@@ -62,3 +65,24 @@ func (d *DB) MarkAsViewed(id int) error {
} }
return nil return nil
} }
func (d *DB) IsBanned(ip string) (bool, error) {
var bannedIP string
err := d.db.QueryRow("SELECT ip FROM bans WHERE ip = ? ", ip).Scan(&bannedIP)
if err == sql.ErrNoRows {
return false, nil
}
if err != nil {
return false, fmt.Errorf("failed to check ban status: %w", err)
}
return true, nil
}
func (d *DB) AddBan(ip string) error {
_, err := d.db.Exec("INSERT INTO bans (ip, reason, banned_at) VALUES (?, ?, ?)", ip, "1", time.Now().Format(time.RFC3339))
if err != nil {
d.logger.Error("Failed to add ban", "error", err)
return err
}
return nil
}