Update four existing specs (overview, server, napi-and-pubsub, call-protocol) to reflect Phase 0 decisions: three-layer model, IdentityProvider, ForwardingPolicy, OperationEnv, static/dynamic config split. Review all 9 Phase 0a ADRs (026-034) for consistency. Fix 4 critical issues from architecture review: missing OQ-SVC-05 in open-questions.md, deprecated hub terminology, undefined AuthService and noq terms. Replace inline OQ text with cross-references per format rules. Add ConfigServiceImpl definition to configuration.md. Port absolute workspace paths to project-relative links by copying referenced docs (feasibility, certbot, fail2ban, event_source_types) into docs/research/.
4.1 KiB
Fail2ban — dev1
Status
Active. 7 jails. Uses nftables backend with systemd journal.
Active Jails
| Jail | Port | Filter | Max Retry | Find Time | Ban Time | Log Source |
|---|---|---|---|---|---|---|
| sshd | ssh | sshd | default (5) | default (10m) | default (10m) | systemd journal |
| gitea | ssh | gitea | 5 | 10m | 1h | journald (CONTAINER_NAME=gitea) |
| nginx-badbots | http,https | nginx-badbots | 5 | 10m | 1h | /var/log/nginx/access.log |
| nginx-botsearch | http,https | nginx-botsearch | default | default | default | /var/log/nginx/access.log |
| nginx-limit-req | http,https | nginx-limit-req | default | default | default | /var/log/nginx/error.log |
| nginx-401 | http,https | nginx-401 | 5 | 10m | 1h | /var/log/nginx/access.log |
| nginx-403 | http,https | nginx-403 | 10 | 10m | 30m | /var/log/nginx/access.log |
Configuration
Default settings in /etc/fail2ban/jail.d/defaults-debian.conf:
[DEFAULT]
banaction = nftables
banaction_allports = nftables[type=allports]
backend = systemd
Jail configs in /etc/fail2ban/jail.d/:
gitea.conf— Gitea jail with Docker journald log drivernginx.conf— nginx-related jails
Gitea Jail Details
Gitea runs in Docker with the journald log driver. The fail2ban filter uses journalmatch to read only Gitea container logs:
[gitea]
enabled = true
port = ssh
filter = gitea
backend = systemd
journalmatch = CONTAINER_NAME=gitea
maxretry = 5
findtime = 10m
bantime = 1h
action = iptables-allports[chain="DOCKER-USER"]
The DOCKER-USER chain ensures bans affect Docker traffic.
Custom Filters
Default install includes gitea.conf, nginx-401.conf, nginx-403.conf in /etc/fail2ban/filter.d/. Custom filter:
nginx-badbots (/etc/fail2ban/filter.d/nginx-badbots.conf)
Catches malicious requests that the other nginx jails miss: .env/.git probes, PROPFIND/CONNECT abuse, common exploit paths (/actuator, /cgi-bin, /ecp, /SDK), and binary/garbage requests. Matches 400/404/405/413 status codes for known-bad path patterns only — legitimate 404s (e.g. wrong Gitea repo name) are not matched.
Lesson Learned: Default Filters Miss Most Scanner Traffic
The default fail2ban nginx filters (nginx-botsearch, nginx-401, nginx-403, nginx-limit-req) only catch a narrow subset of malicious requests:
- nginx-botsearch only matches
<webmail|phpmyadmin|wordpress|cgi-bin|mysqladmin>paths returning 404. Misses.env,.git/config,/actuator,/SDK,/ecp, crypto mining RPC, PROPFIND/CONNECT abuse, and binary garbage — all of which return 400/405 instead of 404. - nginx-401/403 only trigger on those specific status codes. Most scanners get 400 or 405.
- nginx-limit-req only triggers when the rate limiter in nginx actually rejects a request.
Result: A site with heavy scanner traffic can show zero bans from all four default jails. The nginx-badbots custom filter closes this gap by matching known-bad path patterns regardless of status code.
Verifying Jail Coverage
When setting up fail2ban on a new host:
- Install jails and filters first
- Let traffic flow for a few hours
- Run
sudo fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/<filter>.confto verify each filter matches expected lines - Check
sudo fail2ban-client statusto confirm jails showTotal failed > 0— if any jail stays at 0 for hours on a public-facing host, the filter likely has a gap - Inspect logs manually:
awk '$9>=400' /var/log/nginx/access.log | awk '{print $9}' | sort | uniq -c | sort -rnshows which status codes scanners are hitting
Adding the nginx-badbots Filter to a New Host
- Copy
/etc/fail2ban/filter.d/nginx-badbots.confto the new host - Append the jail config to
/etc/fail2ban/jail.d/nginx.conf:
[nginx-badbots]
enabled = true
port = http,https
filter = nginx-badbots
logpath = /var/log/nginx/access.log
maxretry = 5
findtime = 10m
bantime = 1h
sudo fail2ban-client reload
Commands
sudo fail2ban-client status
sudo fail2ban-client status gitea
sudo fail2ban-client set gitea unbanip <IP>
sudo journalctl -u fail2ban -f