[FIX] nixos : pare-feu ouvert, et ce qu'on appelle par son nom déclaré
NixOS active un pare-feu par défaut ; aucune des quatre autres images cloud n'en active. Le service écoutait bien et répondait en local, mais 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é : 000 après 12 s, puis 303 en 9 ms. Un système déclaratif n'a que ce qui est écrit : xmlsec1, sans lequel Odoo refuse d'installer auth_saml ; parallel et shfmt, appelés par leur nom nu ; growpart, dont l'absence faisait rendre 0 à un agrandissement qui n'agrandissait rien ; et l'agent invité, qui venait de l'image. --- EN --- NixOS enables a firewall by default; none of the four other cloud images does. The service did listen and answered locally, but 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: 000 after 12 s, then 303 in 9 ms. A declarative system has only what is written: xmlsec1, without which Odoo refuses to install auth_saml; parallel and shfmt, called by bare name; growpart, whose absence made a disk grow return 0 while growing nothing; and the guest agent, which came from the image. Assisted-by: Claude Opus 5
This commit is contained in:
parent
e0624d9aaa
commit
c1f2f8db7f
2 changed files with 168 additions and 0 deletions
|
|
@ -146,6 +146,24 @@
|
||||||
wget
|
wget
|
||||||
unzip
|
unzip
|
||||||
wkhtmltopdf
|
wkhtmltopdf
|
||||||
|
# « xmlsec » pose bin/xmlsec1, que les CINQ autres plateformes posent
|
||||||
|
# aussi. Le manifeste d'auth_saml (OCA server-auth, présent dans
|
||||||
|
# l'addons_path) déclare « bin: ["xmlsec1"] », et Odoo REFUSE d'installer
|
||||||
|
# ou de mettre à niveau le module tant que le binaire n'est pas dans le
|
||||||
|
# PATH : « Unable to find 'xmlsec1' in path », en boîte de dialogue.
|
||||||
|
xmlsec
|
||||||
|
# « parallel » et « shfmt » sont appelés par leur NOM NU, l'un par
|
||||||
|
# script/database/db_drop_all.py, l'autre par script/maintenance/
|
||||||
|
# format_bash.sh. Sans parallel, « make db_drop_all » annonce des bases
|
||||||
|
# détruites qui ne l'ont pas été — une opération destructrice qui rend un
|
||||||
|
# succès qu'elle n'a pas obtenu.
|
||||||
|
parallel
|
||||||
|
shfmt
|
||||||
|
# « cloud-utils » porte growpart, qu'aucune autre voie ne fournit ici.
|
||||||
|
# L'agrandissement du disque s'écrit « sudo growpart … || true » : sans le
|
||||||
|
# binaire, il rend 0 sans rien agrandir, et la VM garde la taille de son
|
||||||
|
# image pendant que le déploiement annonce la taille demandée.
|
||||||
|
cloud-utils
|
||||||
];
|
];
|
||||||
|
|
||||||
# Le profil du système ne porte PAS « /include » : la liste par défaut de
|
# Le profil du système ne porte PAS « /include » : la liste par défaut de
|
||||||
|
|
@ -178,6 +196,51 @@
|
||||||
PKG_CONFIG_PATH = "/run/current-system/sw/lib/pkgconfig";
|
PKG_CONFIG_PATH = "/run/current-system/sw/lib/pkgconfig";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# L'agent invité, DÉCLARÉ ici plutôt que reçu de l'image.
|
||||||
|
#
|
||||||
|
# L'image épinglée l'active déjà (son configuration.nix porte la ligne), et
|
||||||
|
# c'est précisément le problème : la garantie appartient alors au tiers qui
|
||||||
|
# rebâtit l'image, pas au dépôt. Les quatre autres distributions reçoivent
|
||||||
|
# l'agent par le runcmd du déploiement, qui appelle leur gestionnaire de
|
||||||
|
# paquets — geste impossible sur un système déclaratif. Ici, c'est cette
|
||||||
|
# ligne ou rien.
|
||||||
|
#
|
||||||
|
# Sans agent, la voie Proxmox perd sa source PRIMAIRE d'adresse et retombe
|
||||||
|
# sur « ip neigh ». Déclarer l'option deux fois est sans effet : une option
|
||||||
|
# booléenne ne rompt l'évaluation que sur des valeurs DIFFÉRENTES. Hors
|
||||||
|
# QEMU, l'unité ne démarre pas — elle n'a pas de section [Install] et
|
||||||
|
# attend une règle udev sur le port virtio.
|
||||||
|
services.qemuGuest.enable = true;
|
||||||
|
|
||||||
|
# Ce que l'agent doit trouver dans son PATH.
|
||||||
|
#
|
||||||
|
# Une unité systemd n'hérite pas du profil du système : celle-ci ne porte
|
||||||
|
# que coreutils, findutils, grep, sed et systemd. Or « guest-exec » exécute
|
||||||
|
# les commandes du produit DANS ce PATH, et l'agrandissement du disque
|
||||||
|
# commence par « findmnt -no SOURCE / » — absent, donc code 127 dès la
|
||||||
|
# première ligne.
|
||||||
|
systemd.services.qemu-guest-agent.path = with pkgs; [
|
||||||
|
util-linux
|
||||||
|
e2fsprogs
|
||||||
|
cloud-utils
|
||||||
|
];
|
||||||
|
|
||||||
|
# Le port d'Odoo, OUVERT.
|
||||||
|
#
|
||||||
|
# NixOS active un pare-feu par défaut ; aucune des images cloud des quatre
|
||||||
|
# autres distributions n'en active un. Le service écoute bien sur
|
||||||
|
# 0.0.0.0:8069 et répond en local, mais l'extérieur ne reçoit RIEN — pas un
|
||||||
|
# refus, un silence, donc une attente jusqu'au délai. Ce qui sonde depuis
|
||||||
|
# l'hôte conclut « Odoo absent » sur une machine où il tourne, et le journal
|
||||||
|
# de l'installation ne porte aucune trace de la cause : elle est dans le
|
||||||
|
# pare-feu, pas dans l'application.
|
||||||
|
#
|
||||||
|
# 8069 SEUL. Le port websocket est configuré à 8072, mais Odoo ne le lie
|
||||||
|
# qu'en mode multi-processus, que cette configuration n'emploie pas : rien
|
||||||
|
# n'y écoute, et l'ouvrir donnerait un port béant sans service derrière.
|
||||||
|
# PostgreSQL n'écoute déjà que sur la boucle locale et n'a rien à ouvrir.
|
||||||
|
networking.firewall.allowedTCPPorts = [ 8069 ];
|
||||||
|
|
||||||
# Le service ERPLibre, DÉCLARÉ et non écrit.
|
# Le service ERPLibre, DÉCLARÉ et non écrit.
|
||||||
#
|
#
|
||||||
# Sur toute autre distribution l'installation dépose l'unité par
|
# Sur toute autre distribution l'installation dépose l'unité par
|
||||||
|
|
|
||||||
|
|
@ -337,6 +337,111 @@ class LeServiceEstDeclare(unittest.TestCase):
|
||||||
self.assertIn(marqueur, self.script)
|
self.assertIn(marqueur, self.script)
|
||||||
|
|
||||||
|
|
||||||
|
class CeQueLeDepotAPPELLE_ParSonNomNu(unittest.TestCase):
|
||||||
|
"""Un système déclaratif n'a que ce qui est écrit.
|
||||||
|
|
||||||
|
Les quatre autres distributions posent ces outils par leur gestionnaire
|
||||||
|
de paquets ; ici, l'absence ne se voit qu'à l'usage, et tard.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.src = MODULE.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
def test_xmlsec_is_declared(self):
|
||||||
|
"""Le manifeste d'auth_saml (OCA server-auth, dans l'addons_path)
|
||||||
|
déclare « bin: ["xmlsec1"] » : Odoo REFUSE d'installer et de mettre à
|
||||||
|
niveau le module tant que le binaire n'est pas dans le PATH."""
|
||||||
|
self.assertIn("\n xmlsec\n", self.src)
|
||||||
|
|
||||||
|
def test_the_tools_called_by_bare_name_are_declared(self):
|
||||||
|
"""« parallel » et « shfmt » sont invoqués sans chemin. Sans
|
||||||
|
parallel, « make db_drop_all » annonce des bases détruites qui ne
|
||||||
|
l'ont pas été."""
|
||||||
|
for outil in ("parallel", "shfmt"):
|
||||||
|
with self.subTest(outil=outil):
|
||||||
|
self.assertIn(f"\n {outil}\n", self.src)
|
||||||
|
|
||||||
|
def test_growpart_has_a_provider(self):
|
||||||
|
"""L'agrandissement s'écrit « sudo growpart … || true » : sans le
|
||||||
|
binaire il rend 0 sans rien agrandir, et la VM garde la taille de son
|
||||||
|
image pendant que le déploiement annonce celle demandée."""
|
||||||
|
self.assertIn("\n cloud-utils\n", self.src)
|
||||||
|
|
||||||
|
|
||||||
|
class LAgentInviteVientDuDepot(unittest.TestCase):
|
||||||
|
"""L'image épinglée active déjà l'agent — et c'est le problème : la
|
||||||
|
garantie appartient alors au tiers qui la rebâtit.
|
||||||
|
|
||||||
|
Les quatre autres distributions le reçoivent du runcmd, qui appelle leur
|
||||||
|
gestionnaire de paquets. Sur un système déclaratif, c'est cette ligne ou
|
||||||
|
rien.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.src = MODULE.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
def test_the_agent_is_declared(self):
|
||||||
|
self.assertIn("services.qemuGuest.enable = true;", self.src)
|
||||||
|
|
||||||
|
def test_its_path_carries_what_guest_exec_runs(self):
|
||||||
|
"""Une unité systemd n'hérite pas du profil du système. « guest-exec »
|
||||||
|
exécute les commandes du produit DANS ce PATH, et l'agrandissement
|
||||||
|
commence par « findmnt -no SOURCE / » : absent, code 127 dès la
|
||||||
|
première ligne."""
|
||||||
|
i = self.src.index("systemd.services.qemu-guest-agent.path")
|
||||||
|
bloc = self.src[i : self.src.index("];", i)]
|
||||||
|
for paquet in ("util-linux", "e2fsprogs", "cloud-utils"):
|
||||||
|
with self.subTest(paquet=paquet):
|
||||||
|
self.assertIn(paquet, bloc)
|
||||||
|
|
||||||
|
|
||||||
|
class LePortDOdooTraverseLePareFeu(unittest.TestCase):
|
||||||
|
"""NixOS active un pare-feu par défaut ; aucune des images cloud des
|
||||||
|
quatre autres distributions n'en active un.
|
||||||
|
|
||||||
|
Le service écoute bien sur 0.0.0.0:8069 et répond en local, mais
|
||||||
|
l'extérieur ne reçoit RIEN — pas un refus, un silence, donc une attente
|
||||||
|
jusqu'au délai. Ce qui sonde depuis l'hôte conclut « Odoo absent » sur une
|
||||||
|
machine où il tourne, et le journal de l'installation ne porte aucune
|
||||||
|
trace de la cause.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.src = MODULE.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
def _port_sonde(self):
|
||||||
|
"""Le port que l'hôte sonde VRAIMENT, lu de sa signature : une
|
||||||
|
constante recopiée ici vieillirait sans que rien ne le dise."""
|
||||||
|
import inspect
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, str(RACINE))
|
||||||
|
from script.todo.qemu_install_monitor import _port_open
|
||||||
|
|
||||||
|
return inspect.signature(_port_open).parameters["port"].default
|
||||||
|
|
||||||
|
def test_the_port_probed_from_the_host_is_the_one_opened(self):
|
||||||
|
port = self._port_sonde()
|
||||||
|
self.assertIn(
|
||||||
|
f"networking.firewall.allowedTCPPorts = [ {port} ];", self.src
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_the_firewall_is_not_simply_turned_off(self):
|
||||||
|
"""Le couper ouvrirait TOUT. PostgreSQL n'écoute aujourd'hui que sur
|
||||||
|
la boucle locale, mais un module qui désactive le pare-feu ne le
|
||||||
|
protégerait plus le jour où cela changerait."""
|
||||||
|
self.assertNotIn("networking.firewall.enable = false", self.src)
|
||||||
|
|
||||||
|
def test_only_what_serves_is_opened(self):
|
||||||
|
"""Odoo ne lie le port websocket qu'en mode multi-processus, que cette
|
||||||
|
configuration n'emploie pas : rien n'y écoute, et l'ouvrir donnerait
|
||||||
|
un port béant sans service derrière."""
|
||||||
|
i = self.src.index("allowedTCPPorts")
|
||||||
|
ligne = self.src[i : self.src.index("\n", i)]
|
||||||
|
self.assertNotIn("8072", ligne)
|
||||||
|
self.assertNotIn("5432", ligne)
|
||||||
|
|
||||||
|
|
||||||
class LeMenuNEcritPasDansEtcSurNixos(unittest.TestCase):
|
class LeMenuNEcritPasDansEtcSurNixos(unittest.TestCase):
|
||||||
def _cmd(self, prod=False):
|
def _cmd(self, prod=False):
|
||||||
import sys
|
import sys
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue