Jails
A jail is a filter over a log plus what to do when it matches. The protected product pushes it, because it is the one that knows what its logs look like.
Pushing a jail
PUT /jails/{name} takes the jail and its filter as text:
{
"jailConf": "[odoo-login]\nenabled = true\nfilter = odoo-login\nlogpath = /var/lib/odoo/*/logs/odoo.log\nport = http,https\n",
"filterConf": "[Definition]\nfailregex = ^.*Login failed for (?:db:\\S+ )?login:\\S+ from <HOST>\\s*$\n"
}
Three rules the call enforces:
- The jail's section header must match the name in the route.
- The filter must define a
failregex. - Every log path must be in a log folder mounted into the container. See Where a jail may read.
SSH on a Synology NAS
DSM's SSH server writes no Failed password lines, so fail2ban's own SSH filter counts nothing on a
Synology NAS and the jail reports zero failures however many wrong passwords arrive. Start the jail
from the SSH on Synology DSM template instead: its filter reads the two lines DSM writes with
the address for each failed session, so three wrong passwords in one session count as two failures.
Set its port to the one DSM's SSH listens on (Control Panel > Terminal & SNMP), and keep the
chain at INPUT, because SSH runs on the NAS itself.
Odoo
Start the jail from the Odoo: failed logins template. Its filter counts every refused sign-in on Odoo 15.0 to 20.0, in both forms Odoo writes:
| Odoo | The line it writes for a refused sign-in |
|---|---|
| 15.0 to 18.0 | Login failed for db:<database> login:<login> from <address> |
| 19.0 and 20.0 | Login failed for login:<login> from <address> |
The filter accepts the line from any module, so a module that refuses a sign-in itself and writes
Odoo's own wording is counted too. Login successful lines are not counted. The address banned is
the last one on the line, so a login name that contains from <address> cannot get another address
banned.
Two things Odoo has to do for the jail to see anything:
- Log at INFO or below. The line is an INFO line, so a
log_levelofwarnor higher writes nothing to count. Odoo also has to write its log to a file (logfilein its configuration), and that file has to be mounted into this container at the path the jail names. See The log has to be mounted. - Run with
--proxy-modebehind a proxy (proxy_mode = True), with the proxy settingX-Forwarded-For. Without it Odoo writes the proxy's own address on every line, and the jail bans the proxy.
The Odoo Host Manager pushes its own Odoo jail to its hosts, so nothing here is needed there.
Fixing the template does not change a jail already saved from it: the jail keeps the filter it was
saved with, which needs the database in the line. On the Jails page, choose Edit on that
jail and replace the failregex line in its Filter with the template's new one, then Save:
failregex = ^.* \S+: Login failed for (?:db:\S+ )?login:.* from <HOST>\s*$
Or remove the jail and add it again from the template.
A jail's own chain
By default every jail bans into the chain on the Options page. A jail can ban into another one with
a chain line in its own section, which outranks the Options page for that jail alone:
[sshd]
chain = INPUT
enabled = true
That is how one host protects its own SSH on INPUT and a published container port on
DOCKER-USER at the same time. In the console, the jail editor's Chain list writes and removes
that line for you; As on the Options page removes it, so the jail inherits again. See
Which chain the bans go into.
A bad definition never reaches the daemon
The definition is written, checked with fail2ban's own configuration test, applied with a reload, and rolled back if the daemon refuses it.
A definition that would stop the daemon therefore never reaches it: you get an error, and the jails you already had keep running.
Where jails are kept
Every jail is kept in the service's database, with its filter. fail2ban still reads files, so the service writes each jail's files from the database, at every start and on every change.
The files are output, not the record. A file in the jail folder that the database does not hold is
removed when the service starts, so the daemon never runs a jail nobody can see on the Jails page.
Add jails on the Jails page or with PUT /jails/{name}.
On the first start after updating from a release that kept jails only as files, every jail file is brought into the database once, with its filter, and nothing is lost.
The log has to be mounted
Read-only, into this container, at the path the jail names. A jail pointed at a path that is not there matches nothing for ever and reports it nowhere except fail2ban's own log - so a jail that looks perfect can protect nothing. See Installing Fail2Ban.
Where a jail may read
The daemon reads a jail's logs as root, and every line it matches is shown on the Bans page and in the notifications - so a jail may read logs, and only logs. At start-up the service reads which folders are mounted into its container, and a jail is refused when one of its log paths is:
- outside every one of those folders, or inside the service's own configuration folder (
/config), which holds its settings, keys and accounts; - not a full path, or climbing out of a folder with
..; - written with a fail2ban variable such as
%(sshd_log)s. Write the path out in full.
Every log path counts: each line of a multi-line logpath, globs included, and a logpath
argument given to an action, such as the mail action that sends the matching lines. The refusal
names the path and the folders that are allowed.
The compose files mount /var/log. The Odoo Host Manager mounts /var/log, its instance tree,
its nginx logs and a log folder of its own, each at the path it has on the host, so its jails need
nothing extra. To allow fewer folders than are mounted, list them:
Jails__LogRoots__0=/var/log/nginx
Jails__LogRoots__1=/var/lib/odoo
A jail stored before 0.6.15 that reads outside these folders keeps running, but is refused the next time it is saved.
The routes
| Route | What it does | License |
|---|---|---|
GET /jails, GET /jails/{name} | Every stored jail, and one with its definition | Always available |
PUT /jails/{name} | Creates or replaces a jail and applies it | Always available |
DELETE /jails/{name} | Stops a jail and removes it | Always available |
POST /reload | Makes the daemon re-read its configuration | Always available |
GET /options | The global options every jail inherits | Always available |
PUT /options | Changes them and applies them | Always available |
No route here is gated. What the license decides is whether a jail's ban writes a firewall rule or
only a WOULD BAN line in the log. See Licensing.
Global options
Every jail inherits them: ban time, find time, retry count and the like. Changing them applies them, so a change here reaches every jail at once - which is what you want for a policy and worth remembering before you narrow one.