fail2ban jail for git-over-HTTPS / API 401 brute force #175
Inga granskare
Etiketter
Inga etiketter
area/backups
area/ci
area/control-panel
area/identity
area/infra
area/observability
area/payments
area/security
area/storage
area/web
blocked
needs-info
needs-triage
ready-for-implementation
type
bug
type
chore
type
docs
type
epic
type
feature
type
task
wontfix
Ingen milstolpe
Inget projekt
Inga tilldelade
1 deltagare
Notiser
Förfallodatum
Inget förfallodatum satt.
Beroenden
Inga beroenden satta
Referens
bitborg/bitborg-infra!175
Läser in…
Hänvisa till i nytt ärende
Ingen beskrivning angiven.
Ta bort grenen "feat/127-fail2ban-https"
Borttagning av en gren är permanent. Även om den borttagna grenen kan fortsätta existera en kort tid innan den faktiskt tas bort, kan det INTE ångras i de flesta fall. Vill du fortsätta?
Problem
Only
[sshd]was jailed injail.local.j2. HTTP Basic auth for git-over-HTTPS andAPI-token auth stays on (
ENABLE_INTERNAL_SIGNIN=falseonly removes the web sign-in form),so those endpoints can be brute-forced with no host-layer lockout — nothing tailed Caddy's logs
for auth abuse.
Change
roles/base/templates/filter.d/caddy-auth.conf.j2— parses Caddy's JSON accesslog for repeated HTTP 401 responses and extracts the client IP.
[caddy-auth]injail.local.j2, wired to the samenftables-multiportbanaction inherited from
[DEFAULT]as[sshd]— no new ban mechanism.templates/filter.d/*.conf.j2into/etc/fail2ban/filter.d/(none existed before), before templatingjail.local, both notifyingthe existing
Restart fail2banhandler. Add a filter by dropping in a new template.Filter / jail design
"status":401. That is the answer to a bad Basic-auth credential or API token —the actual brute-force surface. This covers
/api/…,/user/login, and the git-HTTPS…/info/refs+git-upload/receive-packendpoints uniformly (a host-wide 401 match, as theissue asked). 403 is deliberately excluded — Forgejo returns 403 for
authenticated-but-forbidden and for CSRF, which would ban legitimate logged-in users.
request.remote_ip. Caddy is the edge and, via socket activation(#81), sees the true external client as the TCP peer, logged as
remote_ip— so we ban thereal client, not an internal proxy hop. (
request.client_ipis the XFF-derived value; sincethe only trusted proxy is the internal podman subnet, it equals
remote_ipfor real clients —remote_ipis the unambiguous choice.)<ADDR>(IP-only) is used rather than<HOST>becausethe field is always a bare IP; it matches both IPv4 and IPv6.
tsfield.fail2ban's bare
EPOCHtemplate only anchors on whitespace/line-start, which the"ts":prefix defeats, so the datepattern embeds the epoch template behind that literal key:
datepattern = "ts":{EPOCH}. Parsing the real log time (not read time) is what stops afail2ban restart from re-scanning the file and mass-banning on stale 401s.
/home/gitborg/caddy/logs/access.logviafail2ban_caddy_log_path(
{{ gitborg_home }}/caddy/logs/access.log) — the same host file the caddy role bind-mountsand the monitoring-agent's Alloy already tails. Built from the global
gitborg_homeso thebase role carries no dependency on the caddy role's vars. The jail overrides the
[DEFAULT]systemdbackend topollingbecause this source is a file, not the journal.Tuning (all overridable in
roles/base/defaults/main.yml)Verification
fail2ban-regex(v1.1.0) against five representative Caddy JSON lines(IPv4 git-HTTPS 401, IPv4
/api/v1401, IPv6/user/login401, a200, a403):The three 401s matched (IPv4 + IPv6 IPs extracted); the 200 and 403 were correctly missed; the
epoch parsed on all five lines.
Also green:
pnpm ansible:check(syntax) andansible-lint roles/base(production profile,0 failures).
Deploy (maintainer)
Not applied to prod. Deploy is
ansible-playbook site.yml --tags base; theRestart fail2banhandler reloads the new jail + filter. On existing prod the access log already exists, so the
jail comes up immediately. Caveat: on a fresh provision
baseruns beforecaddy, so thelog file doesn't exist yet at first
baseconverge — the[caddy-auth]jail is skipped untilthe log appears; a second
--tags baserun (or the next full converge) settles it.[sshd]isunaffected throughout.
Related
Complementary to #48 (edge rate-limiting in Caddy): #48 throttles request rate at the
proxy; this bans credential-guessing clients at the host firewall. They stack.
Closes #127
49744649c18200902b3a