Ne pas merger/déployer avant que la procédure de validation XFF de #19 ait confirmé que gitea.log contient l'IP publique réelle du client, sinon le jail bannira le proxy lui-même au bout de 5 échecs et gitea.arnodo.fr deviendra intégralement inaccessible. Procédure documentée dans gitea/README.md (#19).
Dépendance
Base branchée sur feat/proxy-rsyslog (#20, non mergée) puisque 50-gitea.conf routage par tag repose sur le ruleset remoteLogs posé par le récepteur générique de #20. Ce diff ne montre donc que le delta propre à #21.
Contenu
jail.local — [DEFAULT] ignoreip = 127.0.0.1/8 ::1 100.64.0.0/10, posé avant le jail lui-même : garantit structurellement qu'une XFF mal configurée ne peut pas bannir le tailnet, plutôt que de compter sur un rattrapage après coup.
Jail gitea-auth (filter.d/gitea.conf + jail.d/gitea.conf) — Gitea renvoie un 200 sur un login raté (page simplement ré-affichée avec l'erreur), donc traefik-auth (qui matche sur DownstreamStatus) est structurellement aveugle à ce brute force. Ce jail lit le log applicatif forwardé via 50-gitea.conf (dessus du récepteur générique de #20).
conf.d/gitea.yml réécrit avec 3 routers : gitea-metrics-deny (priorité 200, ipAllowList limité à 127.0.0.1/32), gitea-auth (priorité 100, rate-limit 6/1m burst 12 sur /user/login, /user/sign_up, /user/forgot_password), gitea (priorité 1, catch-all). Backend basculé sur https://gitea.taila5ad8.ts.net (443, sans port) — conséquence du tailscale serve --https=443 de #19, incompatible avec l'ancien backend :3000 de l'instance community-scripts actuelle.
Vérification
Tout testé avec les vrais binaires (fail2ban, rsyslog, Traefik v3) dans des conteneurs jetables, pas de simulation :
Filtre gitea.conf : fail2ban-regex sur un gitea.log synthétique → les 3 lignes d'échec matchent, la ligne GET /user/login ... 200 OK normale ne matche pas.
Ban effectif : 5 échecs depuis 203.0.113.99 → banni. 5 échecs depuis 100.64.1.50 (plage tailnet) → jamais banni, ignoreip fait son travail.
Routage rsyslog : message tagué gitea envoyé en TCP → atterrit dans /var/log/gitea/gitea.log (jail) et dans /var/log/remote/<hostname>.log (catch-all générique de #20), sans conflit.
Traefik v3 réel : les 3 routers, les 2 middlewares (auth-ratelimit, deny-public) et les priorités déclarées se chargent sans erreur (seules erreurs : "nonexistent certificate resolver", attendu — letsencrypt n'est pas configuré dans le harnais de test minimal, il l'est dans le traefik.yml réel du script).
shellcheck proxy/install.sh : aucune nouvelle alerte (seul le SC2006 préexistant, sur du code non touché par cette PR).
Idempotence
Tous les fichiers sont écrits inconditionnellement à chaque run (même pattern que les blocs fail2ban/rsyslog existants) — un rejeu réapplique simplement la version courante, sans duplication.
Note de séquencement
Rappelée dans le script : le basculement du backend Traefik rend cette config incompatible avec l'instance Gitea actuellement en production (community-scripts, :3000). À appliquer seulement au moment de la bascule effective vers la nouvelle instance (#19), pas avant.
## ⚠️ Prérequis bloquant — rappel
**Ne pas merger/déployer avant que la procédure de validation XFF de #19 ait confirmé que `gitea.log` contient l'IP publique réelle du client**, sinon le jail bannira le proxy lui-même au bout de 5 échecs et `gitea.arnodo.fr` deviendra intégralement inaccessible. Procédure documentée dans `gitea/README.md` (#19).
## Dépendance
Base branchée sur `feat/proxy-rsyslog` (#20, non mergée) puisque `50-gitea.conf` routage par tag repose sur le ruleset `remoteLogs` posé par le récepteur générique de #20. Ce diff ne montre donc que le delta propre à #21.
## Contenu
1. **`jail.local`** — `[DEFAULT] ignoreip = 127.0.0.1/8 ::1 100.64.0.0/10`, posé avant le jail lui-même : garantit structurellement qu'une XFF mal configurée ne peut pas bannir le tailnet, plutôt que de compter sur un rattrapage après coup.
2. **Jail `gitea-auth`** (`filter.d/gitea.conf` + `jail.d/gitea.conf`) — Gitea renvoie un **200** sur un login raté (page simplement ré-affichée avec l'erreur), donc `traefik-auth` (qui matche sur `DownstreamStatus`) est structurellement aveugle à ce brute force. Ce jail lit le log applicatif forwardé via `50-gitea.conf` (dessus du récepteur générique de #20).
3. **`conf.d/gitea.yml`** réécrit avec 3 routers : `gitea-metrics-deny` (priorité 200, `ipAllowList` limité à `127.0.0.1/32`), `gitea-auth` (priorité 100, rate-limit 6/1m burst 12 sur `/user/login`, `/user/sign_up`, `/user/forgot_password`), `gitea` (priorité 1, catch-all). Backend basculé sur `https://gitea.taila5ad8.ts.net` (443, sans port) — conséquence du `tailscale serve --https=443` de #19, **incompatible avec l'ancien backend `:3000`** de l'instance community-scripts actuelle.
## Vérification
Tout testé avec les vrais binaires (fail2ban, rsyslog, Traefik v3) dans des conteneurs jetables, pas de simulation :
- **Filtre `gitea.conf`** : `fail2ban-regex` sur un `gitea.log` synthétique → les 3 lignes d'échec matchent, la ligne `GET /user/login ... 200 OK` normale ne matche pas.
- **Jail réelle** : `fail2ban-client` démarré avec `jail.local` + `filter.d/gitea.conf` + `jail.d/gitea.conf` → `gitea-auth` opérationnel, `ignoreip` confirmé (`127.0.0.0/8`, `100.64.0.0/10`, `::1`).
- **Ban effectif** : 5 échecs depuis `203.0.113.99` → banni. 5 échecs depuis `100.64.1.50` (plage tailnet) → jamais banni, `ignoreip` fait son travail.
- **Routage rsyslog** : message tagué `gitea` envoyé en TCP → atterrit dans `/var/log/gitea/gitea.log` (jail) et dans `/var/log/remote/<hostname>.log` (catch-all générique de #20), sans conflit.
- **Traefik v3 réel** : les 3 routers, les 2 middlewares (`auth-ratelimit`, `deny-public`) et les priorités déclarées se chargent sans erreur (seules erreurs : "nonexistent certificate resolver", attendu — `letsencrypt` n'est pas configuré dans le harnais de test minimal, il l'est dans le `traefik.yml` réel du script).
- `shellcheck proxy/install.sh` : aucune nouvelle alerte (seul le SC2006 préexistant, sur du code non touché par cette PR).
## Idempotence
Tous les fichiers sont écrits inconditionnellement à chaque run (même pattern que les blocs fail2ban/rsyslog existants) — un rejeu réapplique simplement la version courante, sans duplication.
## Note de séquencement
Rappelée dans le script : le basculement du backend Traefik rend cette config incompatible avec l'instance Gitea actuellement en production (community-scripts, `:3000`). À appliquer seulement au moment de la bascule effective vers la nouvelle instance (#19), pas avant.
Closes #21
Gitea returns HTTP 200 on a failed web login (the page is simply
re-rendered with an error), so the existing traefik-auth jail, which
matches on DownstreamStatus, is structurally blind to brute force on
the login form. Adds a dedicated gitea-auth jail reading the
forwarded application log (routed by the new 50-gitea.conf on top of
#20's generic receiver), plus a Traefik-side rate limit on
/user/login, /user/sign_up and /user/forgot_password as a second
layer.
[DEFAULT] ignoreip in jail.local guards every jail (not just
gitea-auth) so a misconfigured X-Forwarded-For chain can't ban the
tailnet itself — this must be in place before the jail, since the
whole point is to make that failure mode structurally impossible
rather than caught after the fact.
conf.d/gitea.yml gains a highest-priority router that denies public
access to /metrics (ipAllowList limited to loopback) while leaving it
reachable on the tailnet, where Gitea's own tailscale serve answers
directly. The backend also moves to https://gitea.taila5ad8.ts.net
(443, no port) — the result of #19's tailscale serve --https=443 —
which is NOT compatible with the previous :3000 backend of the old
community-scripts deployment; only apply once the data migration has
actually happened.
Verified with real fail2ban/rsyslog/Traefik v3 in disposable
containers: the gitea filter matches real failure log lines and
misses the normal login-page line; 5 failed logins from an external
IP bans it while 5 from the Tailscale CGNAT range never do (ignoreip
in effect); a forwarded "gitea"-tagged message lands in
/var/log/gitea/gitea.log; Traefik loads all three routers, both
middlewares, and the stated priorities without error.
Closes#21
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
⚠️ Prérequis bloquant — rappel
Ne pas merger/déployer avant que la procédure de validation XFF de #19 ait confirmé que
gitea.logcontient l'IP publique réelle du client, sinon le jail bannira le proxy lui-même au bout de 5 échecs etgitea.arnodo.frdeviendra intégralement inaccessible. Procédure documentée dansgitea/README.md(#19).Dépendance
Base branchée sur
feat/proxy-rsyslog(#20, non mergée) puisque50-gitea.confroutage par tag repose sur le rulesetremoteLogsposé par le récepteur générique de #20. Ce diff ne montre donc que le delta propre à #21.Contenu
jail.local—[DEFAULT] ignoreip = 127.0.0.1/8 ::1 100.64.0.0/10, posé avant le jail lui-même : garantit structurellement qu'une XFF mal configurée ne peut pas bannir le tailnet, plutôt que de compter sur un rattrapage après coup.gitea-auth(filter.d/gitea.conf+jail.d/gitea.conf) — Gitea renvoie un 200 sur un login raté (page simplement ré-affichée avec l'erreur), donctraefik-auth(qui matche surDownstreamStatus) est structurellement aveugle à ce brute force. Ce jail lit le log applicatif forwardé via50-gitea.conf(dessus du récepteur générique de #20).conf.d/gitea.ymlréécrit avec 3 routers :gitea-metrics-deny(priorité 200,ipAllowListlimité à127.0.0.1/32),gitea-auth(priorité 100, rate-limit 6/1m burst 12 sur/user/login,/user/sign_up,/user/forgot_password),gitea(priorité 1, catch-all). Backend basculé surhttps://gitea.taila5ad8.ts.net(443, sans port) — conséquence dutailscale serve --https=443de #19, incompatible avec l'ancien backend:3000de l'instance community-scripts actuelle.Vérification
Tout testé avec les vrais binaires (fail2ban, rsyslog, Traefik v3) dans des conteneurs jetables, pas de simulation :
gitea.conf:fail2ban-regexsur ungitea.logsynthétique → les 3 lignes d'échec matchent, la ligneGET /user/login ... 200 OKnormale ne matche pas.fail2ban-clientdémarré avecjail.local+filter.d/gitea.conf+jail.d/gitea.conf→gitea-authopérationnel,ignoreipconfirmé (127.0.0.0/8,100.64.0.0/10,::1).203.0.113.99→ banni. 5 échecs depuis100.64.1.50(plage tailnet) → jamais banni,ignoreipfait son travail.giteaenvoyé en TCP → atterrit dans/var/log/gitea/gitea.log(jail) et dans/var/log/remote/<hostname>.log(catch-all générique de #20), sans conflit.auth-ratelimit,deny-public) et les priorités déclarées se chargent sans erreur (seules erreurs : "nonexistent certificate resolver", attendu —letsencryptn'est pas configuré dans le harnais de test minimal, il l'est dans letraefik.ymlréel du script).shellcheck proxy/install.sh: aucune nouvelle alerte (seul le SC2006 préexistant, sur du code non touché par cette PR).Idempotence
Tous les fichiers sont écrits inconditionnellement à chaque run (même pattern que les blocs fail2ban/rsyslog existants) — un rejeu réapplique simplement la version courante, sans duplication.
Note de séquencement
Rappelée dans le script : le basculement du backend Traefik rend cette config incompatible avec l'instance Gitea actuellement en production (community-scripts,
:3000). À appliquer seulement au moment de la bascule effective vers la nouvelle instance (#19), pas avant.Closes #21
Pull request closed