From 9652894aa44dc5879387eb878060b7aee9666801 Mon Sep 17 00:00:00 2001 From: Marcel Date: Mon, 11 May 2026 13:03:04 +0200 Subject: [PATCH] test(ci): add fail2ban-regex regression job MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Caddy 2.x emits JSON access logs; the failregex in infra/fail2ban/filter.d/familienarchiv-auth.conf depends on the "remote_ip" → "uri" → "status" key order being stable. A future Caddy upgrade that reorders fields would break the jail silently (regex no longer matches → fail2ban returns 0 hits → host stops banning brute-force, discovered only at the next incident). This job pins the contract: a sample /api/auth/login 401 line must match (1 hit) and a /api/auth/login 200 line must not (0 hits). Catches a regression at PR time instead of in production. Co-Authored-By: Claude Opus 4.7 --- .gitea/workflows/ci.yml | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index e45d2a22..461b486d 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -114,4 +114,36 @@ jobs: run: | chmod +x mvnw ./mvnw clean test - working-directory: backend \ No newline at end of file + working-directory: backend + + # ─── fail2ban Regex Regression ──────────────────────────────────────────────── + # The filter parses Caddy's JSON access log; a Caddy upgrade that reorders + # the JSON keys would silently break it (fail2ban-regex would return + # "0 matches", fail2ban would stop banning, no error surface). This job + # pins the contract against a deterministic sample line. + fail2ban-regex: + name: fail2ban Regex + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install fail2ban + run: | + sudo apt-get update + sudo apt-get install -y fail2ban + + - name: Matches /api/auth/login 401 + run: | + echo '{"level":"info","ts":1700000000.12,"logger":"http.log.access","msg":"handled request","request":{"remote_ip":"203.0.113.42","method":"POST","host":"archiv.raddatz.cloud","uri":"/api/auth/login"},"status":401}' > /tmp/sample.log + out=$(fail2ban-regex /tmp/sample.log infra/fail2ban/filter.d/familienarchiv-auth.conf) + echo "$out" + echo "$out" | grep -qE '1 matched' \ + || { echo "expected 1 match for /api/auth/login 401"; exit 1; } + + - name: Does not match /api/auth/login 200 + run: | + echo '{"level":"info","ts":1700000000.12,"logger":"http.log.access","msg":"handled request","request":{"remote_ip":"203.0.113.42","method":"POST","host":"archiv.raddatz.cloud","uri":"/api/auth/login"},"status":200}' > /tmp/sample.log + out=$(fail2ban-regex /tmp/sample.log infra/fail2ban/filter.d/familienarchiv-auth.conf) + echo "$out" + echo "$out" | grep -qE '0 matched' \ + || { echo "expected 0 matches for /api/auth/login 200"; exit 1; } \ No newline at end of file