From 286328215d543c26623448729142323919c37232 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Thu, 17 Sep 2026 03:49:07 -0400 Subject: [PATCH] =?UTF-8?q?[UPD]=20changelog=20:=20le=20m=C3=A9canisme=20e?= =?UTF-8?q?ntier=20de=20l'autorit=C3=A9=20sur=20NixOS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le point annonçait qu'un fragment de service suffit à donner l'autorité du cache à NixOS. C'était vrai de ce qu'on savait en l'écrivant, et le lecteur qui s'y fie repart avec un mécanisme qui échoue. Mesuré depuis : « sudo nix store info » rend « Store URL: local » et un NIX_REMOTE vide — le nix de root ne passe pas par le démon, il télécharge lui-même, avec l'environnement que sudo lui laisse, et sudo n'en laisse aucun. Le point nomme désormais les trois frontières où l'environnement se perd, et ce qui traverse chacune. --- EN --- The entry announced that a service drop-in is enough to give NixOS the cache's authority. That was true of what was known when it was written, and a reader who relies on it leaves with a mechanism that fails. Measured since: « sudo nix store info » reports « Store URL: local » and an empty NIX_REMOTE — root's nix does not go through the daemon, it downloads on its own, with whatever environment sudo leaves it, and sudo leaves none. The entry now names the three boundaries where the environment is lost, and what crosses each. Assisted-by: Claude Opus 5 --- CHANGELOG.base.md | 4 ++-- CHANGELOG.fr.md | 2 +- CHANGELOG.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.base.md b/CHANGELOG.base.md index 723abf2..0561996 100644 --- a/CHANGELOG.base.md +++ b/CHANGELOG.base.md @@ -217,7 +217,7 @@ au [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - `make` looks for bash instead of assuming `/bin/bash`. That path does not exist on NixOS, where the shell lives in the store, and make stopped before running any recipe — including the one that installs what creates that path. Elsewhere the resolved shell is the same one as before - The locale and the keyboard a deployed VM is given now apply on Debian, where both silently failed. A locale is generated from `/etc/locale.gen` and nowhere else, so `update-locale` refused one that was not there and the VM stayed on C.UTF-8; the keyboard module ends on a `console-setup` the genericcloud image does not carry, so `/etc/default/keyboard` — the file localed and X read — is written directly instead. Every Debian deployment used to print `cloud-init: status: error`, and a word that always shows warns of nothing - A guest with no per-file trust anchor is taken out of the download cache instead of being intercepted without one. Interception is transparent and covers the whole bridge, so a VM given no authority still fails every HTTPS download on « self-signed certificate in certificate chain » — and on a declarative system placing the authority comes too late, the first rebuild being the first download. On a Proxmox host it is the HOST that is exempted: a nested guest leaves masqueraded behind it and the bridge never sees its own address. Measured from inside the guest: code 000 and SSL verification 19, then 200 and 0. Without it the package manager fell back to building 564 derivations, whose sources failed for the same reason -- NixOS learns the download cache's authority WITHOUT a rebuild, so it is no longer taken out of the cache — which used to close offline deployment to it, the store being the only source there and an exempted VM having none. It has no per-file trust anchor and /etc is generated read-only, while a declaration would come too late, the first rebuild being the first download. Measured on an intercepted VM: a READ goes through the nix client and a session variable suffices, but REALISING a derivation goes through nix-daemon, which never sees it; a drop-in under /run/systemd/system — a tmpfs, writable where /etc is not — reaches it. The bundle concatenates the system authorities with the cache's, giving the cache's alone would stop trusting everything else. Checked with upstream CUT on a fresh VM: nix-shell realises from the store +- NixOS learns the download cache's authority WITHOUT a rebuild, so it is no longer taken out of the cache — which used to close offline deployment to it, the store being the only source there and an exempted VM having none. It has no per-file trust anchor and /etc is generated read-only, while a declaration would come too late, the first rebuild being the first download. The authority is therefore POINTED AT, consumer by consumer, through the environment — and the environment is lost at three boundaries the four imperative families never meet. nix-daemon is socket-activated and sees no session: a drop-in under /run/systemd/system, a tmpfs writable where /etc is not, reaches it. sudo wipes it, and root's nix talks straight to the local store rather than the daemon, downloading on its own: `Defaults env_keep` carries the variables across, visudo being reached through the system profile since cloud-init's PATH there holds no sudo. And the ssh session opens one second before cloud-init writes the bundle, so the exports live inside the wait for it rather than at the head of the command. The bundle concatenates the system authorities with the cache's, giving the cache's alone would stop trusting everything else. Checked on a fresh VM with upstream CUT: nix-shell realises from the store; and with the cache intercepting, a complete install ends with no certificate refusal - Odoo answers from outside a NixOS VM. It listened on 0.0.0.0:8069 and replied locally, but NixOS enables a firewall by default where none of the four other cloud images does: the host received nothing — not a refusal, silence until the timeout — and the monitor declared Odoo absent on a machine where it was running. Measured from the host: 000 after 12 s, then 303 in 9 ms - ERPLibre runs as a service on NixOS. The install ended by writing a unit into `/etc/systemd/system`, generated from the store and mounted read-only: it returned 1 at its last step, after the clone, the venv and an Odoo start had all succeeded. The unit is now declared by the module; its interpreter comes from the store, `/bin` being an envfs FUSE mount that systemd does not see when it resolves the executable; and its PATH carries bash, whose absence stopped `run.sh` before Odoo - What a declarative system must declare, and the four others receive free from their cloud image: xmlsec1, without which Odoo refuses to install auth_saml, a module of the addons path; parallel and shfmt, called by bare name; growpart, absent from the whole system while the disk grow is written « … || true » and returned 0 without growing anything; and the guest agent, which came from the image rather than the repository, its unit PATH lacking findmnt so that guest-exec died with 127 on its first line @@ -271,7 +271,7 @@ au [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - `make` cherche bash au lieu de présumer `/bin/bash`. Ce chemin n'existe pas sur NixOS, où le shell vit dans le store, et make s'arrêtait avant d'exécuter la moindre recette — y compris celle qui installe de quoi créer ce chemin. Ailleurs, le shell résolu est celui d'avant - Le locale et le clavier qu'une VM déployée reçoit s'appliquent désormais sur Debian, où les deux échouaient en silence. Un locale se génère à partir de `/etc/locale.gen` et de nulle part ailleurs : `update-locale` refusait celui qui n'y était pas et la VM restait en C.UTF-8 ; le module clavier finit par un `console-setup` que l'image genericcloud ne porte pas, alors `/etc/default/keyboard` — le fichier que localed et X relisent — est écrit directement. Chaque déploiement Debian imprimait `cloud-init: status: error`, et un mot qui s'affiche toujours n'avertit plus de rien - Un invité sans ancre de confiance par fichier est soustrait au cache de téléchargement plutôt qu'intercepté sans elle. Le détournement est transparent et vaut pour tout le pont : une VM à qui l'on ne donne pas l'autorité échoue quand même sur « self-signed certificate in certificate chain » — et sur un système déclaratif, poser l'autorité arrive trop tard, la première reconstruction étant le premier téléchargement. Sur un hôte Proxmox, c'est l'HÔTE qui est excepté : un invité imbriqué sort masqué derrière lui et le pont ne voit jamais sa propre adresse. Mesuré depuis l'invité : code 000 et vérification SSL 19, puis 200 et 0. Sans cela le gestionnaire de paquets se rabattait sur 564 dérivations à construire, dont les sources échouaient pour la même raison -- NixOS apprend l'autorité du cache de téléchargement SANS reconstruire, et n'en est donc plus soustrait — ce qui lui fermait le déploiement hors ligne, le magasin étant alors la seule source et une VM exceptée n'ayant plus rien. Il n'a pas d'ancre de confiance par fichier et /etc est généré en lecture seule, quand une déclaration arriverait trop tard, la première reconstruction étant le premier téléchargement. Mesuré sur une VM interceptée : une LECTURE passe par le client nix et se contente d'une variable de session, mais RÉALISER une dérivation passe par nix-daemon, qui ne la voit pas ; un fragment sous /run/systemd/system — un tmpfs, inscriptible là où /etc ne l'est pas — l'atteint. Le faisceau concatène les autorités du système et celle du cache, donner la seconde seule ferait cesser d'approuver tout le reste. Vérifié amont COUPÉ sur une VM neuve : nix-shell réalise depuis le magasin +- NixOS apprend l'autorité du cache de téléchargement SANS reconstruire, et n'en est donc plus soustrait — ce qui lui fermait le déploiement hors ligne, le magasin étant alors la seule source et une VM exceptée n'ayant plus rien. Il n'a pas d'ancre de confiance par fichier et /etc est généré en lecture seule, quand une déclaration arriverait trop tard, la première reconstruction étant le premier téléchargement. L'autorité y est donc POINTÉE, consommateur par consommateur, par l'environnement — et l'environnement se perd à trois frontières que les quatre familles impératives ne rencontrent jamais. nix-daemon est activé par socket et ne voit aucune session : un fragment sous /run/systemd/system, un tmpfs inscriptible là où /etc ne l'est pas, l'atteint. sudo l'efface, et le nix de root parle droit au magasin local plutôt qu'au démon, téléchargeant lui-même : « Defaults env_keep » fait traverser les variables, visudo étant atteint par le profil du système, le PATH qu'y donne cloud-init ne portant aucun sudo. Et la session ssh s'ouvre une seconde avant que cloud-init n'écrive le faisceau, si bien que les exports vivent DANS l'attente de celui-ci plutôt qu'en tête de commande. Le faisceau concatène les autorités du système et celle du cache, donner la seconde seule ferait cesser d'approuver tout le reste. Vérifié amont COUPÉ sur une VM neuve : nix-shell réalise depuis le magasin ; et cache interceptant, une installation complète se termine sans un refus de certificat - Odoo répond depuis l'extérieur d'une VM NixOS. Il écoutait sur 0.0.0.0:8069 et répondait en local, mais NixOS active un pare-feu par défaut là où aucune des quatre autres images cloud n'en active : l'hôte ne recevait rien — pas un refus, un silence jusqu'au délai — et le suivi déclarait Odoo absent sur une machine où il tournait. Mesuré depuis l'hôte : 000 après 12 s, puis 303 en 9 ms - ERPLibre tourne comme service sur NixOS. L'installation finissait par écrire une unité dans `/etc/systemd/system`, généré depuis le store et monté en lecture seule : elle rendait 1 à sa dernière étape, après que le clone, le venv et un démarrage d'Odoo avaient tous réussi. L'unité est désormais déclarée par le module ; son interpréteur vient du store, `/bin` étant un montage FUSE d'envfs que systemd ne voit pas quand il résout l'exécutable ; et son PATH porte bash, dont l'absence arrêtait `run.sh` avant Odoo - Ce qu'un système déclaratif doit déclarer, et que les quatre autres reçoivent gratuitement de leur image cloud : xmlsec1, sans lequel Odoo refuse d'installer auth_saml, module du chemin des addons ; parallel et shfmt, appelés par leur nom nu ; growpart, absent de tout le système alors que l'agrandissement du disque s'écrit « … || true » et rendait 0 sans rien agrandir ; et l'agent invité, qui venait de l'image et non du dépôt, le PATH de son unité manquant findmnt si bien que guest-exec mourait en 127 dès sa première ligne diff --git a/CHANGELOG.fr.md b/CHANGELOG.fr.md index 92dbe33..a03a037 100644 --- a/CHANGELOG.fr.md +++ b/CHANGELOG.fr.md @@ -121,7 +121,7 @@ au [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - `make` cherche bash au lieu de présumer `/bin/bash`. Ce chemin n'existe pas sur NixOS, où le shell vit dans le store, et make s'arrêtait avant d'exécuter la moindre recette — y compris celle qui installe de quoi créer ce chemin. Ailleurs, le shell résolu est celui d'avant - Le locale et le clavier qu'une VM déployée reçoit s'appliquent désormais sur Debian, où les deux échouaient en silence. Un locale se génère à partir de `/etc/locale.gen` et de nulle part ailleurs : `update-locale` refusait celui qui n'y était pas et la VM restait en C.UTF-8 ; le module clavier finit par un `console-setup` que l'image genericcloud ne porte pas, alors `/etc/default/keyboard` — le fichier que localed et X relisent — est écrit directement. Chaque déploiement Debian imprimait `cloud-init: status: error`, et un mot qui s'affiche toujours n'avertit plus de rien - Un invité sans ancre de confiance par fichier est soustrait au cache de téléchargement plutôt qu'intercepté sans elle. Le détournement est transparent et vaut pour tout le pont : une VM à qui l'on ne donne pas l'autorité échoue quand même sur « self-signed certificate in certificate chain » — et sur un système déclaratif, poser l'autorité arrive trop tard, la première reconstruction étant le premier téléchargement. Sur un hôte Proxmox, c'est l'HÔTE qui est excepté : un invité imbriqué sort masqué derrière lui et le pont ne voit jamais sa propre adresse. Mesuré depuis l'invité : code 000 et vérification SSL 19, puis 200 et 0. Sans cela le gestionnaire de paquets se rabattait sur 564 dérivations à construire, dont les sources échouaient pour la même raison -- NixOS apprend l'autorité du cache de téléchargement SANS reconstruire, et n'en est donc plus soustrait — ce qui lui fermait le déploiement hors ligne, le magasin étant alors la seule source et une VM exceptée n'ayant plus rien. Il n'a pas d'ancre de confiance par fichier et /etc est généré en lecture seule, quand une déclaration arriverait trop tard, la première reconstruction étant le premier téléchargement. Mesuré sur une VM interceptée : une LECTURE passe par le client nix et se contente d'une variable de session, mais RÉALISER une dérivation passe par nix-daemon, qui ne la voit pas ; un fragment sous /run/systemd/system — un tmpfs, inscriptible là où /etc ne l'est pas — l'atteint. Le faisceau concatène les autorités du système et celle du cache, donner la seconde seule ferait cesser d'approuver tout le reste. Vérifié amont COUPÉ sur une VM neuve : nix-shell réalise depuis le magasin +- NixOS apprend l'autorité du cache de téléchargement SANS reconstruire, et n'en est donc plus soustrait — ce qui lui fermait le déploiement hors ligne, le magasin étant alors la seule source et une VM exceptée n'ayant plus rien. Il n'a pas d'ancre de confiance par fichier et /etc est généré en lecture seule, quand une déclaration arriverait trop tard, la première reconstruction étant le premier téléchargement. L'autorité y est donc POINTÉE, consommateur par consommateur, par l'environnement — et l'environnement se perd à trois frontières que les quatre familles impératives ne rencontrent jamais. nix-daemon est activé par socket et ne voit aucune session : un fragment sous /run/systemd/system, un tmpfs inscriptible là où /etc ne l'est pas, l'atteint. sudo l'efface, et le nix de root parle droit au magasin local plutôt qu'au démon, téléchargeant lui-même : « Defaults env_keep » fait traverser les variables, visudo étant atteint par le profil du système, le PATH qu'y donne cloud-init ne portant aucun sudo. Et la session ssh s'ouvre une seconde avant que cloud-init n'écrive le faisceau, si bien que les exports vivent DANS l'attente de celui-ci plutôt qu'en tête de commande. Le faisceau concatène les autorités du système et celle du cache, donner la seconde seule ferait cesser d'approuver tout le reste. Vérifié amont COUPÉ sur une VM neuve : nix-shell réalise depuis le magasin ; et cache interceptant, une installation complète se termine sans un refus de certificat - Odoo répond depuis l'extérieur d'une VM NixOS. Il écoutait sur 0.0.0.0:8069 et répondait en local, mais NixOS active un pare-feu par défaut là où aucune des quatre autres images cloud n'en active : l'hôte ne recevait rien — pas un refus, un silence jusqu'au délai — et le suivi déclarait Odoo absent sur une machine où il tournait. Mesuré depuis l'hôte : 000 après 12 s, puis 303 en 9 ms - ERPLibre tourne comme service sur NixOS. L'installation finissait par écrire une unité dans `/etc/systemd/system`, généré depuis le store et monté en lecture seule : elle rendait 1 à sa dernière étape, après que le clone, le venv et un démarrage d'Odoo avaient tous réussi. L'unité est désormais déclarée par le module ; son interpréteur vient du store, `/bin` étant un montage FUSE d'envfs que systemd ne voit pas quand il résout l'exécutable ; et son PATH porte bash, dont l'absence arrêtait `run.sh` avant Odoo - Ce qu'un système déclaratif doit déclarer, et que les quatre autres reçoivent gratuitement de leur image cloud : xmlsec1, sans lequel Odoo refuse d'installer auth_saml, module du chemin des addons ; parallel et shfmt, appelés par leur nom nu ; growpart, absent de tout le système alors que l'agrandissement du disque s'écrit « … || true » et rendait 0 sans rien agrandir ; et l'agent invité, qui venait de l'image et non du dépôt, le PATH de son unité manquant findmnt si bien que guest-exec mourait en 127 dès sa première ligne diff --git a/CHANGELOG.md b/CHANGELOG.md index c32eb8b..55fe4b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -121,7 +121,7 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - `make` looks for bash instead of assuming `/bin/bash`. That path does not exist on NixOS, where the shell lives in the store, and make stopped before running any recipe — including the one that installs what creates that path. Elsewhere the resolved shell is the same one as before - The locale and the keyboard a deployed VM is given now apply on Debian, where both silently failed. A locale is generated from `/etc/locale.gen` and nowhere else, so `update-locale` refused one that was not there and the VM stayed on C.UTF-8; the keyboard module ends on a `console-setup` the genericcloud image does not carry, so `/etc/default/keyboard` — the file localed and X read — is written directly instead. Every Debian deployment used to print `cloud-init: status: error`, and a word that always shows warns of nothing - A guest with no per-file trust anchor is taken out of the download cache instead of being intercepted without one. Interception is transparent and covers the whole bridge, so a VM given no authority still fails every HTTPS download on « self-signed certificate in certificate chain » — and on a declarative system placing the authority comes too late, the first rebuild being the first download. On a Proxmox host it is the HOST that is exempted: a nested guest leaves masqueraded behind it and the bridge never sees its own address. Measured from inside the guest: code 000 and SSL verification 19, then 200 and 0. Without it the package manager fell back to building 564 derivations, whose sources failed for the same reason -- NixOS learns the download cache's authority WITHOUT a rebuild, so it is no longer taken out of the cache — which used to close offline deployment to it, the store being the only source there and an exempted VM having none. It has no per-file trust anchor and /etc is generated read-only, while a declaration would come too late, the first rebuild being the first download. Measured on an intercepted VM: a READ goes through the nix client and a session variable suffices, but REALISING a derivation goes through nix-daemon, which never sees it; a drop-in under /run/systemd/system — a tmpfs, writable where /etc is not — reaches it. The bundle concatenates the system authorities with the cache's, giving the cache's alone would stop trusting everything else. Checked with upstream CUT on a fresh VM: nix-shell realises from the store +- NixOS learns the download cache's authority WITHOUT a rebuild, so it is no longer taken out of the cache — which used to close offline deployment to it, the store being the only source there and an exempted VM having none. It has no per-file trust anchor and /etc is generated read-only, while a declaration would come too late, the first rebuild being the first download. The authority is therefore POINTED AT, consumer by consumer, through the environment — and the environment is lost at three boundaries the four imperative families never meet. nix-daemon is socket-activated and sees no session: a drop-in under /run/systemd/system, a tmpfs writable where /etc is not, reaches it. sudo wipes it, and root's nix talks straight to the local store rather than the daemon, downloading on its own: `Defaults env_keep` carries the variables across, visudo being reached through the system profile since cloud-init's PATH there holds no sudo. And the ssh session opens one second before cloud-init writes the bundle, so the exports live inside the wait for it rather than at the head of the command. The bundle concatenates the system authorities with the cache's, giving the cache's alone would stop trusting everything else. Checked on a fresh VM with upstream CUT: nix-shell realises from the store; and with the cache intercepting, a complete install ends with no certificate refusal - Odoo answers from outside a NixOS VM. It listened on 0.0.0.0:8069 and replied locally, but NixOS enables a firewall by default where none of the four other cloud images does: the host received nothing — not a refusal, silence until the timeout — and the monitor declared Odoo absent on a machine where it was running. Measured from the host: 000 after 12 s, then 303 in 9 ms - ERPLibre runs as a service on NixOS. The install ended by writing a unit into `/etc/systemd/system`, generated from the store and mounted read-only: it returned 1 at its last step, after the clone, the venv and an Odoo start had all succeeded. The unit is now declared by the module; its interpreter comes from the store, `/bin` being an envfs FUSE mount that systemd does not see when it resolves the executable; and its PATH carries bash, whose absence stopped `run.sh` before Odoo - What a declarative system must declare, and the four others receive free from their cloud image: xmlsec1, without which Odoo refuses to install auth_saml, a module of the addons path; parallel and shfmt, called by bare name; growpart, absent from the whole system while the disk grow is written « … || true » and returned 0 without growing anything; and the guest agent, which came from the image rather than the repository, its unit PATH lacking findmnt so that guest-exec died with 127 on its first line