From e5363913ec368fea74fdff200b4ef6260b28fb3b Mon Sep 17 00:00:00 2001 From: Marcel Date: Mon, 11 May 2026 14:59:40 +0200 Subject: [PATCH] fix(fail2ban): pin polling backend so jail actually reads Caddy access log Closes #503. Debian's fail2ban package ships defaults-debian.conf with `[DEFAULT] backend = systemd`. Without an explicit override, our familienarchiv-auth jail inherits the systemd backend at runtime, reads from journald, and never inspects /var/log/caddy/access.log. A live login brute-force would not be banned. Add `backend = polling` to the jail and a CI step that links the jail into /etc/fail2ban/ and asserts `fail2ban-client -d` resolves it to the polling backend, not the inherited systemd backend. Co-Authored-By: Claude Opus 4.7 --- .gitea/workflows/ci.yml | 15 +++++++++++++++ infra/fail2ban/jail.d/familienarchiv.conf | 6 ++++++ 2 files changed, 21 insertions(+) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 6f6aa0e0..d48d6a48 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -172,6 +172,21 @@ jobs: echo "$out" | grep -qE '0 matched' \ || { echo "expected 0 matches for /api/documents 401"; exit 1; } + # ── Backend resolves to file-polling, not systemd ───────────────────── + # The Debian/Ubuntu fail2ban package ships defaults-debian.conf with + # `[DEFAULT] backend = systemd`. Without `backend = polling` in our + # jail, the daemon loads the jail but reads from journald and never + # touches /var/log/caddy/access.log — i.e. the regex above passes in + # isolation while the live jail is inert. See issue #503. + - name: Jail resolves with polling backend (not inherited systemd) + run: | + sudo ln -sfn "$PWD/infra/fail2ban/jail.d/familienarchiv.conf" /etc/fail2ban/jail.d/familienarchiv.conf + sudo ln -sfn "$PWD/infra/fail2ban/filter.d/familienarchiv-auth.conf" /etc/fail2ban/filter.d/familienarchiv-auth.conf + dump=$(sudo fail2ban-client -d 2>&1) + echo "$dump" | grep -E "add.*familienarchiv-auth" || true + echo "$dump" | grep -qE "\['add', 'familienarchiv-auth', 'polling'\]" \ + || { echo "FAIL: familienarchiv-auth jail did not resolve to 'polling' backend"; exit 1; } + # ─── Compose Bucket-Bootstrap Idempotency ───────────────────────────────────── # docker-compose.prod.yml's create-buckets service runs on every # `docker compose up` (one-shot, no restart). Must be idempotent — a diff --git a/infra/fail2ban/jail.d/familienarchiv.conf b/infra/fail2ban/jail.d/familienarchiv.conf index e70d655f..b5dffed0 100644 --- a/infra/fail2ban/jail.d/familienarchiv.conf +++ b/infra/fail2ban/jail.d/familienarchiv.conf @@ -19,6 +19,12 @@ [familienarchiv-auth] enabled = true +# Override Debian's `backend = systemd` default (set in +# /etc/fail2ban/jail.d/defaults-debian.conf). Without this line our jail +# inherits the systemd backend, reads from journald, and never inspects +# Caddy's file-based JSON access log — i.e. brute-force protection is inert. +# `polling` works without inotify and is fine for one rotated log file. +backend = polling filter = familienarchiv-auth logpath = /var/log/caddy/access.log maxretry = 10