fix(proxy): exclure les 401 git-over-HTTPS du jail traefik #23

Closed
Damien wants to merge 2 commits from fix/proxy-jail-git-401 into main
Owner

Contexte

git clone/fetch/push en HTTPS fait systématiquement un aller-retour 401 avant d'envoyer les credentials (challenge WWW-Authenticate). Le jail traefik-auth (maxretry=10, findtime=5m sur tout 401/403/429/5xx) bannissait donc une IP légitime au bout d'une poignée d'opérations git.

Correctif

Ajout d'un ignoreregex au filtre /etc/fail2ban/filter.d/traefik.conf, limité aux 401 sur info/refs, git-upload-pack et git-receive-pack (protocole smart-HTTP). Les 401 sur toute autre route (UI, API) restent couverts par le failregex existant, inchangé.

Idempotence

Le bloc qui écrit traefik.conf était déjà inconditionnel (heredoc tee à chaque run) — l'idempotence était donc déjà acquise avant ce changement. Un rejeu du script réapplique simplement la version corrigée du filtre, sans effet de bord ni duplication.

Vérification

  • shellcheck proxy/install.sh : aucune nouvelle alerte (une alerte SC2006 préexistante en ligne 270, non liée à ce changement).
  • Heredoc déjà quoté (<< 'EOF'), la regex passe telle quelle sans expansion shell.

Closes #17

## Contexte `git clone`/`fetch`/`push` en HTTPS fait systématiquement un aller-retour 401 avant d'envoyer les credentials (challenge `WWW-Authenticate`). Le jail `traefik-auth` (maxretry=10, findtime=5m sur tout 401/403/429/5xx) bannissait donc une IP légitime au bout d'une poignée d'opérations git. ## Correctif Ajout d'un `ignoreregex` au filtre `/etc/fail2ban/filter.d/traefik.conf`, limité aux 401 sur `info/refs`, `git-upload-pack` et `git-receive-pack` (protocole smart-HTTP). Les 401 sur toute autre route (UI, API) restent couverts par le `failregex` existant, inchangé. ## Idempotence Le bloc qui écrit `traefik.conf` était déjà inconditionnel (heredoc `tee` à chaque run) — l'idempotence était donc déjà acquise avant ce changement. Un rejeu du script réapplique simplement la version corrigée du filtre, sans effet de bord ni duplication. ## Vérification - `shellcheck proxy/install.sh` : aucune nouvelle alerte (une alerte SC2006 préexistante en ligne 270, non liée à ce changement). - Heredoc déjà quoté (`<< 'EOF'`), la regex passe telle quelle sans expansion shell. Closes #17
Damien added 1 commit 2026-07-31 15:16:51 +00:00
Git smart-HTTP always fires an unauthenticated request first, gets a
401 challenge, then retries with credentials. On a private repo, ~10
git operations in 5 minutes hit maxretry and ban the legitimate
client for an hour. Excludes 401s on info/refs, git-upload-pack and
git-receive-pack while leaving other 401 sources (UI, API) covered.

Closes #17
Author
Owner

Le correctif est juste et le commentaire explique bien le pourquoi. Un point à corriger avant merge.

L'ignoreregex ne couvre qu'un seul ordre de champs, alors que le failregex juste au-dessus en couvre deux.

Le commentaire existant est explicite : « Two patterns cover both possible field orderings in the JSON. » L'ignoreregex ajouté suppose au contraire que RequestPath précède toujours DownstreamStatus :

