118 lines
4.2 KiB
Go
118 lines
4.2 KiB
Go
|
|
package services
|
||
|
|
|
||
|
|
import (
|
||
|
|
"log/slog"
|
||
|
|
"path/filepath"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestBanServiceMatchesIPAndCIDR(t *testing.T) {
|
||
|
|
bans := newTestBanService(t)
|
||
|
|
now := time.Date(2026, 5, 31, 12, 0, 0, 0, time.UTC)
|
||
|
|
ipBan, err := bans.createBan("203.0.113.5", "single IP", BanSourceManual, "test", now.Add(time.Hour), now)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("createBan IP returned error: %v", err)
|
||
|
|
}
|
||
|
|
cidrBan, err := bans.createBan("198.51.100.0/24", "CIDR", BanSourceManual, "test", now.Add(time.Hour), now)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("createBan CIDR returned error: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if matched, ok, err := bans.Match("203.0.113.5", now); err != nil || !ok || matched.Ban.ID != ipBan.ID {
|
||
|
|
t.Fatalf("Match IP = %+v, %v, %v", matched, ok, err)
|
||
|
|
}
|
||
|
|
if matched, ok, err := bans.Match("198.51.100.42", now); err != nil || !ok || matched.Ban.ID != cidrBan.ID {
|
||
|
|
t.Fatalf("Match CIDR = %+v, %v, %v", matched, ok, err)
|
||
|
|
}
|
||
|
|
if _, ok, err := bans.Match("192.0.2.1", now); err != nil || ok {
|
||
|
|
t.Fatalf("Match unrelated = %v, %v", ok, err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestBanServiceIgnoresExpiredAndUnbanned(t *testing.T) {
|
||
|
|
bans := newTestBanService(t)
|
||
|
|
now := time.Date(2026, 5, 31, 12, 0, 0, 0, time.UTC)
|
||
|
|
expired, err := bans.createBan("203.0.113.6", "expired", BanSourceManual, "test", now.Add(time.Hour), now)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("createBan expired returned error: %v", err)
|
||
|
|
}
|
||
|
|
if _, ok, err := bans.Match("203.0.113.6", now.Add(2*time.Hour)); err != nil || ok {
|
||
|
|
t.Fatalf("expired Match = %v, %v", ok, err)
|
||
|
|
}
|
||
|
|
active, err := bans.createBan("203.0.113.7", "active", BanSourceManual, "test", now.Add(time.Hour), now)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("createBan active returned error: %v", err)
|
||
|
|
}
|
||
|
|
if err := bans.Unban(active.ID, now.Add(time.Minute)); err != nil {
|
||
|
|
t.Fatalf("Unban returned error: %v", err)
|
||
|
|
}
|
||
|
|
if _, ok, err := bans.Match("203.0.113.7", now.Add(2*time.Minute)); err != nil || ok {
|
||
|
|
t.Fatalf("unbanned Match = %v, %v", ok, err)
|
||
|
|
}
|
||
|
|
if expired.Status(now.Add(2*time.Hour)) != "expired" {
|
||
|
|
t.Fatalf("expired status = %q", expired.Status(now.Add(2*time.Hour)))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestBanServiceAutoBanThresholdsAndDisabled(t *testing.T) {
|
||
|
|
bans := newTestBanService(t)
|
||
|
|
now := time.Date(2026, 5, 31, 12, 0, 0, 0, time.UTC)
|
||
|
|
if result, err := bans.RecordAbuse("203.0.113.8", AbuseKindMaliciousPath, "/.env", 3, now); err != nil || result.Enabled {
|
||
|
|
t.Fatalf("disabled RecordAbuse = %+v, %v", result, err)
|
||
|
|
}
|
||
|
|
settings, err := bans.Settings()
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Settings returned error: %v", err)
|
||
|
|
}
|
||
|
|
settings.AutoBanEnabled = true
|
||
|
|
if err := bans.UpdateSettings(settings); err != nil {
|
||
|
|
t.Fatalf("UpdateSettings returned error: %v", err)
|
||
|
|
}
|
||
|
|
for i := 0; i < 2; i++ {
|
||
|
|
result, err := bans.RecordAbuse("203.0.113.8", AbuseKindMaliciousPath, "/.env", 3, now.Add(time.Duration(i)*time.Minute))
|
||
|
|
if err != nil || result.Triggered {
|
||
|
|
t.Fatalf("RecordAbuse before threshold = %+v, %v", result, err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
result, err := bans.RecordAbuse("203.0.113.8", AbuseKindMaliciousPath, "/.env", 3, now.Add(3*time.Minute))
|
||
|
|
if err != nil || !result.Triggered || result.Ban.ID == "" {
|
||
|
|
t.Fatalf("RecordAbuse threshold = %+v, %v", result, err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestBanServiceMaliciousPathRules(t *testing.T) {
|
||
|
|
bans := newTestBanService(t)
|
||
|
|
if pattern, err := bans.MaliciousPattern("/foo/.ENV"); err != nil || pattern == "" {
|
||
|
|
t.Fatalf("MaliciousPattern .env = %q, %v", pattern, err)
|
||
|
|
}
|
||
|
|
if pattern, err := bans.MaliciousPattern("/static/.env"); err != nil || pattern != "" {
|
||
|
|
t.Fatalf("MaliciousPattern static = %q, %v", pattern, err)
|
||
|
|
}
|
||
|
|
if err := bans.SaveRules([]string{"/custom-probe"}, time.Now().UTC()); err != nil {
|
||
|
|
t.Fatalf("SaveRules returned error: %v", err)
|
||
|
|
}
|
||
|
|
if pattern, err := bans.MaliciousPattern("/x/CUSTOM-probe"); err != nil || pattern != "/custom-probe" {
|
||
|
|
t.Fatalf("MaliciousPattern custom = %q, %v", pattern, err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func newTestBanService(t *testing.T) *BanService {
|
||
|
|
t.Helper()
|
||
|
|
root := t.TempDir()
|
||
|
|
upload, err := NewUploadService(1024*1024, filepath.Join(root, "data"), "http://example.test", slog.Default())
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("NewUploadService returned error: %v", err)
|
||
|
|
}
|
||
|
|
t.Cleanup(func() {
|
||
|
|
if err := upload.Close(); err != nil {
|
||
|
|
t.Fatalf("Close returned error: %v", err)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
bans, err := NewBanService(upload.DB())
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("NewBanService returned error: %v", err)
|
||
|
|
}
|
||
|
|
return bans
|
||
|
|
}
|