fix(deployer) : le service peut écrire dans sa destination, et le déploiement le mesure #111
No reviewers
Labels
No labels
montee-majeure
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
foXaCe/backend!111
Loading…
Reference in a new issue
No description provided.
Delete branch "passe-20261018-064841-86b4d7bc"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Le déploiement mesurait une seule chose au sujet de son service : qu'il puisse lire ce dont
il a besoin (le sel du miroir,
FOXA-339). Il ne mesurait pas qu'il puisse écrire chez lui —et c'est exactement là qu'un défaut vivait, invisible par construction.
La mesure
/opt/nuvio-tvexchangesur le LXC 125, le 2026-10-18 :10001est un uid SANS NOM (getent passwd 10001ne rend rien), et le service tourne ennuvio-tvx(uid 997). Le dossier était donc lisible par tous et inscriptible par personned'autre que ce propriétaire inexistant. Le service grave ses sessions de la cabine dedans :
et le fichier n'existait pas du tout, au lieu d'exister vide. Le commentaire du code dit la
conséquence : « une session non gravée tombera au prochain redémarrage ». Toute session tombe
donc à la première relance du service.
Pourquoi rien ne pouvait le voir
Le script ne chowne pas la racine de la destination, et c'est une décision écrite
(
ordre_pose_droits: «./— la destination elle-même — est écarté : elle existe avant le dépôtet n'en fait pas partie »). Un propriétaire de travers sur cette racine est donc invisible au
dépôt, et y reste : aucune livraison ne le verra, aucune ne le réparera. Et la preuve résidente ne
regardait que la lecture. Sur cette cible, le déploiement répond même « déjà déployé » et ne touche
à rien — le défaut pouvait durer des semaines sans qu'aucune passe n'ait à le dire.
Même famille que le sel du miroir (
FOXA-339) : quelque chose dont le service a besoin, hors de ceque le dépôt compare.
Le correctif
ordre_ecriture_par_le_service()imprime le fragment au lieu de l'exécuter — comme la pose desdroits — pour qu'un test puisse le jouer sur une arborescence témoin :
runuser, en tant que le service, jamais sous root : une sonde jouée sous rootrend « conforme » sur un dossier que le service ne peut pas ouvrir, et c'est ce que faisaient les
sondes du miroir, jouées sous root pendant que la substitution était morte ;
test -wpuistest -xsont des appels système, pas une lectured'
ls -l— parce qu'elle se joue aussi en--repetition, qui promet qu'aucun octet ne sera écritsur le LXC ; un fichon créé puis effacé salirait la destination si l'effacement échouait, et
l'écart de contenu du dépôt suivant le compterait (leçon des sept
.pycdu 2026-08-18) ;ECRITURE-POSSIBLE,ECRITURE-REFUSEE, etECRITURE-NON-MESUREEquand
runusermanque — un contrôle non joué (code 2), jamais un échec déguisé ;jamais
-R(un-Ra mis la cabine hors service onze heures, le 2026-08-18).Les preuves
Sept tests, éprouvés par réinjection : chacun rougit sur la mutation qu'il nomme.
test_la_preuve_residente_exige_que_le_service_puisse_ecrire_dans_sa_destinationtest_l_ecriture_est_mesuree_en_tant_que_le_service_et_jamais_sous_roottest_la_sonde_d_ecriture_n_ecrit_rientest_une_destination_absente_n_est_jamais_un_feu_verttest_un_outil_de_mesure_absent_ne_se_confond_ni_avec_un_oui_ni_avec_un_nonreturn 3(non joué) confondu avec un échectest_la_preuve_residente_exige_que_le_service_puisse_ecrire_dans_sa_destination⚠ La sonde qui écrit n'est PAS attrapée par le résidu : elle efface derrière elle et l'arbre
témoin est identique après. Elle l'est par un faux
rmqui journalise et refuse — l'écho dufaux
chowndu 2026-08-18 : on mesure l'intention effective, pas l'absence de trace. C'est lapremière version de ce test qui était verte sur cette mutation ; elle a été corrigée, pas
commentée.
Cas dit non jouable plutôt que simulé : sous root,
test -wrend vrai partout. Le test sesaute avec ce motif quand
os.geteuid() == 0; les cas négatifs qui mordent sur tous les bancs(chemin absent,
runuserqui refuse, outil absent) portent la mesure.Porte
./run_tests.sh -q— 2124 passés, 14 sautés, 0 échec. Sur le même banc, la référence passe de2117 à 2124 : les sept tests de ce correctif.
verifier-parite-webui.shet le garde d'octetssont verts (aucun fichier de la cabine n'est touché ici).
Ce que cette PR ne porte pas
Les récits et les comptes de
JOURNAL.md/CLAUDE.md. La référence de porte se remesure sur lebanc en vigueur (
RG78) et l'écart se déclare ; ce n'est pas à cette PR de choisir le banc. Je lesporte à la demande, dans la même passe ou après la fusion.
Le correctif est déjà appliqué sur la cabine
chown nuvio-tvx:nuvio-tvx /opt/nuvio-tvexchange— le dossier seul, sans-R— puis l'écrituretelle que le service la fait, jouée en
nuvio-tvx:Le service n'a pas été redémarré (
NRestarts=0, démarré le 2026-10-17 à 19:13). Ce garde-ciest là pour que la prochaine fois, la livraison le dise.
Défaut mesuré le 2026-10-18 sur le LXC 125 : `/opt/nuvio-tvexchange` appartenait à l'uid `10001` — un uid SANS NOM — alors que le service tourne en `nuvio-tvx`. Mode `drwxr-xr-x` : inscriptible par le seul propriétaire, qui n'existe pas. Le service gravait ses sessions de la cabine dans `cabine_sessions.json` sans y arriver — `CABINE-SESSIONS-NON-GRAVEES PermissionError`, trois traces, dont deux à l'instant où quelqu'un ouvrait la cabine — et le fichier n'existait pas du tout, au lieu d'exister vide. Conséquence : toute session tombe à la première relance du service. Le défaut était invisible par construction : le script ne chowne PAS la racine de la destination (décision écrite dans `ordre_pose_droits` — « `./` — la destination elle-même — est écarté »), et la preuve résidente ne regardait que la LECTURE. Même famille que le sel du miroir (`FOXA-339`) : quelque chose dont le service a besoin, hors de ce que le dépôt compare. Nul n'aurait pu le voir : sur cette cible, le déploiement répond « déjà déployé » et ne touchait à rien. La preuve résidente mesure donc l'écriture, par `ordre_ecriture_par_le_service()` — une fonction qui IMPRIME le fragment, pour qu'un test puisse le jouer sur une arborescence témoin, comme le fait la pose des droits : · mesurée avec `runuser`, EN TANT QUE le service, jamais sous root : une sonde jouée sous root rend « conforme » sur un dossier que le service ne peut pas ouvrir — c'est ce que faisaient les sondes du miroir, jouées sous root pendant que la substitution était morte ; · elle n'écrit RIEN — `test -w` est un appel système, pas une lecture d'`ls -l` — parce qu'elle se joue aussi en `--repetition`, qui promet qu'aucun octet ne sera écrit sur le LXC ; un fichon créé puis effacé salirait la destination si l'effacement échouait, et l'écart de contenu du dépôt suivant le compterait (leçon des sept `.pyc` du 2026-08-18) ; · trois mots, tous positifs : `ECRITURE-POSSIBLE`, `ECRITURE-REFUSEE`, et `ECRITURE-NON-MESUREE` quand `runuser` manque — un contrôle non joué (code 2), jamais un échec de la cible ; · le refus nomme la réparation, et le piège qu'elle ne doit pas déclencher. Sept tests, éprouvés par réinjection : chacun rougit sur la mutation qu'il nomme (garde laissé dans le fichier mais non joué, sonde jouée sous root, sonde qui écrit, fragment toujours vert, outil absent rendu comme un refus). La sonde qui écrit est attrapée par un faux `rm` qui journalise et refuse — l'écho du faux `chown` du 2026-08-18. Porte : `./run_tests.sh -q` — 2124 passés, 14 sautés, 0 échec (2117 + 7). Les récits et comptes de `JOURNAL.md` / `CLAUDE.md` ne sont pas inclus : ils appartiennent à la fusion.f763d17722611dca7494Le premier client réel de ce garde : le déploiement de
84a6c3e, deux heures après l'ouverture de la PREn déployant
FOXA-2959(--cible tvexchange,BAK-FOXA-2959), la racine de la destination estrepassée à l'uid du poste qui déploie. Mesuré :
La gravure des sessions de la cabine était donc re-cassée par le déploiement lui-même :
Le mécanisme
tar -xzfs'exécute en root dans le conteneur et restaure par défaut le propriétaire inscritdans l'archive. L'archive est créée sur le poste (
tar -czf - -C "$src" .), donc./y portel'uid du poste —
1000, qui estdepotmajdans le conteneur etstephanesur ce poste.ordre_pose_droitschowne tous les chemins listés par l'archive sauf./, la destinationelle-même, qui garde donc cet uid. Personne ne le voit : les fichiers déposés, eux, sont bien à
nuvio-tvx.Ce n'est donc pas un accident de parcours : chaque déploiement de
tvexchangere-casse laracine, et le service perd le droit d'écrire dans son propre dossier jusqu'à la réparation
manuelle. La racine
10001mesurée le 2026-10-18 était le même défaut, avec l'uid d'un posteantérieur.
Ce que ce garde aurait dit, mesuré
Avec lui en place, ce déploiement aurait rendu ceci au lieu d'un VERT :
C'est le seul endroit du dépôt qui interroge le service qui tourne, et il l'aurait dit.
Réparé, et reprouvé
Aucun résidu de sonde, service non redémarré (
NRestarts=0),/healthzsert84a6c3e.Reste à trancher — et ça ne se tranche pas ici
Le garde constate ; il ne corrige pas la cause. Faire chown la racine de la destination par le
déploiement (nature
residentseulement, dossier seul) est un changement de périmètre duchown— et le périmètre de la pose des droits est exactement ce qui a coûté onze heures le2026-08-18. Ça ne se fait pas dans une passe qui n'en a pas le mandat : je le porte séparément, et
seulement avec l'accord de l'opérateur.