Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
914168f80f | ||
|
|
3a61371e58 | ||
|
|
d7d49ec0ed | ||
|
|
59e4393e82 | ||
|
|
bd73ba24e8 | ||
|
|
28d1410d62 | ||
|
|
680973df3d |
@@ -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
1
.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
bin/
|
bin/
|
||||||
|
dist/
|
||||||
|
|||||||
72
.goreleaser.yml
Normal file
72
.goreleaser.yml
Normal 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"]
|
||||||
|
|
||||||
3
Makefile
3
Makefile
@@ -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
21
build/banforge
Normal 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"
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user