Zone publique : les expositions pointent vers ip_publique (les NS restent au site).
client_pki : l AC interne ne signe que le domaine interne. Dorsal : serveur par defaut 444.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
L edge expose les interfaces web aux personnes ; les VM du site vont directement au
service. Declare par domaine (service | edge, defaut edge) ; plancher, zone et certificat
du service suivent. Pose au site : 35 planchers sur 35 conformes ; locataires inchanges.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Deux mouvements d une seule doctrine : le site fournit tout ce dont un
tenant a besoin pour venir au monde.
1. LES DEPOTS TIERS. Grafana, Icinga, Smallstep et Collabora ne publient
qu en HTTPS ; apt-cacher-ng ne relaie pas un tunnel, et le socle pose donc
Acquire::https::Proxy DIRECT. Chaque machine sortait elle-meme sur
Internet. Le cache declare desormais un Remap par fournisseur, et chaque
role demande en {{ ..._depot_schema }}:// - http des qu un cache est
declare, https sinon. Le TLS n est rompu nulle part : il est TERMINE au
cache, qui est notre machine, et l integrite vient des signatures.
avant : 4 fournisseurs en HTTPS direct, 14 machines sortant seules
apres : 0 source en HTTPS direct, 0 erreur apt sur 14 machines
Trois lecons. Le remap appartient au cache qui SORT : pose sur un cache
chaine, il tente le HTTPS a travers son amont et rend 503. apt_repository
AJOUTE au lieu de remplacer, donc l ancienne ligne https sortait toujours.
Et la liste des fournisseurs ne se devine pas - j en avais trois, l audit
en a revele un quatrieme.
2. LE CACHE DU TENANT. Sa ligne portait son propre retrait depuis toujours
- service MUTUALISABLE, un ecosysteme au premier age peut pointer sur celui
de son hote. Retiree. Ce qu on perd, dit franchement : plus de trafic
inter-zone et plus de charge sur site-cache-01, contre un service de moins
a poser, superviser et reproduire.
3. UN ROLE QU ON RETIRE DOIT DEFAIRE CE QU IL A FAIT. Le retrait a montre
que rien ne nettoie derriere. Le fichier apt visait un cache eteint en
ecrasant le plancher qui fonctionnait - le defaut deja paye a quinze
machines. Et les sondes du cache restaient, le porteur poussant pour des
services qu Icinga ne definit plus (404). Le socle retire le premier,
client_sante derive les sondes attendues et retire les orphelines.
P65 refuse tout role visant un depot relaye en https ecrit en dur. Sa
limite est dite : elle empeche une regression sur ce qui est connu, elle ne
decouvre pas l inconnu.
Mesure : Icinga 87 OK sur 96, prouver 65 OK, lint 0 defaut.
Reste, et c est dit : apt-cacher-ng tourne toujours sur forge-01 que plus
aucun plan ne declare.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
LE CONSTAT. 39 roles declarent leurs flux, 32 leur empreinte, 32 leur
authentification — tous derives. Et 19 groupes sur 19 declaraient une
surveillance en prose que RIEN n executait ; Icinga en surveillait deux.
La carte disait ce qui etait surveille, et personne ne surveillait.
LE MECANISME. Un role declare ses sondes dans meta/supervision.yml et
depose lui-meme son script dans /usr/local/lib/setops/sondes/. Le porteur
client_sante les fait toutes tourner et pousse un resultat passif par
sonde, sans savoir ce qu elles mesurent. serveur_icinga derive les objets
Service ET le filtre de permission d API des memes declarations. Ajouter
une sonde ne demande de toucher ni au porteur ni a Icinga.
PREMIERE SONDE : client_pki/certificat. Heures restantes sur le certificat
reellement pose, chaine verifiee, et empreinte SERVIE comparee au disque
quand un service le consomme. 14/14 au tenant, 7/7 au site.
QUATRE OBSTACLES, ET TROIS SONT LA MEME LECON.
La sonde a rendu 14/14 en CRITIQUE sur une PKI saine : openssl verify
-CAfile racine ne trouve pas l intermediaire qui signe nos certificats.
step certificate verify, lui, repond VALIDE.
Deployee au site, elle a rendu 5/7 : le seuil d avertissement (12 h)
etait AU-DESSUS du point de renouvellement (8 h, le tiers restant). Elle
criait avant que le mecanisme ne soit cense agir. Seuils ramenes a 6 h et
3 h. Un seuil se DERIVE du moment ou le mecanisme surveille agit.
Une alarme toujours allumee ne vaut pas mieux qu une alarme jamais
allumee : elle apprend a ne plus regarder. Une sonde se prouve DEUX FOIS,
verte sur le sain et rouge sur le casse.
Le filtre d API etait ecrit avant la lecture des declarations : les
services auraient existe et Icinga aurait refuse leurs resultats.
Et mon controle negatif a casse un service reel : substituer le certificat
d hote a fait propager un cert sans sa clef vers node_exporter. Un controle
negatif se fait sur une COPIE.
P64 tient les deux bouts : declaree sans etre deposee, ou deposee sans
etre declaree. Trois controles negatifs rejoues.
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
`serveur_ops` et `serveur_ops_site` sont deployes sur `site-ops-01` : le moteur,
les plans des trois tenants, les collections hors ligne, et la voute de
l'underlay deposee CHIFFREE. Le site a son runner.
DEUX FORMES DE RUNNER, ET UNE SEULE ETAIT PREVUE.
`serveur_ops` exigeait un depot `role: instance` et un `serveur_ops_instance` —
la forme d'un runner de TENANT, qui pilote un ecosysteme, monte son plan par le
lien `instance`, detient sa voute.
Un runner de SITE ne pilote rien : il MATERIALISE le terrain que d'autres
occuperont, et son inventaire est dynamique. Son plan declarait donc ses depots
de tenants en `role: tenant`, a dessein et avec ses raisons ecrites — et le role
le refusait au nom d'une exigence qui ne le concernait pas. Il exige desormais
que le runner pilote QUELQUE CHOSE, sans prescrire quoi.
Et il posait le lien quand meme : `serveur_ops_instance: ""` donnait
`instance -> /opt/setops/`, un repertoire qui n'est l'ecosysteme de personne. Un
lien qui existe et ne designe rien est pire qu'un lien absent : tout ce qui le
suit croit avoir trouve une instance.
LE CERTIFICAT COUVRE LES NOMS DU SERVICE.
`client_pki` ne mettait dans ses SAN que le FQDN, le nom court et l'IP. Le
clonage du genome a bute dessus : « certificate subject name
(site-forge-01.genese.internal) does not match target hostname
'forge.genese.internal' ». Le nom declare `expose:` etait publie partout —
plancher, zone DNS — et couvert nulle part.
Les expositions que cet hote SERT REELLEMENT entrent maintenant dans le
certificat. Un certificat qui revendiquerait le nom d'un service rendu ailleurs
serait une usurpation, pas une commodite.
`make genome-pousser` — LE RUNNER POUSSE, LE POSTE NE ROUTE PAS.
La forge du genome vit DANS le site, sur un reseau que le poste de l'exploitant
ne route pas. On ne perce pas un chemin pour lui : on lui retire le role. Le
poste emballe les commits dans un `git bundle` — un fichier, verifiable, qui ne
demande aucun reseau — et le runner verifie, avance EN AVANCE RAPIDE SEULEMENT,
puis pousse avec SA cle, autorisee en ecriture sur ces depots-la seulement.
Ce qui est pousse se DERIVE de `serveur_ops_depots`. `DEPOT=<nom>` en cible un
seul.
Trois pieges rencontres, tous inscrits dans le code : l'outil ne peut pas etre
supposé present sur le runner (il ne recevrait cette version qu'APRES la poussee
qu'elle sert a faire — le controleur le porte) ; la branche n'est pas toujours
`main` (deux depots vivent sur `master`, et le plan le declarait deja) ; le
dossier des depots freres contient un espace, d'ou `argv` et non `cmd`.
Le juge n'est pas le journal du playbook mais LA FORGE : on demande a son API ce
qu'elle porte, et on refuse si ca differe de ce que porte le runner. Six depots
verifies.
Pourquoi ca comptait : la forge du site etait restee quatre commits derriere le
poste, sans que rien ne le signale — dont le correctif qui desarme le pare-feu
Proxmox. Un ecosysteme qui se reproduit depuis une forge en retard reproduit ses
defauts.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
J'ai repete toute la journee qu'un SITE n'est pas un plan. C'etait faux : j'en
reconstruisais un morceau par morceau dans underlay.yml sans le nommer. Ce qui est vrai,
c'est qu'un site ne DERIVE pas — il declare ses adresses parce qu'il EST le terrain — et
ne passe donc pas par `instancier`. Mais ne pas deriver n'est pas ne pas avoir de plan.
SITE-Chezlepro/plan/{10-intrants,serveurs,applications}.yml ; underlay.yml ne garde que
la fabric. Ce qui est MESURE d'un cote, ce qui est VOULU de l'autre. Les groupes viennent
du registre des applications, les gardes ont suivi le plan (4 controles negatifs).
Huit defauts, tous invisibles chez un tenant parce qu'il a toujours tout :
- client_pki codait `infra-pki-01` EN DUR — vrai par coincidence de nomenclature ;
- aucun moyen pour un service non-root de lire la cle (Forgejo tourne en `git`) ;
- le certificat, PUBLIC par nature, restait en 0600 ;
- l'unite Forgejo n'avait pas d'ExecReload : un cert renouvele aurait ete servi perime ;
- le role ne savait pas servir TLS lui-meme (il y avait toujours un edge) ;
- le cert ne couvrait pas le nom de SERVICE, faute d'`expose:` ;
- les registres se chargeaient en tout-ou-rien : sans domaines.yml, plancher vide ;
- le flux declarait 3000 en dur.
Le message accusait presque toujours autre chose : le DNS quand c'etait un nom faux, une
permission de fichier quand c'etait un port privilegie, rien du tout quand le plancher
s'ecrivait vide.
ROOT_URL est gravee dans les URL de clonage : la forge sert desormais sur 443, avec
CAP_NET_BIND_SERVICE, et son URL n'a plus de port.
Verifie depuis le reseau : Verify return code 0, https://forge.genese.internal/ -> 200.
42 preuves vertes, flux coherents (34 roles, 92 flux).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Deuxieme application du patron devis/applicateur aux services. Trouve a la
premiere execution : sur infra-pki-01 — l'autorite elle-meme — le certificat
etait expire depuis plus de 8 h et le renouvellement echouait toutes les 14
minutes sur « 'step ca renew' requires the '--ca-url' flag ». Rien ne le
signalait.
Cause : sur l'hote de l'AC, /etc/step est le STEPPATH du SERVEUR, pas un
amorcage client — pas de defaults.json, et l'unite de renouvellement en
dependait. La lecon etait deja ecrite dans le commentaire de la tache
d'emission (« l'autorite ne bootstrape pas »), jamais reportee sur l'unite.
Le role ne pouvait pas non plus se soigner : la re-emission ne regardait que
la FORME (cert absent ou SAN manquant), jamais la validite. client_pki
verifie desormais l'echeance (client_pki_marge_renouvellement).
Ce qu'il a fallu desapprendre : les certificats vivent 24 h et se renouvellent
toutes les ~14 min ; « empreinte servie != empreinte disque » est l'etat
NORMAL. Comparer les empreintes aurait donne un verificateur qui crie en
permanence. Le signal est l'echeance de ce qui est SERVI, plus l'absence de
client_pki_reload_services.
Le devis a d'abord menti, du defaut meme qu'il traque : include_vars au niveau
du play prime sur les group_vars. Et le premier correctif a PARU marcher —
set_fact accepte un dictionnaire entier en argument libre sans erreur et n'en
fait rien. Il faut reimposer cle par cle. Les deux devis sont corriges et le
piege est consigne dans docs/devis-services.md avant d'ecrire le prochain.
Verifie dans les deux sens : CONFORME sur 14 hotes ; sur un releve ou l'on
rejoue une copie perimee en memoire, 2 ecarts et code de sortie 1.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le rôle dérive l'empreinte à chaud depuis l'autorité, parce qu'un from-zero
régénère l'AC avec une empreinte neuve. Figée en voûte, elle serait périmée
dès la première reconstruction — et une empreinte périmée fait échouer le
bootstrap de chaque hôte.
Or `defaults/main.yml` portait encore un défaut lisant
`vault_step_ca_fingerprint`. Ce défaut était mort : la tâche suivante écrase
le fait sans condition, donc la valeur de la voûte n'avait aucun effet.
Le recensement de voute.py s'y laissait prendre — il cherche la chaîne
`vault_*` sans pouvoir savoir qu'un défaut n'est jamais lu. La « source
unique » avait hérité de l'erreur, et le panneau réclamait un secret
impossible à fournir avant que l'AC n'existe.
Défaut retiré : la clé disparaît du recensement (25 -> 24 exigés), du
gabarit et du panneau. `client_pki_ca_fingerprint_override` reste le moyen
d'épingler une empreinte.
`docs/intrants-communs.md` §H était une troisième copie manuelle de la liste
des secrets, avec les deux mêmes erreurs ; elle renvoie maintenant à
`voute.py lister` et à la preuve P18.
Preuves : 24 OK, 0 échec ; voute.py verifier --strict passe.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bug latent de flotte : cert-renewer rechargeait un service nommé d'après le
cert (%i = FQDN, inexistant) au lieu du consommateur réel → nginx/postfix/
dovecot/slapd servaient l'ancien cert jusqu'à expiration. Symptôme : cert
edge périmé en mémoire → échec TLS OIDC → login SSO cassé.
Correctif : client_pki_reload_services (vrais consommateurs), par groupe.
Fix immédiat de l'incident : reload nginx sur l'edge.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
step ca certificate ne recevait aucun --san explicite. Ajout d'une liste
client_pki_sans (FQDN + nom court + IP) et d'une boucle --san dans la
commande. Le cert porte désormais DNS:fqdn, DNS:court, IP:adresse — avec
EKU Server+Client Auth, prêt pour un mTLS bidirectionnel. Vérifié sur
infra-dns-01 (ré-émission).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Les rôles à secrets déclaraient serveur_X_password: "" avec la variable
de voûte seulement en commentaire — aucun mapping réel. Remplir la voûte
ne branchait donc rien (secret vide → assertion échoue).
Les 12 secrets des 8 rôles pointent maintenant vers leur source :
serveur_X_password: "{{ vault_X | default('') }}"
Comportement inchangé si la voûte est vide ; branché dès qu'elle a le
secret. Prouvé : assertion step_ca passe avec vault_step_ca_password.
Chaque instance remplit ses vault_* ; aucun mapping par instance.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>