infra : le socket Docker ne fuit plus dans les conteneurs de tâche (#103) #189

Merged
lenaic merged 1 commit from gabriel/103-retirer-socket-taches into develop 2026-09-08 09:56:18 +00:00
Member

Ferme #103.

container.docker_host portait le chemin explicite du socket Docker de l'hôte. Avec forgejo-runner, cette forme partage ce même socket avec chaque conteneur de tâche, sans qu'aucun workflow ne l'ait déclaré ni qu'un valid_volumes vide ne s'y oppose — n'importe quelle poussée disposait donc d'un accès root sur le serveur.

  • infra/compose/forge/runner-config.yml : docker_host: "-". Le runner garde son propre accès (nécessaire pour créer ses conteneurs de tâche) mais ne le repartage plus.
  • infra/ansible/roles/ci_runner/tasks/main.yml : garde-fou ajouté à l'assertion de conformité pour empêcher toute régression silencieuse sur ce champ.
  • docs/FORGE.md §5 : corrige la portée du risque, qui concernait les tâches et pas seulement l'exécuteur, et référence désormais ce fichier plutôt que docs/SECURITE-forge.md (inexistant, deux mentions mortes corrigées au passage dans docker-compose.yml).
  • .forgejo/workflows/verif-socket-docker.yml : sonde manuelle (workflow_dispatch), qui installe le client Docker et attend que docker version échoue dans la tâche. Un échec de cette étape est le résultat attendu.

Vérifié en local : bancs test-runner-reproductible.sh, test-hygiene-workflows.sh, test-liens-markdown.sh et test-verifier-images.sh, tous verts ; test-role-app.yml rejoué sans régression.

Reste à faire, hors dépôt : appliquer sur le serveur (ansible-playbook site.yml --tags ci, hors chaîne en cours), puis déclencher le workflow d'essai et une exécution normale de la chaîne pour les deux journaux de preuve demandés par le ticket.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NECwhiHyPhse3ZiCeYnr53

Ferme #103. `container.docker_host` portait le chemin explicite du socket Docker de l'hôte. Avec forgejo-runner, cette forme partage ce même socket avec **chaque conteneur de tâche**, sans qu'aucun workflow ne l'ait déclaré ni qu'un `valid_volumes` vide ne s'y oppose — n'importe quelle poussée disposait donc d'un accès root sur le serveur. - `infra/compose/forge/runner-config.yml` : `docker_host: "-"`. Le runner garde son propre accès (nécessaire pour créer ses conteneurs de tâche) mais ne le repartage plus. - `infra/ansible/roles/ci_runner/tasks/main.yml` : garde-fou ajouté à l'assertion de conformité pour empêcher toute régression silencieuse sur ce champ. - `docs/FORGE.md` §5 : corrige la portée du risque, qui concernait les tâches et pas seulement l'exécuteur, et référence désormais ce fichier plutôt que `docs/SECURITE-forge.md` (inexistant, deux mentions mortes corrigées au passage dans `docker-compose.yml`). - `.forgejo/workflows/verif-socket-docker.yml` : sonde manuelle (`workflow_dispatch`), qui installe le client Docker et attend que `docker version` échoue dans la tâche. Un échec de cette étape est le résultat attendu. **Vérifié en local** : bancs `test-runner-reproductible.sh`, `test-hygiene-workflows.sh`, `test-liens-markdown.sh` et `test-verifier-images.sh`, tous verts ; `test-role-app.yml` rejoué sans régression. **Reste à faire, hors dépôt** : appliquer sur le serveur (`ansible-playbook site.yml --tags ci`, hors chaîne en cours), puis déclencher le workflow d'essai et une exécution normale de la chaîne pour les deux journaux de preuve demandés par le ticket. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01NECwhiHyPhse3ZiCeYnr53
infra: le socket Docker ne fuit plus dans les conteneurs de tâche (#103)
All checks were successful
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 43s
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 8s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 19s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 1m24s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 5m33s
3bde1cdffc
container.docker_host portait le chemin explicite du socket, ce qui fait
partager ce même socket par forgejo-runner avec chaque conteneur de tâche,
sans qu'aucun workflow ne l'ait demandé — accès root sur le serveur depuis
n'importe quelle poussée. La valeur "-" garde l'accès du runner lui-même,
nécessaire pour créer ses conteneurs, sans le repartager.

- runner-config.yml : docker_host à "-"
- rôle ci_runner : garde-fou Ansible sur cette valeur
- docs/FORGE.md : le risque concernait les tâches, pas seulement le runner
- workflow d'essai manuel (verif-socket-docker.yml) pour le prouver

Reste à appliquer sur le serveur : ansible-playbook site.yml --tags ci.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NECwhiHyPhse3ZiCeYnr53
lenaic approved these changes 2026-09-08 09:55:47 +00:00
lenaic left a comment

Approuvée. Le changement est juste et la faille est réelle : le serveur tourne encore avec la valeur vulnérable, vérifié à l'instant.

g2-forge-runner:/data/config.yml
  25:  docker_host: "unix:///var/run/docker.sock"

Ce que j'ai vérifié plutôt que déduit

Aucun job n'a besoin du socket dans son conteneur. C'était le seul risque de régression, et je m'attendais à en trouver un. Le seul docker compose des workflows est dans deploy.yml :

-a "chdir=/opt/enervision/.repo/infra/compose/$chemin docker compose ps"

Il passe par Ansible sur la cible, en SSH, donc hors du conteneur de tâche. Le reste de ci.yml ne fait que du git, du node et du sh. Rien ne casse.

Le garde-fou dans l'assertion du rôle ci_runner est au bon endroit, et son fail_msg dit la conséquence et pas seulement la règle. Le workflow de sonde en workflow_dispatch est une bonne idée : un contrôle dont le succès est l'échec de docker version se lit sans commentaire.

Une chose à ne pas oublier au moment d'appliquer

--tags ci n'est jamais joué par le déploiement continu, et site.yml explique pourquoi en capitales : la chaîne tourne sur cet exécuteur, elle ne doit pas pouvoir le redémarrer au milieu de son propre job. C'est donc un geste manuel, hors chaîne en cours, comme tu l'écris. Je le mentionne parce que c'est exactement le genre de détail qui fait qu'une correction de sécurité reste six jours dans develop sans être en service.

Je n'ai pas pu éprouver l'effet sur une tâche vivante, il n'y en avait aucune en cours au moment où j'ai regardé. Ta sonde le fera mieux que moi.

Un point qui n'est pas technique

Le corps de cette demande se termine par « Generated with Claude Code » et un lien de session. C'est la seule des cinquante dernières demandes du dépôt dans ce cas.

Sur une demande de sécurité, dans un dépôt qu'un jury va lire, ça se retire. Je ne le fais pas à ta place, c'est ton texte.

Approuvée. Le changement est juste et la faille est réelle : le serveur tourne encore avec la valeur vulnérable, vérifié à l'instant. ``` g2-forge-runner:/data/config.yml 25: docker_host: "unix:///var/run/docker.sock" ``` ## Ce que j'ai vérifié plutôt que déduit **Aucun job n'a besoin du socket dans son conteneur.** C'était le seul risque de régression, et je m'attendais à en trouver un. Le seul `docker compose` des workflows est dans `deploy.yml` : ``` -a "chdir=/opt/enervision/.repo/infra/compose/$chemin docker compose ps" ``` Il passe par Ansible sur la cible, en SSH, donc hors du conteneur de tâche. Le reste de `ci.yml` ne fait que du git, du node et du sh. Rien ne casse. Le garde-fou dans l'assertion du rôle `ci_runner` est au bon endroit, et son `fail_msg` dit la conséquence et pas seulement la règle. Le workflow de sonde en `workflow_dispatch` est une bonne idée : un contrôle dont le succès est l'échec de `docker version` se lit sans commentaire. ## Une chose à ne pas oublier au moment d'appliquer `--tags ci` n'est **jamais** joué par le déploiement continu, et `site.yml` explique pourquoi en capitales : la chaîne tourne sur cet exécuteur, elle ne doit pas pouvoir le redémarrer au milieu de son propre job. C'est donc un geste manuel, hors chaîne en cours, comme tu l'écris. Je le mentionne parce que c'est exactement le genre de détail qui fait qu'une correction de sécurité reste six jours dans `develop` sans être en service. Je n'ai pas pu éprouver l'effet sur une tâche vivante, il n'y en avait aucune en cours au moment où j'ai regardé. Ta sonde le fera mieux que moi. ## Un point qui n'est pas technique Le corps de cette demande se termine par « Generated with Claude Code » et un lien de session. C'est la seule des cinquante dernières demandes du dépôt dans ce cas. Sur une demande de **sécurité**, dans un dépôt qu'un jury va lire, ça se retire. Je ne le fais pas à ta place, c'est ton texte.
lenaic merged commit 73635befce into develop 2026-09-08 09:56:18 +00:00
lenaic deleted branch gabriel/103-retirer-socket-taches 2026-09-08 09:56:18 +00:00
olivier approved these changes 2026-09-08 09:57:07 +00:00
olivier left a comment

Relu, et le fond est juste — approuvé

Ce que j'ai vérifié moi-même, pas seulement relu

La sémantique de docker_host est la bonne, et c'est celle d'amont. Vérifié sur l'image effectivement déployée, forgejo-runner v6.4.0, en lui faisant cracher son propre generate-config :

# If "-" or "", an available docker host will automatically be found.
# If "automount", an available docker host will automatically be found and mounted in the job container (e.g. /var/run/docker.sock).
# Otherwise the specified docker host will be used and an error will be returned if it doesn't work.
docker_host: "-"

"-" est littéralement la valeur par défaut d'amont : le runner trouve son démon, les conteneurs de tâche n'ont rien. La correction va dans le bon sens et ne prive pas l'exécuteur de ce dont il a besoin.

Aucune tâche de la chaîne ne dépend du socket. Les quatre jobs (python, dashboard, repo, meta) n'appellent jamais le client Docker, et le docker compose ps de deploy.yml s'exécute sur la cible à travers Ansible en SSH, pas dans le conteneur de tâche. Le risque nommé au ticket (« une tâche qui en dépendrait sans qu'on le sache ») est bien nul.

Le label ubuntu-24.04 de la sonde est réellement enregistré. Il ne vit pas dans le fichier versionné mais dans .runner sur le serveur, et aucun workflow existant ne s'en servait — donc ça méritait d'être constaté plutôt que supposé. Le .runner déployé porte bien ubuntu-24.04:docker://ubuntu:24.04. La sonde sera prise, elle ne restera pas en attente.

Fusionner ne change rien en production, et le retour arrière tient bien en une ligne. La configuration en service porte encore unix:///var/run/docker.sock, et deploy.yml ne joue pas l'étiquette ci — le banc test-runner-reproductible.sh le surveille même explicitement. Comparé le fichier en service au fichier de référence du clone : le seul écart fonctionnel est cette ligne, tout le reste n'est que commentaire. Le --tags ci restant à jouer n'emporte donc pas d'effet de bord caché.

Bancs et chaîne. Les quatre bancs rejoués en local sur la branche, verts (test-hygiene-workflows.sh prend d'ailleurs déjà le nouveau workflow en charge tout seul). Les 5 jobs de la chaîne verts sur 3bde1cd. Plus aucune référence à docs/SECURITE-forge.md nulle part. La huitième condition du rôle porte bien son is defined, donc le contrôle « les conditions tolèrent une clé absente » continue de passer sans qu'il ait fallu y toucher.


Deux remarques, aucune bloquante

1. Le commentaire d'en-tête de la sonde dit l'inverse du script. . forgejo/workflows/verif-socket-docker.yml, lignes 8-9 :

« Il échoue quand la protection est en place : ce n'est pas une panne, c'est la preuve attendue. »

Or le script fait le contraire, et il fait bien : le job réussit quand docker version échoue, et échoue (exit 1) quand Docker répond. C'est la bonne convention — mais le commentaire, lui, invite à lire un rouge comme une preuve de succès. Ça compte parce que le livrable du ticket est précisément les deux journaux de preuve : en relisant cet en-tête au moment de commenter le run, on risque d'étiqueter le résultat à l'envers. La description de la demande porte la même ambiguïté (« Un échec de cette étape est le résultat attendu »). Deux lignes de commentaire à retourner.

2. Rien dans la chaîne ne rattrape une régression de docker_host. L'assertion ajoutée au rôle est la bonne idée, mais elle lit la configuration depuis le clone sur la cible (slurp de ci_runner_config_src) : elle ne se déclenche donc qu'au moment d'un --tags ci. Et infra-ansible.yml ne joue que yamllint, --syntax-check, ansible-lint et test-role-app.yml — qui couvre le rôle app, pas ci_runner. Conséquence : un retour à unix:///var/run/docker.sock dans runner-config.yml passe la chaîne au vert, et ne se voit qu'au prochain déploiement de l'étiquette ci.

Or tests/ci/test-runner-reproductible.sh interroge déjà ce même fichier pour network, force_pull, cache.enabled et capacity. Un cas de plus au même endroit ferme la boucle en quatre lignes, et le banc est déjà joué par ci.yml — sur une propriété de sécurité qui vaut root sur la machine, ça me paraît le bon endroit pour l'attraper.


Je fusionne : rien de tout ça ne justifie de retenir la correction, qui referme un accès root sur simple poussée. Les deux points ci-dessus se traitent en un commit de suite, à faire pendant que tu joues --tags ci et les deux runs de preuve.

## Relu, et le fond est juste — approuvé ### Ce que j'ai vérifié moi-même, pas seulement relu **La sémantique de `docker_host` est la bonne, et c'est celle d'amont.** Vérifié sur l'image effectivement déployée, `forgejo-runner v6.4.0`, en lui faisant cracher son propre `generate-config` : ``` # If "-" or "", an available docker host will automatically be found. # If "automount", an available docker host will automatically be found and mounted in the job container (e.g. /var/run/docker.sock). # Otherwise the specified docker host will be used and an error will be returned if it doesn't work. docker_host: "-" ``` `"-"` est littéralement la valeur par défaut d'amont : le runner trouve son démon, les conteneurs de tâche n'ont rien. La correction va dans le bon sens et ne prive pas l'exécuteur de ce dont il a besoin. **Aucune tâche de la chaîne ne dépend du socket.** Les quatre jobs (`python`, `dashboard`, `repo`, `meta`) n'appellent jamais le client Docker, et le `docker compose ps` de `deploy.yml` s'exécute sur la cible à travers Ansible en SSH, pas dans le conteneur de tâche. Le risque nommé au ticket (« une tâche qui en dépendrait sans qu'on le sache ») est bien nul. **Le label `ubuntu-24.04` de la sonde est réellement enregistré.** Il ne vit pas dans le fichier versionné mais dans `.runner` sur le serveur, et aucun workflow existant ne s'en servait — donc ça méritait d'être constaté plutôt que supposé. Le `.runner` déployé porte bien `ubuntu-24.04:docker://ubuntu:24.04`. La sonde sera prise, elle ne restera pas en attente. **Fusionner ne change rien en production, et le retour arrière tient bien en une ligne.** La configuration en service porte encore `unix:///var/run/docker.sock`, et `deploy.yml` ne joue pas l'étiquette `ci` — le banc `test-runner-reproductible.sh` le surveille même explicitement. Comparé le fichier en service au fichier de référence du clone : **le seul écart fonctionnel est cette ligne**, tout le reste n'est que commentaire. Le `--tags ci` restant à jouer n'emporte donc pas d'effet de bord caché. **Bancs et chaîne.** Les quatre bancs rejoués en local sur la branche, verts (`test-hygiene-workflows.sh` prend d'ailleurs déjà le nouveau workflow en charge tout seul). Les 5 jobs de la chaîne verts sur `3bde1cd`. Plus aucune référence à `docs/SECURITE-forge.md` nulle part. La huitième condition du rôle porte bien son `is defined`, donc le contrôle « les conditions tolèrent une clé absente » continue de passer sans qu'il ait fallu y toucher. --- ### Deux remarques, aucune bloquante **1. Le commentaire d'en-tête de la sonde dit l'inverse du script.** `. forgejo/workflows/verif-socket-docker.yml`, lignes 8-9 : > « Il échoue quand la protection est en place : ce n'est pas une panne, c'est la preuve attendue. » Or le script fait le contraire, et il fait bien : le job **réussit** quand `docker version` échoue, et **échoue** (`exit 1`) quand Docker répond. C'est la bonne convention — mais le commentaire, lui, invite à lire un rouge comme une preuve de succès. Ça compte parce que le livrable du ticket est précisément *les deux journaux de preuve* : en relisant cet en-tête au moment de commenter le run, on risque d'étiqueter le résultat à l'envers. La description de la demande porte la même ambiguïté (« Un échec de cette étape est le résultat attendu »). Deux lignes de commentaire à retourner. **2. Rien dans la chaîne ne rattrape une régression de `docker_host`.** L'assertion ajoutée au rôle est la bonne idée, mais elle lit la configuration *depuis le clone sur la cible* (`slurp` de `ci_runner_config_src`) : elle ne se déclenche donc qu'au moment d'un `--tags ci`. Et `infra-ansible.yml` ne joue que yamllint, `--syntax-check`, `ansible-lint` et `test-role-app.yml` — qui couvre le rôle `app`, pas `ci_runner`. Conséquence : un retour à `unix:///var/run/docker.sock` dans `runner-config.yml` passe la chaîne au vert, et ne se voit qu'au prochain déploiement de l'étiquette `ci`. Or `tests/ci/test-runner-reproductible.sh` interroge déjà ce même fichier pour `network`, `force_pull`, `cache.enabled` et `capacity`. Un cas de plus au même endroit ferme la boucle en quatre lignes, et le banc est déjà joué par `ci.yml` — sur une propriété de sécurité qui vaut root sur la machine, ça me paraît le bon endroit pour l'attraper. --- Je fusionne : rien de tout ça ne justifie de retenir la correction, qui referme un accès root sur simple poussée. Les deux points ci-dessus se traitent en un commit de suite, à faire pendant que tu joues `--tags ci` et les deux runs de preuve.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
g2/enervision!189
No description provided.