From f614eeb445a1c5756e6aba8a41d38e65aab94a48 Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 16 Oct 2026 22:04:36 +0200 Subject: [PATCH] Test the refusal of a jail whose pattern names the host twice Main refuses such a jail (fail2ban would refuse its whole configuration and keep no bans), but no test held it: the only one sat untracked in an old worktree. Issue 959. --- internal/catalogue/jail_problems_test.go | 49 ++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 internal/catalogue/jail_problems_test.go diff --git a/internal/catalogue/jail_problems_test.go b/internal/catalogue/jail_problems_test.go new file mode 100644 index 00000000..800d36a1 --- /dev/null +++ b/internal/catalogue/jail_problems_test.go @@ -0,0 +1,49 @@ +package catalogue + +import ( + "strings" + "testing" +) + +// A jail whose pattern names twice is refused by name: fail2ban would read it as a duplicate +// capture group and refuse its whole configuration, leaving the machine with no bans at all — which +// is what happened on the control node the day this was built (novox/hq ADR 0179). +func TestAJailsPatternNamesTheHostOncePerShape(t *testing.T) { + twice := Manifest{Module: "route-proxy", Jails: []Jail{{ + Name: "route-proxy", + Failregex: `^.*(?:refused for :\d+|asked from :\d+)$`, + Jail: "backend = systemd", + }}} + problems := twice.jailProblems() + if len(problems) != 1 || !strings.Contains(problems[0], "names 2 times") { + t.Fatalf("a pattern naming twice was not refused by name: %v", problems) + } + if !strings.Contains(problems[0], "no bans at all") { + t.Errorf("the refusal does not say what it costs: %q", problems[0]) + } + + // The same two shapes, one pattern each, is how it is written — and is accepted. + split := Manifest{Module: "route-proxy", Jails: []Jail{{ + Name: "route-proxy", + Failregex: "^.*refused for :\\d+$\n ^.*asked from :\\d+$", + Jail: "backend = systemd", + }}} + if problems := split.jailProblems(); len(problems) != 0 { + t.Fatalf("one per pattern was refused: %v", problems) + } +} + +// A jail with no name, no pattern, or a name another jail of the same module already took is refused. +func TestAJailIsNamedOnceAndSaysWhatAFailureLooksLike(t *testing.T) { + m := Manifest{Module: "mailu", Jails: []Jail{ + {Name: "", Failregex: "^.*rip=$"}, + {Name: "mailu-front", Failregex: " "}, + {Name: "mailu-front", Failregex: "^.*rip=$"}, + }} + problems := strings.Join(m.jailProblems(), "\n") + for _, want := range []string{"a jail with no name", "says nothing a failed attempt looks like", "two jails called"} { + if !strings.Contains(problems, want) { + t.Errorf("missing %q in:\n%s", want, problems) + } + } +}