Dossier personnel de l'élève monté comme lecteur réseau : rclone sert le dossier local, l'authentification est déléguée au portail via auth_request Nginx (mêmes identifiants que la connexion), sans nouveau système de comptes. Nettoyé par la purge RGPD comme les autres données d'élève. Corrige au passage un vrai bug d'isolation trouvé en testant avec deux comptes jetables : rewrite injectait la variable posée par auth_request_set, mais rewrite s'exécute dans une phase Nginx antérieure à cette variable, donc toujours vide - un élève pouvait lire le fichier d'un autre en devinant son nom. Corrigé par une location à regex nommée + proxy_pass. Détails : ADR 0008. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
82 lines
4.6 KiB
Markdown
82 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).
|