Files
guard/internal/config/config_test.go
T
abrown38andClaude Opus 5.5 d06bf8d531 Guard: watch logs, ban misbehaving addresses in Housecarl
Files and the journal, patterns with one <IP>, bounties within a window,
bans in a Housecarl ban list with repeat offenders' bans growing; presets
for sshd, jarl, housecarl, steward, bind-xfr, postfix-sasl, dovecot,
nginx-auth and housecarl-drops; guard run/check/test/status/bans/ban/unban;
a Debian package with a sandboxed unit; an end-to-end test with Housecarl.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 16:29:37 +01:00

121 lines
3.8 KiB
Go

package config
import (
"bufio"
"os"
"path/filepath"
"strings"
"testing"
"time"
"guard/internal/match"
)
// TestPresets runs each preset over its sample lines in testdata/presets:
// what it must find, and what it must leave alone.
func TestPresets(t *testing.T) {
for name, p := range Presets() {
if p.Source != "" {
continue
}
m, err := match.Compile(p.Match, p.Ignore)
if err != nil {
t.Fatalf("preset %s: %v", name, err)
}
f, err := os.Open(filepath.Join("..", "..", "testdata", "presets", name+".txt"))
if err != nil {
t.Errorf("preset %s has no sample lines: %v", name, err)
continue
}
sc := bufio.NewScanner(f)
n := 0
for sc.Scan() {
want, line, ok := strings.Cut(sc.Text(), "\t")
if !ok || strings.HasPrefix(want, "#") {
continue
}
n++
a, matched := m.Match(line)
switch {
case want == "-" && matched:
t.Errorf("%s: matched %s in %q", name, a, line)
case want != "-" && !matched:
t.Errorf("%s: no match in %q", name, line)
case want != "-" && a.String() != want:
t.Errorf("%s: %s, want %s, in %q", name, a, want, line)
}
}
f.Close()
if n == 0 {
t.Errorf("preset %s: no sample lines", name)
}
}
}
func TestExampleParses(t *testing.T) {
c, err := Parse([]byte(Example()))
if err != nil {
t.Fatal(err)
}
w := c.Watch[0]
if w.Kind() != "journal" || w.Ban.List != "web" || w.Hits != 5 || w.Within.D() != 10*time.Minute || len(w.Match) == 0 {
t.Errorf("the ssh watch: %+v", w)
}
if c.Defaults.Repeat.Max.D() != 7*24*time.Hour {
t.Errorf("repeat %+v", c.Defaults.Repeat)
}
}
func TestErrors(t *testing.T) {
base := "housecarl: {url: https://fw:8070, list: web}\n"
cases := []struct{ yaml, want string }{
{"", "give url"},
{base + "watch: [{name: a, preset: nope}]", `no preset "nope"`},
{base + "watch: [{name: a, file: /x, match: ['no address']}]", "exactly one"},
{base + "watch: [{name: a, file: /x, journal: {unit: x}, match: ['<IP>']}]", "one source"},
{base + "watch: [{name: a, file: /x}]", "no match patterns"},
{base + "watch: [{name: a, source: housecarl-drops, match: ['<IP>']}]", "no patterns"},
{base + "watch: [{name: a, preset: sshd, ban: {protocol: tcp}}]", "without ban.port"},
{base + "watch: [{name: a, preset: sshd, within: 10 minutes}]", "isn't a length of time"},
{base + "watch: [{name: a, preset: sshd}, {name: A, preset: sshd}]", "used twice"},
{base + "never_ban: ['not an address!']\nwatch: [{name: a, preset: sshd}]", "never_ban"},
{base + "bogus: 1\nwatch: [{name: a, preset: sshd}]", "bogus"},
{"housecarl: {url: https://fw:8070}\nwatch: [{name: a, preset: sshd}]", "which ban list"},
}
for _, c := range cases {
_, err := Parse([]byte(c.yaml))
if err == nil || !strings.Contains(err.Error(), c.want) {
t.Errorf("%q: %v, want %q", c.yaml, err, c.want)
}
}
}
func TestPresetSourceAndOwnPatterns(t *testing.T) {
c, err := Parse([]byte(`housecarl: {socket: /run/housecarl/housecarl.sock, list: router}
watch:
- {name: ssh, preset: sshd, file: /var/log/auth.log, match: ['custom <IP> line']}
- {name: scans, preset: housecarl-drops}
`))
if err != nil {
t.Fatal(err)
}
ssh := c.Watch[0]
if ssh.Kind() != "file" || ssh.File != "/var/log/auth.log" || ssh.Match[len(ssh.Match)-1] != "custom <IP> line" || len(ssh.Match) < 2 {
t.Errorf("own file and pattern beside the preset's: %+v", ssh)
}
if s := c.Watch[1]; s.Kind() != SourceHousecarlDrops || s.Protocol != "tcp" {
t.Errorf("housecarl-drops: %+v", s)
}
}
func TestDuration(t *testing.T) {
for in, want := range map[string]time.Duration{"30s": 30 * time.Second, "1d12h": 36 * time.Hour, "1w": 7 * 24 * time.Hour} {
if d, err := ParseDuration(in); err != nil || d != want {
t.Errorf("%s: %v %v", in, d, err)
}
if got := FormatDuration(want); got != in {
t.Errorf("format %v: %s, want %s", want, got, in)
}
}
}