feat(infra): production deployment pipeline — Caddy, staging, Gitea Actions (#497) #499

Merged
marcel merged 39 commits from feat/issue-497-prod-deploy into main 2026-05-11 14:29:33 +02:00
2 changed files with 56 additions and 0 deletions
Showing only changes of commit ad69d7cb83 - Show all commits

View File

@@ -0,0 +1,29 @@
# fail2ban filter for credential-stuffing attempts against the
# Familienarchiv login endpoint.
#
# Parses Caddy JSON access log entries (configured in
# infra/caddy/Caddyfile via the (access_log) snippet).
#
# Sample matched line (whitespace inserted for readability):
# {"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,…}
#
# Caddy emits remote_ip *inside* the request object and status at the
# top level. The order within the request object is stable
# (remote_ip → … → uri) across Caddy 2.7+. Lazy `.*?` keeps the regex
# robust to header-dict size growth.
[INCLUDES]
before = common.conf
[Definition]
failregex = ^\s*\{.*?"remote_ip":"<HOST>".*?"uri":"/api/auth/login.*?"status":\s*401\b
ignoreregex =
# Caddy's ts field is a Unix epoch with sub-second precision.
datepattern = "ts":{EPOCH}

View File

@@ -0,0 +1,27 @@
# Jail definition for the Familienarchiv login endpoint.
#
# Install: ln -sf /opt/familienarchiv/infra/fail2ban/jail.d/familienarchiv.conf \
# /etc/fail2ban/jail.d/familienarchiv.conf
# ln -sf /opt/familienarchiv/infra/fail2ban/filter.d/familienarchiv-auth.conf \
# /etc/fail2ban/filter.d/familienarchiv-auth.conf
# systemctl reload fail2ban
#
# Verify with:
# fail2ban-client status familienarchiv-auth
# fail2ban-regex /var/log/caddy/access.log familienarchiv-auth
#
# Tuning rationale:
# - maxretry 10: legitimate users mistyping passwords don't trip the jail
# - findtime 10m: rolling window that catches automated brute force
# - bantime 30m: long enough to discourage scripted attacks, short
# enough that a user who fat-fingered their VPN comes
# back online within a coffee break
[familienarchiv-auth]
enabled = true
filter = familienarchiv-auth
logpath = /var/log/caddy/access.log
maxretry = 10
findtime = 10m
bantime = 30m
action = iptables-multiport[name=familienarchiv-auth, port="http,https"]