fail2ban restarts when the composed jail file changes, and each filter is a file of its own, so a module that changed only its failregex left the running jail on the old pattern. The jail file now names each filter's digest.
73 lines
3.2 KiB
Go
73 lines
3.2 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// A node's fail2ban jails are composed from the modules it runs (novox/hq to-be 31): the holder
|
|
// (jailing) gathers every module's declared jail into one jail file and a filter file per jail.
|
|
func TestJailsAreComposedFromTheNodesModules(t *testing.T) {
|
|
modules := []Manifest{
|
|
{Module: "fail2ban", Jailing: &Jailing{Into: "/etc/fail2ban/jail.d/mesh-composed.conf", FilterInto: "/etc/fail2ban/filter.d"}},
|
|
{Module: "postgres", Jails: []Jail{{Name: "postgres-auth", Failregex: "auth failed from <HOST>", Jail: "port = 5432\nmaxretry = 5"}}},
|
|
}
|
|
files := jailsInto(modules, modules[0].Jailing)
|
|
|
|
by := map[string]map[string]any{}
|
|
for _, f := range files {
|
|
by[f["id"].(string)] = f
|
|
}
|
|
jail := by[ComposedJailsID()]
|
|
if jail == nil || jail["path"] != "/etc/fail2ban/jail.d/mesh-composed.conf" {
|
|
t.Fatalf("the composed jail file was not written: %v", jail)
|
|
}
|
|
body := jail["content"].(string)
|
|
if !strings.Contains(body, "[postgres-auth]") || !strings.Contains(body, "filter = postgres-auth") ||
|
|
!strings.Contains(body, "port = 5432") {
|
|
t.Fatalf("the postgres jail stanza was not composed in:\n%s", body)
|
|
}
|
|
filter := by["filter-postgres-auth"]
|
|
if filter == nil || filter["path"] != "/etc/fail2ban/filter.d/postgres-auth.conf" {
|
|
t.Fatalf("the jail's filter file was not written: %v", filter)
|
|
}
|
|
if !strings.Contains(filter["content"].(string), "failregex = auth failed from <HOST>") {
|
|
t.Fatalf("the failregex was not written: %v", filter["content"])
|
|
}
|
|
}
|
|
|
|
// A holder whose node runs no jail-declaring module still gets the file, empty — so removing the
|
|
// last jail is a change the service restarts on, not a file that vanishes.
|
|
func TestTheComposedJailFileIsWrittenEvenWhenEmpty(t *testing.T) {
|
|
files := jailsInto([]Manifest{{Module: "fail2ban"}}, &Jailing{Into: "/x", FilterInto: "/f"})
|
|
if len(files) != 1 || files[0]["id"] != ComposedJailsID() {
|
|
t.Fatalf("the empty composed jail file was not written alone: %v", files)
|
|
}
|
|
}
|
|
|
|
// A changed pattern restarts fail2ban (novox/hq issue 191's rollout): the service restarts when the
|
|
// composed jail file changes, and the filter is a file of its own, so the jail file names the
|
|
// filter's digest. Changing only the failregex must change the jail file; the same pattern must not.
|
|
func TestAChangedFilterChangesTheJailFile(t *testing.T) {
|
|
jailFile := func(failregex string) string {
|
|
modules := []Manifest{
|
|
{Module: "fail2ban", Jailing: &Jailing{Into: "/etc/fail2ban/jail.d/mesh.conf", FilterInto: "/etc/fail2ban/filter.d"}},
|
|
{Module: "gitea", Jails: []Jail{{Name: "gitea", Failregex: failregex, Jail: "port = 222"}}},
|
|
}
|
|
for _, f := range jailsInto(modules, modules[0].Jailing) {
|
|
if f["id"] == ComposedJailsID() {
|
|
return f["content"].(string)
|
|
}
|
|
}
|
|
t.Fatal("no composed jail file")
|
|
return ""
|
|
}
|
|
before := jailFile("web login failed from <HOST>")
|
|
if again := jailFile("web login failed from <HOST>"); again != before {
|
|
t.Errorf("the same pattern composed a different jail file, which would restart fail2ban for nothing")
|
|
}
|
|
if after := jailFile("web login failed from <HOST>\n Invalid user .* from <HOST>"); after == before {
|
|
t.Errorf("a changed pattern left the jail file as it was, so fail2ban keeps the old filter:\n%s", after)
|
|
}
|
|
}
|