A changed jail filter restarts fail2ban #231
@@ -1,6 +1,8 @@
|
||||
package catalogue
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -43,8 +45,16 @@ func jailsInto(modules []Manifest, j *Jailing) []map[string]any {
|
||||
|
||||
out := make([]map[string]any, 0, len(jails)+1)
|
||||
for _, d := range jails {
|
||||
fmt.Fprintf(&composed, "\n# from %s\n[%s]\nenabled = true\nfilter = %s\n%s\n",
|
||||
d.module, d.jail.Name, d.jail.Name, strings.TrimRight(d.jail.Jail, "\n"))
|
||||
// **The filter's digest rides in the jail file.** fail2ban is restarted when this file
|
||||
// changes, and the filter is a file of its own: a module that changed only what a failure
|
||||
// looks like rewrote the filter on disk and left the running jail on the old pattern, with
|
||||
// nothing said (novox/hq issue 191's rollout found it on gitea's sshd). Naming the filter's
|
||||
// digest here makes a changed pattern a changed jail file, so the restart the service
|
||||
// already takes on it covers the filter too.
|
||||
sum := sha256.Sum256([]byte(d.jail.Failregex))
|
||||
fmt.Fprintf(&composed, "\n# from %s, filter %s\n[%s]\nenabled = true\nfilter = %s\n%s\n",
|
||||
d.module, hex.EncodeToString(sum[:])[:12], d.jail.Name, d.jail.Name,
|
||||
strings.TrimRight(d.jail.Jail, "\n"))
|
||||
// The filter is a file of its own, named as the jail's filter= references it.
|
||||
out = append(out, map[string]any{
|
||||
"id": "filter-" + d.jail.Name,
|
||||
|
||||
@@ -44,3 +44,29 @@ func TestTheComposedJailFileIsWrittenEvenWhenEmpty(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user