feat(proxy): jail fail2ban gitea + rate-limit et fermeture de /metrics #21

Closed
opened 2026-07-30 15:44:50 +00:00 by Damien · 0 comments
Owner

Objectif

Regrouper tout ce qui est spécifique à Gitea côté proxy : routage rsyslog, jail fail2ban (couche 3) et réécriture de la configuration dynamique Traefik (couche 2 + fermeture de /metrics).

⚠️ Prérequis bloquant

Ne pas 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.

Si Gitea logue l'IP tailnet du proxy, le jail bannira le proxy lui-même au bout de 5 échecs et gitea.arnodo.fr deviendra intégralement inaccessible.

1. Routage rsyslog

/etc/rsyslog.d/50-gitea.conf — routage par tag vers /var/log/gitea/gitea.log, suivant le pattern posé en #20. Un seul fichier, aucune modification du listener générique.

2. Filet de sécurité fail2ban

/etc/fail2ban/jail.local :

[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 100.64.0.0/10

Garantit qu'une erreur de configuration XFF ne peut pas bannir le tailnet. À poser avant le jail lui-même.

3. Filtre et jail

/etc/fail2ban/filter.d/gitea.conf — filtre officiel Gitea :

[Definition]
failregex = .*(Failed authentication attempt|invalid credentials|Attempted access of unknown user).* from <HOST>
ignoreregex =

/etc/fail2ban/jail.d/gitea.conf :

[gitea-auth]
enabled  = true
filter   = gitea
logpath  = /var/log/gitea/gitea.log
maxretry = 5
findtime = 10m
bantime  = 1h
action   = iptables-multiport[name=gitea, port="80,443", protocol=tcp]

Rappel du besoin : Gitea renvoie un HTTP 200 sur un échec de login web (la page est simplement re-rendue avec l'erreur). Le jail traefik-auth existant, qui matche sur DownstreamStatus, est donc structurellement aveugle au brute force sur le formulaire de login. D'où ce jail distinct, basé sur le log applicatif.

Le fail2ban-prometheus-exporter déjà en place remontera gitea-auth sans aucune modification.

4. Réécriture de conf.d/gitea.yml

Trois routers avec priorités explicites, plus le basculement du backend.

http:
  routers:
    gitea-metrics-deny:
      rule: "Host(`gitea.arnodo.fr`) && PathPrefix(`/metrics`)"
      priority: 200
      entryPoints: [websecure]
      service: gitea
      middlewares: [deny-public]
      tls: { certResolver: letsencrypt }

    gitea-auth:
      rule: "Host(`gitea.arnodo.fr`) && (Path(`/user/login`) || Path(`/user/sign_up`) || Path(`/user/forgot_password`))"
      priority: 100
      entryPoints: [websecure]
      service: gitea
      middlewares: [auth-ratelimit]
      tls: { certResolver: letsencrypt }

    gitea:
      rule: "Host(`gitea.arnodo.fr`)"
      priority: 1
      entryPoints: [websecure]
      service: gitea
      tls: { certResolver: letsencrypt }

  middlewares:
    auth-ratelimit:
      rateLimit:
        average: 6
        period: 1m
        burst: 12
    deny-public:
      ipAllowList:
        sourceRange: ["127.0.0.1/32"]

  services:
    gitea:
      loadBalancer:
        servers:
          - url: "https://gitea.taila5ad8.ts.net"

Points d'attention :

  • ipAllowList et non ipWhiteList — le stack tourne sur traefik:v3.
  • sourceCriterion du rate-limit laissé au défaut (remoteAddr) : Traefik est le bord, il voit directement l'IP client.
  • Le backend passe de http://gitea.taila5ad8.ts.net:3000 à https://gitea.taila5ad8.ts.net (443, sans port), conséquence du tailscale serve mis en place en #19.
  • /metrics reste joignable sur le tailnet ; seul l'accès public est fermé.

Note de séquencement

La migration des données étant hors périmètre du milestone, le basculement du backend rend cette configuration incompatible avec l'instance Gitea actuellement en production. À appliquer au moment de la bascule effective, pas avant.

Fichiers touchés

  • proxy/install.sh — blocs rsyslog service, fail2ban, écriture de conf.d/gitea.yml

Critères d'acceptation

  • Validation XFF de #19 confirmée avant activation
  • fail2ban-client status gitea-auth opérationnel
  • fail2ban-regex /var/log/gitea/gitea.log /etc/fail2ban/filter.d/gitea.conf matche les échecs réels
  • 5 logins ratés depuis une IP externe → ban effectif sur 80/443
  • Une IP du tailnet ne peut pas être bannie (vérification de ignoreip)
  • https://gitea.arnodo.fr/metrics renvoie 403 depuis Internet
  • https://gitea.<tailnet>.ts.net/metrics répond avec le bearer token
  • Le 7ᵉ POST sur /user/login en une minute est throttlé
  • git clone HTTPS non affecté par le rate-limit ni par le jail
  • Rejeu du script : idempotent, aucune duplication
  • Le jail apparaît dans les métriques du fail2ban-exporter

Dépendances

  • Bloquée par #19 (validation XFF, tailscale serve)
  • Bloquée par #20 (listener rsyslog)
## Objectif Regrouper tout ce qui est spécifique à Gitea côté proxy : routage rsyslog, jail fail2ban (couche 3) et réécriture de la configuration dynamique Traefik (couche 2 + fermeture de `/metrics`). ## ⚠️ Prérequis bloquant **Ne pas 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.** Si Gitea logue l'IP tailnet du proxy, le jail bannira le proxy lui-même au bout de 5 échecs et `gitea.arnodo.fr` deviendra intégralement inaccessible. ## 1. Routage rsyslog `/etc/rsyslog.d/50-gitea.conf` — routage par tag vers `/var/log/gitea/gitea.log`, suivant le pattern posé en #20. Un seul fichier, aucune modification du listener générique. ## 2. Filet de sécurité fail2ban `/etc/fail2ban/jail.local` : ```ini [DEFAULT] ignoreip = 127.0.0.1/8 ::1 100.64.0.0/10 ``` Garantit qu'une erreur de configuration XFF ne peut pas bannir le tailnet. À poser **avant** le jail lui-même. ## 3. Filtre et jail `/etc/fail2ban/filter.d/gitea.conf` — filtre officiel Gitea : ```ini [Definition] failregex = .*(Failed authentication attempt|invalid credentials|Attempted access of unknown user).* from <HOST> ignoreregex = ``` `/etc/fail2ban/jail.d/gitea.conf` : ```ini [gitea-auth] enabled = true filter = gitea logpath = /var/log/gitea/gitea.log maxretry = 5 findtime = 10m bantime = 1h action = iptables-multiport[name=gitea, port="80,443", protocol=tcp] ``` Rappel du besoin : Gitea renvoie un **HTTP 200** sur un échec de login web (la page est simplement re-rendue avec l'erreur). Le jail `traefik-auth` existant, qui matche sur `DownstreamStatus`, est donc structurellement aveugle au brute force sur le formulaire de login. D'où ce jail distinct, basé sur le log applicatif. Le `fail2ban-prometheus-exporter` déjà en place remontera `gitea-auth` sans aucune modification. ## 4. Réécriture de `conf.d/gitea.yml` Trois routers avec priorités explicites, plus le basculement du backend. ```yaml http: routers: gitea-metrics-deny: rule: "Host(`gitea.arnodo.fr`) && PathPrefix(`/metrics`)" priority: 200 entryPoints: [websecure] service: gitea middlewares: [deny-public] tls: { certResolver: letsencrypt } gitea-auth: rule: "Host(`gitea.arnodo.fr`) && (Path(`/user/login`) || Path(`/user/sign_up`) || Path(`/user/forgot_password`))" priority: 100 entryPoints: [websecure] service: gitea middlewares: [auth-ratelimit] tls: { certResolver: letsencrypt } gitea: rule: "Host(`gitea.arnodo.fr`)" priority: 1 entryPoints: [websecure] service: gitea tls: { certResolver: letsencrypt } middlewares: auth-ratelimit: rateLimit: average: 6 period: 1m burst: 12 deny-public: ipAllowList: sourceRange: ["127.0.0.1/32"] services: gitea: loadBalancer: servers: - url: "https://gitea.taila5ad8.ts.net" ``` Points d'attention : - **`ipAllowList`** et non `ipWhiteList` — le stack tourne sur `traefik:v3`. - `sourceCriterion` du rate-limit laissé au défaut (`remoteAddr`) : Traefik est le bord, il voit directement l'IP client. - **Le backend passe de `http://gitea.taila5ad8.ts.net:3000` à `https://gitea.taila5ad8.ts.net`** (443, sans port), conséquence du `tailscale serve` mis en place en #19. - `/metrics` reste joignable sur le tailnet ; seul l'accès public est fermé. ## Note de séquencement La migration des données étant hors périmètre du milestone, le basculement du backend rend cette configuration incompatible avec l'instance Gitea actuellement en production. À appliquer au moment de la bascule effective, pas avant. ## Fichiers touchés - `proxy/install.sh` — blocs rsyslog service, fail2ban, écriture de `conf.d/gitea.yml` ## Critères d'acceptation - [ ] Validation XFF de #19 confirmée avant activation - [ ] `fail2ban-client status gitea-auth` opérationnel - [ ] `fail2ban-regex /var/log/gitea/gitea.log /etc/fail2ban/filter.d/gitea.conf` matche les échecs réels - [ ] 5 logins ratés depuis une IP externe → ban effectif sur 80/443 - [ ] Une IP du tailnet ne peut **pas** être bannie (vérification de `ignoreip`) - [ ] `https://gitea.arnodo.fr/metrics` renvoie 403 depuis Internet - [ ] `https://gitea.<tailnet>.ts.net/metrics` répond avec le bearer token - [ ] Le 7ᵉ POST sur `/user/login` en une minute est throttlé - [ ] `git clone` HTTPS non affecté par le rate-limit ni par le jail - [ ] Rejeu du script : idempotent, aucune duplication - [ ] Le jail apparaît dans les métriques du `fail2ban-exporter` ## Dépendances - **Bloquée par #19** (validation XFF, `tailscale serve`) - **Bloquée par #20** (listener rsyslog)
Damien added this to the gitea-lxc-migration milestone 2026-07-30 15:44:50 +00:00
Damien added a new dependency 2026-07-30 19:08:47 +00:00
Damien removed a dependency 2026-07-30 19:10:27 +00:00
Damien added a new dependency 2026-07-30 19:10:59 +00:00
Damien added reference feat/proxy-jail-gitea 2026-08-01 08:23:21 +00:00
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Reference: Damien/infra-scripts#21