Commit graph

13 commits

Author SHA1 Message Date
788c5073dc DNS public, phase 1 : le locataire ecrit, le site sert
Role serveur_dns_public (secondaire public, transfert signe TSIG, aucune zone interne) ;
serveur_powerdns exerce enfin autorite primaire-cache dans une instance pdns@public a part,
pour que le site ne puisse jamais interroger la zone .internal. Relations derivees des plans
des locataires, mots de flux dns_public_site et primaires_dns_locataires. Eprouve avant
d ecrire : allow-axfr-ips et TSIG sont alternatifs, le primaire notifie aussi ses NS, le
serial fige aurait gele le secondaire. P82 refuse l exposition sans DNSSEC.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 18:08:20 -04:00
a8af541059 D-86 : la supervision se deploie juste apres la PKI, pas a la fin
On n allume pas la lumiere une fois la maison finie. L observabilite et le
moteur de supervision etaient deployes APRES presque tout ce qu ils
surveillent. Une reconstruction depuis zero est pourtant le moment ou l on
a le plus besoin de voir, et c etait le seul moment ou l on ne voyait rien.

  4. observabilite       postgresql, prometheus, loki, grafana, icinga
  5. agents_supervision  client_metrique, client_journal, client_sante

DEPLACER LES SERVEURS SANS LES AGENTS N AURAIT RIEN CHANGE : ce sont les
agents qui rapportent, et ils etaient en derniere couche. Les deux couches
vont donc ensemble. Des la couche 5, chaque hote expedie ses metriques,
ses journaux et l etat de ses unites — tout ce qui se deploie ensuite est
mesure pendant qu on le construit.

COUT : serveur_postgresql monte aussi (icinga l exige, il n exige rien).
C est le seul entrainement.

RESTE TARD A DESSEIN : icingaweb2 et oauth2_proxy reclament LDAP et
Keycloak. C est la CONSOLE, pas la mesure.

CE QUI REND CE DEPLACEMENT POSSIBLE : le DNS reste en couche 6, donc apres
la supervision, alors qu icinga joint sa base par un NOM. Ca tient grace au
plancher /etc/hosts pose des la couche 1 — 34 entrees, verifie. C est
exactement ce pour quoi il existe.

LIMITE : les notifications dependent de client_smtp, encore en derniere
couche. Pendant une reconstruction, l etat est mesure et consultable, mais
rien ne part par courriel avant la fin.

ET : l ordre est VALIDE, pas EPROUVE. P08 accepte, le graphe accepte,
site.yml regenere passe le syntax-check sur 782 lignes de plan. La seule
preuve reelle d un ordre de reconstruction, c est une reconstruction.

make prouver : CONFORME, 64 OK, 0 echec, 0 saute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-09 23:39:19 -04:00
6d23121dc2 supervision : systemctl --failed entre dans Icinga (role client_sante)
CE QU IL FERME. openipmi.service echouait a chaque demarrage sur les
quatorze machines depuis le 2026-09-02, et systemctl --failed rendait ZERO
partout : non parce qu elles allaient bien, mais parce qu aucune n avait
redemarre depuis. Il a fallu qu un humain redemarre une machine pour que
le defaut existe aux yeux de quelqu un. Un controle qui ne peut echouer
qu au demarrage ne mesure rien tant que rien ne demarre.

PASSIF, ET A DUREE DE VIE. Un controle actif ne voit pas la machine MUETTE.
Ici c est le noeud qui parle, et le ttl de son envoi fait la fraicheur :
sans nouvelle, Icinga perime le service tout seul. Le silence alerte autant
que l echec. Le minuteur declenche AU DEMARRAGE autant que toutes les 15
min : les echecs de cette famille naissent au boot.

CRITIQUE DES LA PREMIERE UNITE, jamais un seuil — une unite en echec est
soit un vrai probleme soit du bruit a retirer, et un seuil ferait vivre le
bruit. Les tolerances se nomment une par une, vide par defaut.

CONTROLE NEGATIF. Unite factice sur obs-01, etat relu dans IcingaDB :
CRITICAL, et le verdict NOMME l unite. Les treize autres OK. Apres
nettoyage : 14/14 OK.

UN CONFLIT EVITE. setops-sauvegardes.conf definissait les object Host ; un
second fichier de controle aurait redefini les memes, et Icinga refuse un
objet en double — la configuration entiere aurait ete rejetee, donc AUCUNE
supervision, en voulant en ajouter. Les hotes vivent maintenant dans
setops-hotes.conf, definis une fois.

