Files
jschoubben 47d412e13f A module declares its fail2ban jail; the mesh composes them per node (to-be 31)
The mechanism, mirroring Filtering: a module declares Jails (name, failregex,
jail stanza) naming no node/path (ADR 0112); the intrusion-prevention holder
declares Jailing (where composed jails go); the mesh gathers every assigned
module's jails into one jail.d file (a fixed id the fail2ban service restarts
on) plus a filter.d file per jail. A node not running a module has none of its
jails. Tested. Behaviour-neutral until a service module declares a jail — the
per-service content (postgres/mssql/mailu failregex+logpath) is authored next,
against how each container actually logs.
2026-09-27 17:20:50 +02:00

47 lines
2.0 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)
}
}