Build on git.eeqj.de/sneak/baseimage:2026-11-05 with the docker CLI copied from docker:cli. A runit service sleeps INTERVAL seconds (default 1200), then runs the reporter with --since INTERVAL --submit; DOCKER_CONTAINER_NAME and IPABUSEDB_API_KEY come from the container environment. Add .dockerignore, make image, and README section; fix the binary name in .gitignore.
120 lines
3.9 KiB
Go
120 lines
3.9 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"net/netip"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestParseExampleLines(t *testing.T) {
|
|
data, err := os.ReadFile("loglines_example.txt")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
p := newParser(time.Time{})
|
|
if err := p.ingest(bytes.NewReader(data)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
want := map[string]int{
|
|
"41.93.28.11": 2,
|
|
"45.191.4.252": 2,
|
|
"164.77.161.180": 2,
|
|
"212.128.3.152": 2,
|
|
}
|
|
if len(p.events) != len(want) {
|
|
t.Fatalf("got %d events, want %d", len(p.events), len(want))
|
|
}
|
|
for ipStr, count := range want {
|
|
ev, ok := p.events[netip.MustParseAddr(ipStr)]
|
|
if !ok {
|
|
t.Errorf("missing event for %s", ipStr)
|
|
continue
|
|
}
|
|
if ev.count != count {
|
|
t.Errorf("%s: count %d, want %d", ipStr, ev.count, count)
|
|
}
|
|
}
|
|
|
|
var out bytes.Buffer
|
|
if err := writeCSV(&out, p.sorted()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
lines := strings.Split(strings.TrimSpace(out.String()), "\n")
|
|
if lines[0] != "IP,Categories,Comment,ReportDate" {
|
|
t.Errorf("bad header: %q", lines[0])
|
|
}
|
|
if len(lines) != 5 {
|
|
t.Fatalf("got %d csv lines, want 5:\n%s", len(lines), out.String())
|
|
}
|
|
if !strings.Contains(lines[1], "41.93.28.11,\"18,22\",") || !strings.HasSuffix(lines[1], "2026-10-06T12:04:24Z") {
|
|
t.Errorf("unexpected first row: %q", lines[1])
|
|
}
|
|
// The descriptive "Invalid user ... from" line should win over the
|
|
// later "Connection closed by invalid user" line for the comment.
|
|
if !strings.Contains(lines[2], ",Invalid user production from 45.191.4.252 port 38924 (2 events),") {
|
|
t.Errorf("unexpected comment in row: %q", lines[2])
|
|
}
|
|
// AllowUsers rejections are reported with their own message.
|
|
if !strings.Contains(lines[1], "User root from 41.93.28.11 not allowed because not listed in AllowUsers (2 events)") {
|
|
t.Errorf("unexpected comment in row: %q", lines[1])
|
|
}
|
|
}
|
|
|
|
func TestExclusions(t *testing.T) {
|
|
p := newParser(time.Time{})
|
|
for _, line := range []string{
|
|
"2026-10-06T12:04:24.687263829Z Invalid user git from 1.2.3.4 port 38924",
|
|
"2026-10-06T12:04:24.687263829Z User git from 1.2.3.5 not allowed because not listed in AllowUsers",
|
|
"2026-10-06T12:04:24.687263829Z Invalid user bob from 172.19.0.1 port 38924",
|
|
"2026-10-06T12:04:24.687263829Z Invalid user bob from 10.0.0.9 port 38924",
|
|
"2026-10-06T12:04:24.687263829Z Invalid user bob from 127.0.0.1 port 38924",
|
|
"2026-10-06T12:04:24.687263829Z Invalid user bob from fd00::1 port 38924",
|
|
"2026-10-06T12:04:24.687263829Z Invalid user bob from not-an-ip port 38924",
|
|
"2026-10-06T12:04:24.687263829Z Received disconnect from 8.8.8.8 port 53534:11: disconnected by user",
|
|
"garbage line without timestamp",
|
|
} {
|
|
p.handleLine(line)
|
|
}
|
|
if len(p.events) != 0 {
|
|
t.Errorf("expected no events, got %v", p.events)
|
|
}
|
|
if p.skipped != 1 {
|
|
t.Errorf("skipped = %d, want 1", p.skipped)
|
|
}
|
|
}
|
|
|
|
func TestSinceCutoff(t *testing.T) {
|
|
cutoff := time.Date(2026, 10, 6, 12, 4, 29, 0, time.UTC)
|
|
p := newParser(cutoff)
|
|
p.handleLine("2026-10-06T12:04:24.687263829Z Invalid user production from 45.191.4.252 port 38924")
|
|
p.handleLine("2026-10-06T12:04:30.222573149Z Invalid user production from 212.128.3.152 port 60626")
|
|
if len(p.events) != 1 {
|
|
t.Fatalf("got %d events, want 1", len(p.events))
|
|
}
|
|
if _, ok := p.events[netip.MustParseAddr("212.128.3.152")]; !ok {
|
|
t.Error("expected only the post-cutoff event to remain")
|
|
}
|
|
}
|
|
|
|
func TestIPv6AndLatestWins(t *testing.T) {
|
|
p := newParser(time.Time{})
|
|
p.handleLine("2026-10-06T12:04:24Z Invalid user a from 2001:db8::1 port 1")
|
|
p.handleLine("2026-10-06T12:04:26Z Invalid user b from 2001:db8::1 port 2")
|
|
p.handleLine("2026-10-06T12:04:25Z Invalid user c from 2001:db8::1 port 3")
|
|
ev := p.events[netip.MustParseAddr("2001:db8::1")]
|
|
if ev == nil || ev.count != 3 || ev.user != "b" {
|
|
t.Fatalf("unexpected event: %+v", ev)
|
|
}
|
|
var out bytes.Buffer
|
|
if err := writeCSV(&out, p.sorted()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(out.String(), "(3 events)") {
|
|
t.Errorf("expected event count in comment:\n%s", out.String())
|
|
}
|
|
}
|