TROIS OBSTACLES. ${#tableau[@]} contient {# que Jinja lit comme un debut de
commentaire (remede : comment_start_string en tete du gabarit). Ma premiere
sonde a traduit un 403 « Missing permission: objects/query/service » en
« 0 service » — encore un echec qui ecrasait permission ; l etat se lit
dans IcingaDB. Et un echec apt transitoire sur mon-01, local et disparu au
second essai : mesure avant conclusion.

NON FAIT : le SITE n a pas recu client_sante.

make prouver : CONFORME, 63 OK, 0 echec, 0 saute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-09 20:32:08 -04:00
3ea0c95492 sauvegarde : l etat d un locataire quitte enfin sa propre flotte
Some checks are pending
verifier / verifier (push) Waiting to run
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
2026-09-01 22:16:20 -04:00
a5c9a88f00 resolveur du site : le troisieme service prete pendant la jeunesse d un tenant
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
2026-08-30 12:37:33 -04:00
58ce3dfea4 forge du site : le marqueur manquant — le genome n etait ouvert a personne
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
2026-08-28 16:15:42 -04:00
8890c997af runner : serveur_ops_tenant — le runner d'un tenant recoit enfin sa voute
Some checks are pending
verifier / verifier (push) Waiting to run
La doctrine des runners decrit trois portees depuis le 2026-08-22 :

  calculer      plan -> inventaire        aucune voute        serveur_ops
  configurer    roles sur ses machines    voute du TENANT     <- revendiquee, jamais recue
  materialiser  creer/detruire des VM     voute du SITE       serveur_ops_site

La deuxieme ligne etait un trou. RIEN ne deposait jamais la voute d'un tenant sur
son runner. Un runner pouvait deriver son inventaire et ne rien pouvoir en faire :
chaque role qui demande un secret echouait sur son assertion, et l'echec ne disait
pas qu'il manquait un FICHIER, seulement que les valeurs etaient vides.

Decouvert en preparant la reconstruction de Chezlepro. Le runner du SITE peut
materialiser ses quinze machines ; il ne peut pas les configurer, parce que
Chezlepro a sa PROPRE autorite de certification — donc `client_pki` y reclame un
secret de Chezlepro. Le runner du site ne l'a pas, et NE DOIT PAS l'avoir : c'est
la ligne qui rend l'hebergement mutualise defendable.

`serveur_ops_tenant` est le symetrique exact de `serveur_ops_site` : un MARQUEUR,
pas un installateur. Il depose la voute `decrypt: false`, puis RELIT l'en-tete du
fichier depose — une voute dechiffree par accident est une fuite silencieuse. Le
dossier d'inventaire (`principal` ou `production`) se DECOUVRE sur la machine
plutot que d'etre ecrit.

Pourquoi un role a part et non une option de `serveur_ops` : donner sa voute a un
runner est un POUVOIR, pas un reglage. Le declarer au plan force a repondre a
« cette machine a-t-elle le droit de configurer cet ecosysteme ? » — une option
activee par defaut y repondrait a notre place.

CHEZLEPRO LISAIT ENCORE SON GENOME CHEZ PATIENT 0.

Trouve au passage, et bloquant pour la reconstruction : `serveur_ops_forge_amont`
pointait sur `10.29.16.11` — la forge de patient 0, l'amont d'avant que le site
ait la sienne. D-81 a tranche depuis. Laisse tel quel, Chezlepro se serait
reconstruit depuis un moteur perime, sur un reseau que le decoupage en zones a de
toute facon deplace.

Corrige vers la forge du site, PAR SON IP — `forge.genese.internal` appartient a
la zone souveraine du site, et un tenant ne resout que la sienne ; le certificat
SERVI porte bien `IP Address:10.0.33.11`. La racine de l'AC du site est desormais
versionnee a cote de la carte de la fabric a laquelle elle appartient, et son
chemin se DERIVE du symlink `underlay.yml` : il vaut depuis le poste comme depuis
un runner.

Le role est inscrit dans les quatre registres qui l'exigeaient — couches de
deploiement, graphe des dependances, catalogue des services, carte d'orientation.
Ce sont les preuves P08, P38 et P48 qui l'ont reclame, chacune a son tour.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 15:30:51 -04:00
6e126dd8a9 site : le chemin qui materialise les machines de l'hebergeur
- site_machines.py : traduit une declaration d'underlay en SETOPS_*, que cloner-vm
  consomme deja. Le clone reste le seul chemin eprouve.
- site_inventaire.py : inventaire DYNAMIQUE. Un site ne derivant de rien, sa declaration
  est deja sa forme finale — un hosts.yml genere ne rendrait rien plus inspectable.
  Les groupes sont les services : playbooks/groupes/<role>.yml trouve ses hotes seul.
- cibles site-decrire / site-inventaire / site-creer (CONFIRMER=true) / site-appliquer,
  toutes independantes d'une instance montee : le site existe avant tout tenant.
- playbook de groupe manquant pour serveur_cache_site, et son classement en couche.

Le premier garde-fou de site-appliquer refusait un GROUPE vide : il ne pouvait jamais
se declencher (GROUPE a un defaut global). Le vrai risque, observe en le testant : un
playbook de tenant contre l'inventaire du site ne matche aucun hote et sort avec 0 — un
succes qui n'a rien fait. Refus desormais de tout groupe absent de cet inventaire.

42 preuves vertes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 23:11:32 -04:00
db2d6f6bf0 resolveur : un par tenant, et non plus un par machine
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>
2026-08-24 16:59:34 -04:00
2b55761fba runner : separer le pouvoir de configurer de celui de materialiser
La ligne de partage est celle des voutes. serveur_ops calcule et configure --
voute du tenant, SSH chez lui. serveur_ops_site materialise -- voute du SITE,
API de l'hyperviseur, jamais de SSH chez un tenant.

Un runner par tenant qui materialiserait mettrait la voute du SITE en N
exemplaires. Un runner unique qui ferait tout traverserait le default-deny
inter-tenant et rendrait l'emancipation impossible.

Le role depose la voute CHIFFREE et relit l'en-tete apres avoir ecrit : sans
`decrypt: false`, Ansible dechiffre la source quand il detient le mot de passe
-- constate le jour meme, 776 octets en clair au lieu de 3465. Controle negatif
fait, la garde mord.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 15:30:03 -04:00
32e1f6fbcd artefacts : la forge sert le code, il manquait qui sert les binaires
Some checks failed
verifier / verifier (push) Has been cancelled
serveur_artefacts (apt-cacher-ng) + client_artefacts, integration universelle
qui s'eteint quand aucun hote ne porte le service et RETIRE la direction posee.
Chez patient 0 : colocalise sur forge-01 -- la forge est la source, du code et
des binaires.

La preuve est le mode hors ligne : paquet en cache servi en 0 o/s, paquet absent
refuse par un 503. Tant qu'internet repond, un apt update qui reussit ne dit pas
d'ou vient l'octet.

Trois lecons : apt fait heriter Acquire::https::Proxy de la valeur HTTP (d'ou
403 CONNECT denied sur les depots tiers, et smallstep injoignable) ; un service
ne doit pas dependre de lui-meme pour se reparer (l'apt update du role passait
par le cache hors ligne) ; et rediriger 2>/dev/null, c'est choisir de ne pas
voir.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 14:41:56 -04:00
665b07ba82 serveur_ops : la difference entre une archive et une matrice
Some checks are pending
verifier / verifier (push) Waiting to run
Un ecosysteme pouvait DETENIR son genome sans savoir l'executer. Le poste
d'exploitation porte Ansible epingle, le genome clone depuis SA PROPRE forge, et
une cle SSH qui n'appartient qu'a lui. Il n'emporte ni la voute ni son mot de
passe : la structure se reconstruit depuis la forge, les secrets depuis la
sauvegarde. Deux sources qu'un meme incident n'atteint pas ensemble.

Une machine neuve traverse tout le moteur sans rien heriter, et revele ce qu'un
etat anterieur masquait. Deux defauts silencieux en sont sortis.

setops_plan_dir pointait le lien `instance` en dur : neuf roles lisaient donc le
plan d'une AUTRE instance. Le plancher de patient 0 portait les FQDN de
Chezlepro, et son edge publiait server_name forge.chezlepro.internal. La
variable suit desormais l'inventaire reellement charge.

Trois instances sur quatre declaraient les SAN d'exposition de leur edge ; la
quatrieme et le modele public ne les avaient pas. La forge de patient 0 etait
donc publiee derriere le certificat auto-signe de Debian, sans que rien ne le
signale -- `git clone` fut le premier a refuser, a juste titre. P42 le reclame
maintenant pour tout ecosysteme qui declare un edge.

Le harnais a ecrit la moitie de ce role : 5 echecs sur la piece neuve, aucun
n'empechait le code de tourner, tous la rendaient invisible a la carte, au
graphe et au lecteur. 42 preuves, 0 echec.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 11:58:10 -04:00
f05f505b88 Reconstruction propre : orchestrateur ordonné, registre des flux + pare-feu
Rend l'écosystème reconstructible en une commande (create+deploy idempotent) et
ajoute la couche « accès » (nftables least-privilege) au zéro-confiance.

Orchestrateur (phase 2) :
- docs/couches-deploiement.yml : registre des couches (socle → pki → services → apps → agents)
- scripts/orchestrer.py : tri par couche + topo intra-couche (graphe) → playbooks/site.yml ordonné
- Makefile : site / deployer-tout / flotte-creer / reconstruire / myDay (+ gardes CONFIRMER)
- docs/dependances-groupes.yml : graphe complété (keycloak→openldap, dovecot, postfix, icingaweb2, nextcloud)

Audit codé-en-dur (phase 1b) : labels/slug OIDC dérivés de l'intrant `organisation`
(serveur_forgejo/grafana/nextcloud) — le moteur ne porte plus de nom de tenant.

Registre des flux réseau (phase 0) :
- meta/flux.yml pour tous les rôles (29 rôles, 63 flux ; schéma + matrice validés)
- scripts/resoudre_flux.py : matrice d'audit (docs/registre-flux.md) + rulesets nftables résolus par hôte
- roles/nftables_baseline : déploie le ruleset résolu (moindre-privilège) quand activé, sinon repli

Correctifs : détection du coffre Vault (chemin production → inventaire réellement résolu).
Outillage : make wiki-publier (publication du wiki pédagogique dans Forgejo).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 03:08:09 -04:00