^.*"RequestPath":"[^"]*/(info/refs|...)[^"]*".*"DownstreamStatus":401

Si Traefik sérialise dans l'autre ordre, l'exclusion ne matche pas et le ban sur git fetch revient — c'est-à-dire précisément le bug que cette PR corrige, mais silencieusement et seulement dans certaines conditions. ignoreregex accepte plusieurs lignes comme failregex, donc :

ignoreregex = ^.*"RequestPath":"[^"]*/(info/refs|git-upload-pack|git-receive-pack)[^"]*".*"DownstreamStatus":401
              ^.*"DownstreamStatus":401.*"RequestPath":"[^"]*/(info/refs|git-upload-pack|git-receive-pack)[^"]*"

Validation attendue : fail2ban-regex sur un vrai access.log montrant les 401 git en ignored, et un 401 sur /user/login toujours en matched.

Le reste est bon — heredoc quoté, écriture inconditionnelle donc idempotente, restart déjà en place.

Le correctif est juste et le commentaire explique bien le pourquoi. Un point à corriger avant merge. **L'`ignoreregex` ne couvre qu'un seul ordre de champs, alors que le `failregex` juste au-dessus en couvre deux.** Le commentaire existant est explicite : « Two patterns cover both possible field orderings in the JSON. » L'`ignoreregex` ajouté suppose au contraire que `RequestPath` précède toujours `DownstreamStatus` : ``` ^.*"RequestPath":"[^"]*/(info/refs|...)[^"]*".*"DownstreamStatus":401 ``` Si Traefik sérialise dans l'autre ordre, l'exclusion ne matche pas et le ban sur `git fetch` revient — c'est-à-dire précisément le bug que cette PR corrige, mais silencieusement et seulement dans certaines conditions. `ignoreregex` accepte plusieurs lignes comme `failregex`, donc : ```ini ignoreregex = ^.*"RequestPath":"[^"]*/(info/refs|git-upload-pack|git-receive-pack)[^"]*".*"DownstreamStatus":401 ^.*"DownstreamStatus":401.*"RequestPath":"[^"]*/(info/refs|git-upload-pack|git-receive-pack)[^"]*" ``` **Validation attendue** : `fail2ban-regex` sur un vrai `access.log` montrant les 401 git en `ignored`, et un 401 sur `/user/login` toujours en `matched`. Le reste est bon — heredoc quoté, écriture inconditionnelle donc idempotente, restart déjà en place.
Damien added 1 commit 2026-08-01 08:58:55 +00:00
L'ignoreregex n'excluait que l'ordre RequestPath puis DownstreamStatus,
alors que le failregex juste au-dessus gère explicitement les deux
ordres possibles de sérialisation JSON de Traefik. Si Traefik
sérialise dans l'autre ordre, l'exclusion ne matchait pas et le ban
sur git fetch revenait silencieusement.

Ajoute la seconde ligne (DownstreamStatus puis RequestPath), symétrique
au failregex.
Author
Owner

Corrigé (25bd5f2) : ajout de la seconde ligne ignoreregex (DownstreamStatus puis RequestPath), symétrique au failregex existant.

Vérifié avec fail2ban-regex sur un access.log synthétique couvrant les deux ordres de champs, pour les deux cas (git et non-git) :

Failregex: 2 total
|   1) [1] ^.*"ClientHost":"<HOST>".*"DownstreamStatus":(401|403|429|5[0-9]{2})
|   2) [1] ^.*"DownstreamStatus":(401|403|429|5[0-9]{2}).*"ClientHost":"<HOST>"

Ignoreregex: 4 total
|   1) [2] ^.*"RequestPath":"[^"]*/(info/refs|git-upload-pack|git-receive-pack)[^"]*".*"DownstreamStatus":401
|   2) [2] ^.*"DownstreamStatus":401.*"RequestPath":"[^"]*/(info/refs|git-upload-pack|git-receive-pack)[^"]*"

Lines: 6 lines, 4 ignored, 2 matched, 0 missed
  • Les 4 lignes 401 sur info/refs/git-upload-pack/git-receive-pack, dans les deux ordres de champs → ignored.
  • Les 2 lignes 401 sur /user/login, dans les deux ordres de champs → matched.

shellcheck proxy/install.sh : aucune nouvelle alerte (SC2006 préexistant, non lié).

Corrigé (25bd5f2) : ajout de la seconde ligne `ignoreregex` (`DownstreamStatus` puis `RequestPath`), symétrique au `failregex` existant. Vérifié avec `fail2ban-regex` sur un `access.log` synthétique couvrant les deux ordres de champs, pour les deux cas (git et non-git) : ``` Failregex: 2 total | 1) [1] ^.*"ClientHost":"<HOST>".*"DownstreamStatus":(401|403|429|5[0-9]{2}) | 2) [1] ^.*"DownstreamStatus":(401|403|429|5[0-9]{2}).*"ClientHost":"<HOST>" Ignoreregex: 4 total | 1) [2] ^.*"RequestPath":"[^"]*/(info/refs|git-upload-pack|git-receive-pack)[^"]*".*"DownstreamStatus":401 | 2) [2] ^.*"DownstreamStatus":401.*"RequestPath":"[^"]*/(info/refs|git-upload-pack|git-receive-pack)[^"]*" Lines: 6 lines, 4 ignored, 2 matched, 0 missed ``` - Les 4 lignes 401 sur `info/refs`/`git-upload-pack`/`git-receive-pack`, dans les deux ordres de champs → `ignored`. - Les 2 lignes 401 sur `/user/login`, dans les deux ordres de champs → `matched`. `shellcheck proxy/install.sh` : aucune nouvelle alerte (SC2006 préexistant, non lié).
Damien closed this pull request 2026-08-01 17:44:02 +00:00

Pull request closed

This pull request cannot be reopened because the branch was deleted.
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Damien/infra-scripts#23