to-be 31: a module declares its fail2ban jail, mesh composes them per node #149
@@ -0,0 +1,65 @@
|
|||||||
|
---
|
||||||
|
layer: to-be
|
||||||
|
status: proposed
|
||||||
|
code: []
|
||||||
|
updated: 2026-09-27
|
||||||
|
decisions:
|
||||||
|
- 02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md
|
||||||
|
---
|
||||||
|
|
||||||
|
# 31 — A module declares its fail2ban jail, and the mesh composes them per node
|
||||||
|
|
||||||
|
**A node's intrusion filter should be composed from the modules it runs, the same way its firewall
|
||||||
|
is.** The mesh already derives a node's nftables ruleset from every assigned module's `listens` and
|
||||||
|
`guards` (the `Filtering` mechanism). fail2ban is the same shape and is not modelled: a module that
|
||||||
|
runs an authenticating service — postgres, mssql, mailu — has a jail (a filter that reads its log
|
||||||
|
and a jail stanza that bans on it), and which jails a node's fail2ban runs should be exactly the
|
||||||
|
jails of the modules assigned to that node.
|
||||||
|
|
||||||
|
The predecessor did this with per-module files: `postgres` shipped `postgres-auth.conf`, `mssql`
|
||||||
|
shipped `mssql-auth.conf`, `mailu` shipped `mailu.conf`, and the node's fail2ban read whichever were
|
||||||
|
present. When HAL retired on novox those became dangling symlinks — fail2ban ran the jails only from
|
||||||
|
memory, and a restart would have dropped them. The base was salvaged (the fail2ban module now ships
|
||||||
|
`sshd`, `recidive`, and the `ignoreip` that spares the mesh's own range), but the **service jails
|
||||||
|
are gone**, because no nox module declares one yet.
|
||||||
|
|
||||||
|
## The shape
|
||||||
|
|
||||||
|
- **A module declares its jail in its manifest**, naming no node and no path (ADR 0112): the filter
|
||||||
|
(the failregex, or a stock filter it uses) and the jail stanza (port, logpath, maxretry, bantime).
|
||||||
|
The `postgres` module says what a postgres brute-force looks like and how to ban it; it does not
|
||||||
|
say on which machine, because it does not know.
|
||||||
|
- **The mesh composes them per node.** For each node, the jails of its assigned modules are gathered
|
||||||
|
and written into the fail2ban holder's `jail.d/` (and filters into `filter.d/`), exactly as
|
||||||
|
`listens`/`guards` are gathered into the node's firewall. So a node running postgres gets the
|
||||||
|
postgres jail; a node not running it does not. The `node-intrusion-prevention` holder receives
|
||||||
|
them the way a provider receives its consumers' contributions.
|
||||||
|
- **The base stays the fail2ban module's**: `sshd`, `recidive`, and the `ignoreip` naming
|
||||||
|
`${machine:mesh-range}` so a tunnel peer is never banned.
|
||||||
|
|
||||||
|
## Why this, and not the module writing the file itself
|
||||||
|
|
||||||
|
A module could declare a `file` resource at `/etc/fail2ban/jail.d/<x>.conf` directly. Rejected: the
|
||||||
|
path is the fail2ban holder's to own (one module owns `jail.d`, as one module owns the firewall
|
||||||
|
table), the jail's logpath and defaults want the mesh's composition (the `ignoreip`, the ban action
|
||||||
|
the node uses), and two modules writing into one directory is the collision the seat/holder model
|
||||||
|
exists to prevent. The module declares *what its jail is*; the holder's composition decides *how it
|
||||||
|
lands* — the same split as `listens` (the module says the port; the mesh says the rule).
|
||||||
|
|
||||||
|
## Why now
|
||||||
|
|
||||||
|
fail2ban on novox currently runs the service jails from memory only; the next restart drops them
|
||||||
|
(the `ignoreip` is safe on disk, so the mesh-partition risk is closed, but postgres/mssql/mailu
|
||||||
|
auth-banning would be lost). This is the mechanism that restores them properly, and it is needed as
|
||||||
|
each of those modules migrates to the other nodes — ace running postgres should get the postgres
|
||||||
|
jail, composed from the postgres module's manifest, without anyone editing a node.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [ADR 0112](../../02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md) — a module
|
||||||
|
names no node or path; its jail is declared the same way its `listens` are
|
||||||
|
- mesh-controller `internal/catalogue/adoption.go` (`Filtering` — the firewall composition this
|
||||||
|
mirrors), `internal/catalogue/manifest.go` (`Listens`/`Guards`, the fields a jail field sits
|
||||||
|
beside)
|
||||||
|
- mesh-catalog `modules/fail2ban` (the base: sshd, recidive, ignoreip); the service modules
|
||||||
|
(`postgres`, `mssql`, `mailu`) that will declare jails
|
||||||
Reference in New Issue
Block a user