serveur_backup verifiait pour tout le monde — juste tant que le depot vivait
dans l ecosysteme. Depuis qu ils deposent chez leur hebergeur, le site heberge
des octets chiffres COTE CLIENT : il ne peut ni les lire ni dire s ils valent
quelque chose. La verification revient donc au seul qui detient la cle, le noeud.
client_backup verifie SON depot distant — pas le fait d avoir lance sa
sauvegarde. Une unite verte sur un depot vide est ce qui a menti un mois.
serveur_icinga n exige plus un hote serveur_backup et se branche sur deux
modeles : depot local (services sur son hote, nommes sauvegarde: <noeud>) ou
pas de depot (services sur chaque noeud, nommes sauvegarde).
Le 404 qui n etait pas une absence : les noeuds recevaient No objects found
alors que icinga2 object list montrait le service charge. Le filtre du compte
d API ne portait que la premiere forme de nom — c est la PERMISSION qui
refusait, avec les mots d une absence.
Aussi : ingress 5665 depuis client_backup, le pair ne nommait que
serveur_backup.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
Chezlepro rangeait ses instantanes sur une VM DE SA PROPRE FLOTTE. Raser
l ecosysteme pour le reconstruire, c etait raser le filet avec.
Le site a son depot ; les neuf detenteurs d etat y deposent ; une
restitution est sortie (annuaire LDAP lisible, hors flotte).
Isolation par compte Unix, pas par convention : home 0700, cle exclusive,
racine partagee a root en 0711 (traversable, non listable). Les deux refus
constates. Le site heberge du chiffre : il ne peut ni lire ni ouvrir, d ou
la verification deplacee chez le locataire qui detient la cle.
Quatre defauts reveles par ce deuxieme usage :
- la racine des depots ne peut etre le home de personne (StrictModes rendait
Permission denied publickey pour un refus de CHEMIN)
- la racine nie le TLD internal, et harden-below-nxdomain etendait ce non a
toute la zone sans jamais interroger l autoritatif : aucun locataire ne
pouvait nommer un service du site
- le gabarit transporte des fichiers de durcissement perimes, et les machines
du site ne recoivent jamais ssh_hardening
- MaxStartups compte les connexions non authentifiees : un depot de site en
voit la somme de ses locataires
Constat non corrige : les machines du site ne sont pas durcies.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
Apres les paquets (serveur_cache_site) et le genome (serveur_forge_site), les NOMS.
Meme patron : un installateur, un marqueur.
CE QUE CA CORRIGE, mesure sur infra-pki-01, premiere machine a porter l autorite
de Chezlepro :
DNS BLOQUE un tenant resout chez lui, sa requete ne sort pas
443 sortant OK par adresse
apt via le cache OK le mandataire resout a sa place
apt s en sortait ; TOUT LE RESTE ETAIT AVEUGLE AUX NOMS. Versionner la cle de
signature Smallstep a fait passer une tache et l echec s est deplace d un cran : le
depot lui-meme devait etre joint par son nom.
POURQUOI PAS L UNBOUND DE LA FRONTIERE : il tourne sur le boitier sans etre un
service gere, et le plan du site l avait deja ecrit une fois — un processus n est
pas un service. site-dns-01 est deploye par le moteur et verifie par le harnais.
OU VIT LA DERIVATION : dans site_inventaire.py, qui connait le plan du site ET le
registre des tenants. Le role tourne sur une machine qui n a aucune raison de lire
la carte de l hebergeur — la derivation appartient a qui detient les deux sources.
PIEGE DE FILTRE, ATTRAPE EN LE BRANCHANT : _supernets_voisins() EXCLUT l instance
montee (personne n est son propre voisin). Le site sert TOUS ceux qu il heberge — y
compris celui qu on deploie. Reutilise tel quel, il privait de resolution le seul
tenant en cours de montage. On reutilise donc la DECOUVERTE partagee sans son
filtre : deux recensements divergent, deux filtres non.
Le marqueur verifie deux choses : que le resolveur ECOUTE, et que sa liste
d autorisation ADMET reellement les tenants. Sans la seconde, la panne chez le
locataire ressemblerait a un DNS mort alors que c est une ACL.
make verifier : vert. make prouver : CONFORME, 55 OK, 0 echec, 0 saute.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
MEME PATRON QUE serveur_artefacts + serveur_cache_site : un installateur, un
marqueur. Le site rend DEUX services a ses locataires pendant leur jeunesse, les
PAQUETS et le GENOME. Le premier etait declare, le second ne l etait pas — alors
que D-81 fait de cette forge l autorite dont tout ecosysteme se reproduit.
Mesure sur ops-01, premiere machine de la reconstruction de Chezlepro :
cache du SITE 10.0.33.21:3142 : OK
forge du SITE 10.0.33.11:443 : BLOQUE
Le plan du tenant declarait pourtant lire son genome a cette adresse. UNE
DEPENDANCE DECLAREE CHEZ LE CONSOMMATEUR, SANS FLUX CHEZ LE FOURNISSEUR — et rien
ne le signalait : la garde de matrice de resoudre_flux ne verifie que les paires
role->role, jamais un pair symbolique comme voisins_site.
POURQUOI UN ROLE A PART : ajouter cet ingress a serveur_forgejo aurait ouvert LA
FORGE DE CHAQUE TENANT a ses voisins. La responsabilite appartient a une machine
precise, pas au logiciel qu elle fait tourner.
Il verifie que la forge ECOUTE vraiment : sans ca il attribuerait l autorite du
genome a un port muet, et l ecosysteme venu s y reproduire attendrait sans savoir
pourquoi — ce qui est exactement arrive, quinze minutes durant.
TROIS GARDES ONT TRAVAILLE : le catalogue a refuse un role qu il ne nomme pas, la
carte a corrige ses deux chiffres, et P49 a exige la regeneration du registre.
P33 a impose partage: true — le marqueur emprunte l ecoute de serveur_forgejo.
Frontiere : 4 objets crees, 0 retire. Verifie depuis ops-01 : 443 OK.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
L'insemination avait un nom depuis ce matin ; elle n'avait pas de flux. Deux
declarations, aux deux bouts, et rien d'autre :
serveur_ops_site egress 22/tcp -> serveur_ops_tenant
serveur_ops_tenant ingress 22/tcp <- runner_site partage: true
ETROIT PAR CONSTRUCTION : il vise le GROUPE `serveur_ops_tenant`, qu'un ecosysteme
ne pose que sur une machine. Au socle, il aurait ouvert le SSH du site vers toute
la flotte du tenant.
L'en-tete disait « ce role n'entre JAMAIS chez un tenant ». Frontiere intenable :
`creer-vm` exige `_instance-requise`, et le runner du site avait deja du basculer
son symlink `instance` sur OPS-Chezlepro pour materialiser ses VM. Declarer ne cree
pas ce pouvoir — ca rend limitable un pouvoir qui s'exercait sans borne. Ce qui
reste interdit n'est pas une regle mais un FAIT : il n'a pas la voute du tenant.
LA REGLE EST EMISE D'UN SEUL COTE, et pas celui qu'on croit. Le paquet penetre le
pare-feu par la patte du SITE, pas par le transit : la regle appartient au cote
site du devis. L'emettre aussi depuis l'`ingress` du tenant aurait produit une
seconde regle sur la mauvaise interface — jamais evaluee, indiscernable d'une regle
utile. La declaration du tenant pose sa regle nftables, et elle seule :
ip saddr { 10.0.31.11 } tcp dport 22 accept
L'adresse DERIVE du plan du site. Ecrite a la main, elle aurait survecu au prochain
deplacement du runner sans bruit — le site a deja deplace ses machines le 08-25.
Plan de la frontiere : 2 objets a creer, 0 a retirer, 126 inchanges. RIEN D'APPLIQUE.
P41 APPLIQUEE AU PLAN DU SITE : `resoudre_flux` en avait besoin a son tour ; les
trois lecteurs demenagent dans `underlay` et `devis_opnsense` delegue.
DEUX GARDES ONT TRAVAILLE : P33 a refuse `ingress 22` sur un hote portant deja le
sshd du socle (reponse : `partage: true`, comme `serveur_backup`), et le devis a
refuse d'emettre vers un alias vide.
UN TEST ROUGE DEPUIS TROIS JOURS. `test_adressage_derive` construisait un site avec
un `index` — or un SITE n'en a pas depuis add94f2 (08-25), remplace par
`bande_basse_de`. Invisible parce que le geste quotidien est `make prouver`, qui ne
joue pas les tests. Remis sur le contrat actuel, avec sa contrepartie : sans
`bande_basse_de`, aucun chevauchement n'est tolere.
make verifier : vert. make prouver : CONFORME, 52 OK, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
`docs/registre-flux.md` est GENERE depuis les `roles/*/meta/flux.yml`, et c'est le
document qu'un humain lit pour savoir ce que le pare-feu laisse passer. Son
EXISTENCE etait verifiee depuis longtemps ; sa FRAICHEUR ne l'etait pas.
Il avait derive : la garde d'administration y portait encore `10.0.0.0/24` alors
que le reseau d'administration vaut `10.17.0.0/24`, deux flux `client_resolveur`
ajoutes depuis n'y figuraient pas, et un hote manquait des listes de sources. Un
lecteur y aurait lu un pare-feu qui n'existe plus.
L'inventaire avait deja sa garde — P03, le diff-vide du plan. Le registre des flux
est le meme genre d'artefact : genere, versionne, lu par un humain. Il lui
manquait la meme. `generer_registre` etant une fonction PURE, P49 la rejoue en
memoire et compare — une preuve qui repare ce qu'elle mesure ne mesure plus rien.
Controle negatif ideal, et il ne s'invente pas : la version commitee elle-meme.
Restauree, la preuve echoue ; regeneree, elle passe.
CE QUE CETTE DECOUVERTE CORRIGE AUSSI DANS MA TETE. J'avais decrit le symptome
comme « le runner salit ses propres clones » — une contradiction structurelle
entre un depot-clone et un repertoire de travail. C'etait faux, et la question de
l'exploitant l'a mis au jour. Regenerer un artefact DOIT produire un diff quand
les sources ont change ; ce qui manquait n'etait pas une architecture, c'etait une
garde. Un symptome observe depuis un seul endroit ressemble toujours a une
propriete de cet endroit.
Ce commit emporte aussi la regeneration elle-meme : le registre du moteur, et les
quatorze fichiers nftables de Chezlepro, remis en accord avec leurs sources.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Mesure avant de decider : ~21 Mo de RSS par VM pour 28 a 189 requetes servies.
Le gain de cache est negligeable ; ce qu'on recupere, c'est un demon au lieu de
cinq et un endroit a regarder au lieu de cinq.
Pas sur la frontiere : un resolveur de SITE devrait connaitre la zone interne de
chaque tenant, et un tenant dont le resolveur vit chez l'hebergeur ne peut plus
s'emanciper avec. La recursion est generique, la zone interne ne l'est pas.
L'autoritatif se replie sur 127.0.0.1:5300 -- derive de la colocalisation, pas
declare a la main. Son port devient `derive` dans meta/flux.yml : les deux lient
53 mais sur des adresses differentes.
Deux pieges. Unbound refuse d'interroger une loopback par defaut : sans lever
do-not-query-localhost, toute la zone rendait SERVFAIL. Et le plancher
/etc/hosts MASQUAIT la panne -- getent repondait, dig disait SERVFAIL. La tache
de validation du role avait raison contre moi.
client_unbound n'installant plus Unbound, son nom mentait : client_resolveur.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Icinga ne surveillait rien : aucun objet Host ni Service de Set-OPS, seulement
la config Debian d'origine sur localhost.
Superviser setops-sauvegarde.service aurait reproduit le defaut du jour meme :
l'unite etait VERTE sur onze noeuds pendant qu'elle n'emportait rien. Le noeud
sait qu'il a LANCE sa sauvegarde, pas qu'elle est ARRIVEE. backup-01 evalue donc
ses depots et pousse un resultat passif par noeud vers l'API Icinga.
Trois criteres, parce qu'un seul suffit a mentir : l'instantane existe, il est
recent (26 h / 50 h), il contient au moins un fichier.
Le sens du flux est delibere : le depot parle a la supervision, jamais l'inverse
— compromettre mon-01 ne donne aucun acces aux sauvegardes.
Le ttl de 6 h fait la fraicheur : si le rapporteur se tait, Icinga perime les
services tout seul. C'est le silence qui a laisse le defaut vivre un mois.
Deux erreurs corrigees par la mesure :
- --data-urlencode refuse en Bad Request (l'API veut du JSON) ; le flux, le TLS
et l'auth marchaient, seule la charge etait perdue.
- le seuil « vide » en octets signalait a tort idm-01 (2363 o) : un export LDIF
d'un annuaire a un compte pese cela. « Vide » se mesure en FICHIERS. Et le
verdict est un AVERTISSEMENT : la machine ne distingue pas « les donnees ont
disparu » de « il n'y en a pas encore ».
Reserve assumee : curl -k — l'API presente le cert de sa propre AC, pas step-ca.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sixieme et dernier arret de la reconstruction from-zero, et le seul qui ne soit
ni un ordre ni une course : un vrai conflit de port.
infra-mail-01 : dovecot ecoute 0.0.0.0:12345 (serveur_dovecot_sasl_port,
choix delibere de Set-OPS pour la soumission :587)
alloy : defaut amont 12345 -> bind: address already in use
Le premier demarre gagne. En exploitation courante le conflit DORMAIT : Alloy
tenait le port depuis toujours et c'est l'ecoute SASL de Dovecot qui echouait,
en silence. L'ordre des couches d'une reconstruction inverse les roles et le
rend visible.
Le vrai defaut n'est pas le numero : c'est qu'un port SUBI ne se declare nulle
part, donc aucun controle ne peut voir la collision. Le port est desormais
IMPOSE (--server.http.listen-addr) et DECLARE dans meta/flux.yml avec
pair: localhost — une revendication de port, pas un flux entre hotes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le generateur emettait `{protocole} dport {port}` pour tout flux. L'ICMP a un
type et un code, pas un port : `icmp dport frag-needed` produit un jeu que `nft`
rejette, et un jeu rejete ne se charge PAS — l'hote perd sa barriere au lieu
d'en gagner une.
Le defaut touchait les 14 hotes. La seconde barriere de D-31 n'avait jamais pu
demarrer nulle part ; personne ne l'avait vu parce qu'aucune VM tenant n'avait
encore ete deployee.
`_selecteur_nft()` traduit : `icmp frag-needed` devient
`icmp type destination-unreachable icmp code frag-needed`. Un code inconnu est
refuse a la generation, avec le nom du role fautif. La garde tourne aussi a la
verification, pour attraper un flux declare mais pas encore porte.
Mesure : nftables actif sur infra-pki-01 et infra-dns-01, 16 et 17 regles, dont
la garde anti-lockout.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`nftables_admin_ssh` est la source unique du « d'où administre-t-on » : il
alimente les alias de la frontière, le devis du commutateur et le jeu nftables
de chaque VM. Chezlepro déclarait encore l'ancien 192.168.255.0/24.
`make flux` échouait par ailleurs sur un tri mêlant ports numériques et
symboliques — défaut latent réveillé par les deux flux ICMP frag-needed, qui
sont dans les deux sens. `_cle_port()` rend la clé homogène.
Aperçus régénérés : les 14 hôtes actifs de Chezlepro portent 10.0.0.0/24.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le devis se terminait par `block out log all` avec une seule règle sortante
(le relais SMTP). Appliqué tel quel, il coupait la flotte d'Internet : plus
d'apt, plus de NTP, plus de récursion DNS. Rien ne le signalait — la ligne
la plus lourde de conséquences du devis, posée à la suite des autres.
La sortie est déclarée dans le registre, donc dérivée :
- `serveur_debian` (socle, 14 hôtes) : 443 et 80 pour les dépôts apt, 123/udp
pour l'horloge — une dérive fait échouer step-ca et le SSO des semaines
après la cause ;
- `client_unbound` : 53 udp et tcp, la récursion depuis la racine qu'implique
`client_unbound_transitaires: []`. Le TCP est le repli obligatoire dès
qu'une réponse DNSSEC dépasse la taille UDP.
Le devis passe de 6 à 11 règles, et sa section 5 énonce le default-deny
sortant, le nombre de règles qui l'accompagnent, et où déclarer un besoin
oublié — jamais à la main dans le pare-feu.
Vérifié : les nftables d'hôte sont inchangés, octet pour octet. Le pair
`externe` reste sauté par resoudre_flux.
Non déclaré volontairement : le rôle `chrony` n'est référencé par aucun
groupe ni playbook — un flux pour lui aurait été une règle morte.
Preuves : 24 OK, 0 échec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>