83 lines
4.6 KiB
Markdown
83 lines
4.6 KiB
Markdown
|
|
# ADR 0008 — WebDAV de l'élève : authentification déléguée (Nginx auth_request) + rclone
|
||
|
|
|
||
|
|
- **Date** : 2026-10-14
|
||
|
|
- **Statut** : accepté
|
||
|
|
|
||
|
|
## Contexte
|
||
|
|
|
||
|
|
Bloc C du CLAUDE.md : « dossier de l'élève montable comme lecteur réseau. »
|
||
|
|
Besoin : chaque élève a un dossier personnel, accessible en WebDAV (Windows/
|
||
|
|
macOS/Linux savent tous monter un lecteur réseau WebDAV nativement), avec les
|
||
|
|
mêmes identifiants que la connexion, et **jamais** accès au dossier d'un
|
||
|
|
autre élève.
|
||
|
|
|
||
|
|
## Décisions
|
||
|
|
|
||
|
|
**`rclone serve webdav`, pas un module WebDAV Nginx/Apache.** rclone sait
|
||
|
|
servir aussi bien un dossier local qu'un bucket S3 avec la même commande —
|
||
|
|
bascule possible plus tard vers Hetzner Object Storage sans rien changer à
|
||
|
|
l'architecture d'authentification. Empreinte mémoire très faible (~20 Mo).
|
||
|
|
|
||
|
|
**Authentification déléguée au portail via `auth_request` (Nginx), pas une
|
||
|
|
authentification propre à rclone.** rclone n'a pas de notion de comptes
|
||
|
|
multiples ; Nginx vérifie les identifiants auprès du portail (même logique
|
||
|
|
que la connexion, `Auth::guard('eleve')->once(...)`) via une sous-requête
|
||
|
|
interne (`WebdavAuthController`), puis limite la suite de la requête au seul
|
||
|
|
sous-dossier de l'élève authentifié.
|
||
|
|
|
||
|
|
**Un seul process rclone pour tous les élèves, pas un par élève.** Chaque
|
||
|
|
sous-dossier (`storage/app/webdav/<id élève>/`) est injecté dans le chemin
|
||
|
|
transmis à rclone via `proxy_pass http://127.0.0.1:.../$eleve_dossier/$chemin`
|
||
|
|
— $eleve_dossier vient de l'en-tête `X-Eleve-Dossier` posé par
|
||
|
|
`WebdavAuthController` après vérification des identifiants.
|
||
|
|
|
||
|
|
## Deux bugs trouvés en vérifiant en réel (pas en local, Nginx n'a pas de test unitaire)
|
||
|
|
|
||
|
|
**1. `rewrite` ne voit jamais la variable posée par `auth_request_set`.**
|
||
|
|
Nginx exécute les directives `rewrite` dans la phase *rewrite*, qui a lieu
|
||
|
|
**avant** la phase *access* où tourne `auth_request`/`auth_request_set` —
|
||
|
|
même écrites après dans le fichier, textuellement. Une première version
|
||
|
|
utilisait `rewrite ^/webdav/(.*)$ /$eleve_dossier/$1 break;` : `$eleve_dossier`
|
||
|
|
y était toujours vide, donc tous les fichiers de tous les élèves atterrissaient
|
||
|
|
à la racine commune du dossier WebDAV — **un élève pouvait lire le fichier
|
||
|
|
d'un autre simplement en devinant son nom.** Jamais vu en local (pas de test
|
||
|
|
automatisé qui exerce une vraie pile Nginx+rclone) : découvert en testant
|
||
|
|
avec deux comptes élèves jetables en vrai sur le serveur.
|
||
|
|
Corrigé : une `location` avec une regex à groupe nommé
|
||
|
|
(`location ~ ^/webdav/(?<chemin_webdav>.*)$`) et `proxy_pass` avec la
|
||
|
|
variable directement dans l'URL — `proxy_pass` construit sa requête dans une
|
||
|
|
phase plus tardive, où `$eleve_dossier` est déjà posée.
|
||
|
|
|
||
|
|
**2. Le cache de répertoires de rclone (`--dir-cache-time`, 5 min par
|
||
|
|
défaut) ne voit pas les dossiers créés par Laravel.** Chaque dossier élève
|
||
|
|
est créé par PHP (`Storage::disk('webdav')->makeDirectory(...)`), en dehors
|
||
|
|
de rclone — qui le découvre seulement à la prochaine actualisation de son
|
||
|
|
cache de liste. Un élève dont le dossier venait d'être créé pouvait recevoir
|
||
|
|
une 404 pendant plusieurs minutes. Corrigé par `--dir-cache-time 1s` dans le
|
||
|
|
service systemd (`infra`/`ansible/roles/portail/templates/rclone-webdav.service.j2`).
|
||
|
|
`--vfs-cache-mode off` (déjà en place, pour ne jamais garder de contenu de
|
||
|
|
fichier en cache) ne couvre pas ce cache de répertoires : deux réglages
|
||
|
|
distincts.
|
||
|
|
|
||
|
|
**3. `SCRIPT_NAME`/`REQUEST_URI` ne sont pas fiables dans une sous-requête
|
||
|
|
`auth_request` quand on construit son propre appel FastCGI.** `$request_uri`
|
||
|
|
dans la sous-requête reflétait la requête d'origine (`/webdav/…`), pas l'URI
|
||
|
|
de la sous-requête elle-même (`/interne/webdav-auth`) : Laravel routait donc
|
||
|
|
sur le mauvais chemin et répondait 404 à chaque appel. Comme cette location
|
||
|
|
a une URI toujours identique (`= /interne/webdav-auth`), la solution a été
|
||
|
|
d'écrire `SCRIPT_FILENAME`, `SCRIPT_NAME` et `REQUEST_URI` en dur dans les
|
||
|
|
paramètres FastCGI plutôt que de les déduire de variables Nginx.
|
||
|
|
|
||
|
|
## Conséquences
|
||
|
|
|
||
|
|
- Testé en réel avec trois comptes élèves jetables : upload, lecture,
|
||
|
|
PROPFIND, isolation stricte entre élèves (un 404 propre, jamais le
|
||
|
|
contenu d'un autre), dossier d'un nouvel élève utilisable immédiatement.
|
||
|
|
Comptes et fichiers de test supprimés ensuite.
|
||
|
|
- Cette classe de bug (interaction entre phases Nginx, cache rclone) n'est
|
||
|
|
détectable qu'en testant la vraie pile déployée — aucun test Pest ne
|
||
|
|
l'aurait vue, puisque les tests appellent le contrôleur Laravel
|
||
|
|
directement, sans passer par Nginx ni rclone.
|
||
|
|
- Reste en dette : pas de quota de stockage par élève, dossier local
|
||
|
|
seulement (pas encore sur Hetzner Object Storage).
|