133 lines
9.8 KiB
Markdown
133 lines
9.8 KiB
Markdown
---
|
|
topic: the mesh
|
|
status: accepted
|
|
date: 2026-10-02
|
|
deciders: jochen
|
|
reconstructed: false
|
|
extends: 02-DECISIONS/0170-the-firewall-seat-serves-its-verbs.md
|
|
---
|
|
|
|
# 179. The intrusion seat serves its verbs, a container may log to the journal, and every door declares its jail
|
|
|
|
## Context
|
|
|
|
Read on the control node on 2026-10-02, the day the machines were confirmed filtered by the mesh
|
|
alone ([ADR 0168](0168-a-converged-machine-is-filtered-by-the-mesh-alone.md)): the intrusion
|
|
prevention watched one door. Its two jails read the ssh daemon's journal and its own log, banned
|
|
five failures in ten minutes for ten minutes, and in a day had seen twelve thousand failed logins
|
|
from three hundred addresses and banned none of the busiest, which paced themselves at one try every
|
|
ten minutes. The mail submission port took a hundred and sixty password guesses in the same day from
|
|
thirty-eight addresses with no jail reading it at all; the forge and the public proxy had no jail
|
|
either, and the proxy logged nothing a jail could read. Nobody could see the jails without a shell:
|
|
the module's three tools existed in code and were served by nothing, and the seat it holds declared
|
|
no verbs.
|
|
|
|
Three things were missing and they are three shapes the mesh already has. The packet filter's seat
|
|
serves verbs every holder owes ([ADR 0170](0170-the-firewall-seat-serves-its-verbs.md)); the
|
|
intrusion seat serves none. A module's `listens` compose into the machine's filter, and [to-be 31](../03-DESIGN/01-to-be/31-a-module-declares-its-fail2ban-jail.md)
|
|
says a module's `jails` compose into the machine's intrusion prevention the same way — the controller
|
|
composes them, and no module declares one. And a jail reads a log; a container's output goes to a
|
|
file of the runtime's own under a path that changes when the container is recreated, which is why
|
|
no jail could read the mail front end, the forge or the proxy, however they logged.
|
|
|
|
## Decision
|
|
|
|
**1. The `node-intrusion-prevention` seat serves four verbs**, and a module that claims it serves
|
|
all four or is refused the claim, as with every seat:
|
|
|
|
- `status` — every jail with what it watches, how many addresses it is counting failures against and
|
|
holding now, and the totals since it started; one jail's detail when named. Read-only.
|
|
- `banned` — every address banned now, with the jail holding it, when it was banned and when the ban
|
|
ends. Read-only.
|
|
- `ban` — ban one address in one jail now, for that jail's ban time. An operator's act on the live
|
|
ban list, which the mesh composes the rules for and never writes itself.
|
|
- `unban` — let one address go, from one jail or from every jail.
|
|
|
|
A holder may serve its own tools beside these; the fail2ban module reads one jail's effective
|
|
settings as its own.
|
|
|
|
**2. A container may log to the journal.** `logging: journald` on a container has the host run it
|
|
with the journal as its log driver; the journal keeps the container's name on every line, and
|
|
`docker logs` keeps working. Where a container logs is part of its spec, so moving it recreates the
|
|
container, and the only place besides the runtime's own file is the journal: a machine's intrusion
|
|
prevention reads the journal already, for the ssh daemon, and a container that logs there is read
|
|
the same way, by the container's name, whatever the container is called by the runtime this time.
|
|
|
|
**3. A module with a door declares its jail, and the holder composes them.** What to-be 31 designed
|
|
is now the rule: a module whose service authenticates from outside — the mail front end, the forge,
|
|
the public proxy — declares in its manifest what a failed attempt looks like in its log and how to
|
|
ban on it, naming no node and no path; the module that holds the intrusion seat declares where the
|
|
composed jails and filters land, and the mesh writes them on every machine that runs both. A machine
|
|
not running the module has no such jail. The holder restarts its daemon on the composed file.
|
|
|
|
**4. The base is strict, and the mesh's own range is never banned.** Three failures in a day ban for
|
|
a day, on every jail unless the jail says otherwise; banned twice in two weeks, by any jail, is
|
|
banned for four. The attackers this mesh sees pace themselves under any ten-minute window; a day's
|
|
window counts them. A person who mistypes three times from one address is out for a day from that
|
|
address, and never from a machine of the mesh, whose range stays in the never-banned list the module
|
|
has carried since [ADR 0112](0112-a-module-definition-names-no-node-mesh-or-path.md). The operator
|
|
chose this knowing it.
|
|
|
|
**5. The proxy says a refused name in its log.** A request for a name this mesh does not serve, from
|
|
outside, is what a scanner does; the proxy already logged a certificate refused for such a name, and
|
|
now logs the plain request too, with the asking address last, as its own jail's filter expects it.
|
|
|
|
## Consequences
|
|
|
|
- The seat's row gains four verbs; a mesh that already runs widens its row at the next controller
|
|
start. The fail2ban module claims them and gains a runtime — a tool server whose image carries the
|
|
fail2ban client, with the daemon's socket shared in from the machine, and nothing else of the
|
|
machine. The daemon stays the machine's; what runs in the container is only the client.
|
|
- **That runtime is the shape the catalogue has today, and it is on its way out.**
|
|
[ADR 0175](0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md),
|
|
accepted the same day as this record, replaces a tool container per module with one tool runtime
|
|
per node on the host side, taking each module's tools as a bundle. Nothing here depends on the
|
|
container: the verbs, the client that speaks to the daemon over its socket, and the jails are the
|
|
same code under either. This module converts with the packet filter's, whose runtime that record
|
|
names, and the socket it needs becomes the node runtime's to reach rather than a mount of its own.
|
|
- The host's container vocabulary grows by `logging`; an older host refuses a declaration that carries
|
|
it, so the host rolls before the modules. Three containers are recreated once, when their modules
|
|
are pushed with the field: the mail front end, the forge and the proxy — each a moment's outage.
|
|
- The fail2ban module declares where jails compose (`jailing`) and the directory the filters go in;
|
|
the mail, forge and proxy modules each declare one jail reading the journal by their container's
|
|
name. The composed jail file is the one resource the daemon restarts on when a module arrives or
|
|
leaves a machine.
|
|
- The two base jails and the composed ones take the day's window; the ssh jail's ten minutes are
|
|
gone. An address banned on the first day of this record stays banned for the day.
|
|
- The module's three old tools, served by nothing, are replaced by the seat's four verbs and one
|
|
own tool; `fail2ban_status` as a name is gone.
|
|
|
|
## How this is checked
|
|
|
|
| Rule | Checked by |
|
|
|---|---|
|
|
| The seat declares the four verbs; a claim that serves fewer is refused by name | the catalogue's seat tests |
|
|
| `status`, `banned`, `ban` and `unban` read and steer the daemon through its client, with the shapes fail2ban 1.1.0 printed live; a non-address and a non-name are refused before anything runs | the module's tests over a fake command runner |
|
|
| A container's `logging` reaches the runtime's arguments and its spec; a place other than the journal is refused | host tests |
|
|
| A module's jails compose into the holder's file and a filter per jail, and the file is written empty when none is declared | the controller's composition tests (to-be 31) |
|
|
| The proxy logs a refused name with the address last | the proxy's tests |
|
|
| A jail's pattern names `<HOST>` once per shape, since two is a duplicate capture group and costs the machine every ban | the catalogue's manifest tests |
|
|
| Live | done 2026-10-02: `status` and `banned` answered on both servers through the console; the proxy's jail counted seven refusals on the home server; a documentation address banned in the ssh jail came back with its end time and was released |
|
|
|
|
## Built and proven live, 2026-10-02
|
|
|
|
All five rules are in the mesh. The host carries `logging`; the controller's seat row carries the four
|
|
verbs and the proxy says a refused name in its log; the fail2ban module holds the seat from a runtime
|
|
with the daemon's socket shared in, composes the jails, and the mail front end, the forge and the
|
|
proxy each declare one. Through the console on the control node: `status` listed five jails with what
|
|
each watches, `banned` listed the nine the long jail holds, and a documentation address banned in the
|
|
ssh jail came back with its ban's end time and was released again. On the home server the proxy's jail
|
|
had counted seven refusals within minutes of starting.
|
|
|
|
**One fault, found by the machine and not by a test.** The proxy's pattern matched two shapes of
|
|
refusal in one expression and so named `<HOST>` twice. fail2ban expands that placeholder into a named
|
|
capture group; two of them is a duplicate group name, and the daemon refuses *its whole configuration*
|
|
and exits — both servers kept no bans at all for about ten minutes, every jail and not the one at
|
|
fault. The pattern is now one per shape. A manifest check refuses the mistake at merge time, naming
|
|
what it would cost, which is the only reason this record can claim the rule rather than the instance.
|
|
|
|
## References
|
|
|
|
- [ADR 0170](0170-the-firewall-seat-serves-its-verbs.md), [ADR 0168](0168-a-converged-machine-is-filtered-by-the-mesh-alone.md), [ADR 0112](0112-a-module-definition-names-no-node-mesh-or-path.md), [ADR 0159](0159-a-tool-call-names-the-machine-and-a-holder-serves-its-seats-verbs.md), [ADR 0175](0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md)
|
|
- [Design 31 — A module declares its fail2ban jail](../03-DESIGN/01-to-be/31-a-module-declares-its-fail2ban-jail.md), [Design 33 — The tools the mesh answers](../03-DESIGN/01-to-be/33-the-tools-the-mesh-answers.md), [Design 08 — Connectivity](../03-DESIGN/01-to-be/08-connectivity.md)
|