Set-OPS-Public/roles/client_pki/tasks/main.yml

444 lines
20 KiB
YAML
Raw Normal View History

---
site : un SITE a bien un plan — et huit defauts que lui seul pouvait reveler 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>
2026-08-25 13:15:34 -04:00
# SANS AUTORITÉ DÉCLARÉE, ON NE DEVINE PAS SON NOM.
#
# L'URL de l'AC se dérivait d'un littéral (`infra-pki-01`), vrai chez tout tenant par
# coïncidence de nomenclature. Le premier écosystème à nommer sa PKI autrement a vu ses
# cinq machines s'enrôler auprès d'un hôte inexistant, et le message accusait le DNS.
#
# Elle se dérive maintenant de `groups['serveur_step_ca']`. Si ce groupe est vide, il n'y
# a pas d'autorité du tout : le dire ici vaut mieux que de laisser `step ca bootstrap`
# échouer sur un nom tronqué, trois tâches plus loin, en parlant de résolution.
- name: Une autorité de certification est-elle déclarée ?
ansible.builtin.assert:
that:
- client_pki_ca_hote | length > 0
fail_msg: >-
Aucun hôte ne porte `serveur_step_ca` dans cet inventaire : il n'y a pas d'autorité
interne à qui demander un certificat. Déclarer une PKI, ou exempter cet écosystème
de `client_pki` — mais avec une raison, jamais en silence.
# L'empreinte du root CA est la SOURCE DE VÉRITÉ de l'autorité elle-même : on la
# dérive à chaud (robuste au from-zero — une AC régénérée a une empreinte neuve).
# `client_pki_ca_fingerprint_override` permet d'épingler explicitement si besoin.
#
# UNE FOIS POUR TOUTE LA FLOTTE (`run_once`, 2026-09-30). L'empreinte est la meme pour
# chaque hote : la deriver par hote lancait TREIZE modules Python en parallele sur l'AC —
# 765 Mo, 1 vCPU. A la reconstruction de Technolibre, le noyau a tue le module de
# `collab-01` (rc=137) et l'agent de journaux au passage. Avec `run_once`, le resultat
# enregistre vaut pour tous les hotes du play.
- name: Dériver l'empreinte du root CA depuis l'autorité
ansible.builtin.command:
cmd: "step certificate fingerprint {{ serveur_step_ca_steppath | default('/etc/step-ca') }}/certs/root_ca.crt"
delegate_to: "{{ groups['serveur_step_ca'][0] }}"
run_once: true
changed_when: false
check_mode: false
register: client_pki_fingerprint_ac
- name: Retenir l'empreinte (dérivée de l'AC, sauf override explicite)
ansible.builtin.set_fact:
client_pki_ca_fingerprint: "{{ client_pki_ca_fingerprint_override | default(client_pki_fingerprint_ac.stdout | trim, true) }}"
- name: Exiger l'empreinte AC et le mot de passe provisioner (Vault)
ansible.builtin.assert:
that:
- client_pki_ca_fingerprint | length > 0
- client_pki_provisioner_password | length > 0
fail_msg: >-
Empreinte du root CA indisponible (step-ca joignable et déployé ?) ;
client_pki_provisioner_password requis (via Ansible Vault).
- name: Assurer le repertoire des trousseaux apt
ansible.builtin.file:
path: /etc/apt/keyrings
state: directory
owner: root
group: root
mode: "0755"
# Recuperee UNE FOIS. Sans garde, chaque deploiement recontactait le serveur du
# fournisseur : cinq cles x quatorze hotes = soixante-dix allers-retours externes pour
# des cles deja installees, et autant d'occasions qu'un tiers lent fasse tomber le
# deploiement. Arbitrage rendu le 2026-08-09 : une plateforme souveraine ne depend pas
# de six serveurs etrangers pour redeployer ce qu'elle possede deja.
#
# CONSEQUENCE ASSUMEE : une rotation de cle amont n'est plus recuperee toute seule. Elle
# ne passe pas inapercue pour autant — `apt` refuse alors le depot, bruyamment. Pour
# forcer le rafraichissement : supprimer le fichier et rejouer le role.
un fichier vide existe, et une sonde pour les correctifs LA GARDE FICHIER ENTIER, en deux corrections. Un telechargement interrompu laisse un fichier de zero octet QUI EXISTE, et toutes les gardes demandaient seulement s il etait la. infra-mail-01 a garde une cle smallstep de 0 octet apres l epreuve hors ligne. La premiere correction n a pas suffi. Ajouter le controle de taille faisait bien s executer la tache - et le fichier faisait toujours 0 octet au passage suivant. get_url sur une destination existante emet une requete CONDITIONNELLE : l amont repond non modifie, le module rend ok, la ruine reste. Le play etait vert et ne reparait rien. Il faut effacer avant de redemander. Controle negatif : 0 -> 1022 octets, 0 erreur apt. Cinq roles. LA SONDE CORRECTIFS, 23e. Set-OPS desarme unattended-upgrades et applique les correctifs au deploiement - choix defendable, le verrou dpkg a fait decrocher une machine d une reconstruction entiere le matin meme. Mais rien ne disait QUAND le geste etait du : une flotte pouvait deriver des mois en restant verte. Elle mesure les paquets de securite en attente ET depuis quand. Elle ne lance pas apt-get update - une sonde qui rafraichit l index toutes les quinze minutes deviendrait la cause de la panne qu elle surveille. Et le seuil de 72 h est un choix d exploitation, pas une derivation : le mecanisme qui applique les correctifs est un geste humain. P64 REFUSAIT UNE DECLARATION CORRECTE. serveur_debian et serveur_durci sont des roles de declaration pure, sans une tache ; le travail est fait par les roles que leur playbook applique. La preuve exigeait declaration et depot dans le meme role - vrai des vingt-deux premieres sondes, faux des qu une sonde appartient au socle. Une garde qui force a contourner ce qu elle protege est un defaut. Elle suit desormais le playbook du groupe. Mesure : 21/21 machines vertes, 65 preuves, 23 sondes, 0 echec. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-10 17:26:35 -04:00
# UN FICHIER VIDE EXISTE (mesure du 2026-09-10).
#
# Cette garde demandait « ce fichier est-il la ? ». Un telechargement interrompu — un 503,
# un delai depasse, une coupure — laisse un fichier de ZERO octet, qui existe. La tache
# est donc sautee a tous les passages suivants, et la machine garde une ressource morte
# pour toujours, en silence.
#
# CE QUE CA A COUTE : `infra-mail-01` a garde une cle smallstep de 0 octet apres une
# epreuve hors ligne. Treize machines portaient 1022 octets, elle portait le vide :
#
# E: Le depot http://packages.smallstep.com/... n'est pas signe.
#
# Le meme jour, l'index `dists/trixie` du cache du site etait tombe de 140 416 a 4 332
# octets — meme famille, autre endroit. Un telechargement partiel ne se signale pas : il
# se fait passer pour un succes.
#
# On demande donc « est-il ENTIER ? » — a defaut de pouvoir demander « est-il JUSTE ? »,
# ce qui exigerait une empreinte de reference que l'amont ne publie pas toujours.
- name: Cette ressource est-elle deja recuperee ? (Telecharger la cle de signature Sm)
ansible.builtin.stat:
path: "{{ client_pki_depot_cle_fichier }}"
register: telecharger_la_cle_de_signature_smallste_present
patient 0 debout — et quatre defauts que seul un ecosysteme DIFFERENT pouvait montrer Quatre machines, zero echec, la forge repond (service actif, ecoute 3000, HTTP 200). forge-01 121 taches, infra-pki-01 109, infra-edge-01 92, infra-dns-01 84. 1. UN TIERS INTERMITTENT ARRETAIT TOUT. `packages.smallstep.com` repond une fois sur deux ; `get_url` abandonne a 10 s sans reprise. Mesure AVANT d'accuser le reseau : DNS resolvait, la poignee TLS aboutissait, 138 Ko depuis deb.debian.org passaient en 0,09 s, et le chemin acceptait 1450 octets en refusant 1500 — l'attendu exact en overlay. Le reseau n'y etait pour rien. Reprises posees sur les trois telechargements du chemin critique (serveur_step_ca, client_pki, serveur_forgejo). Une dizaine d'autres restent sans reprise : liste dans le CHANGELOG. 2. UN `register` A ECRASE UN CHEMIN DE FICHIER. En posant la reprise, j'ai enregistre dans `client_pki_cle`, nom que le role utilisait deja pour la cle privee. L'ecart s'est vu 70 taches plus loin : `step ca certificate` recevait un dict serialise a la place du fichier et refusait « too many positional arguments ». Un register ecrit dans l'espace de noms de TOUT le role. 3. LE SSO SE DECLARAIT AU LIEU DE SE DERIVER. `serveur_forgejo_oidc_actif: true` en dur faisait cabler une source OAuth2 vers un Keycloak inexistant. Derive desormais de l'inventaire. Quatrieme manifestation en deux jours de la meme hypothese — le moteur supposait l'ecosysteme COMPLET — apres les intrants (P32), les bases (P35) et les dependances causales. AUCUN de ces defauts n'etait visible sur Chezlepro, qui porte tout et tournait deja. Ils ne pouvaient apparaitre qu'au premier ecosysteme DIFFERENT. make verifier 41 OK, 0 echec, 0 saute ; make test inchange. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 02:53:44 -04:00
# Un serveur tiers intermittent ne doit pas arreter un deploiement de quarante
# minutes (mesure du 2026-08-23 : la meme URL pend, puis rend 200 en 0,48 s au
# second essai). Defaut de `get_url` : 10 s et aucune reprise.
un fichier vide existe, et une sonde pour les correctifs LA GARDE FICHIER ENTIER, en deux corrections. Un telechargement interrompu laisse un fichier de zero octet QUI EXISTE, et toutes les gardes demandaient seulement s il etait la. infra-mail-01 a garde une cle smallstep de 0 octet apres l epreuve hors ligne. La premiere correction n a pas suffi. Ajouter le controle de taille faisait bien s executer la tache - et le fichier faisait toujours 0 octet au passage suivant. get_url sur une destination existante emet une requete CONDITIONNELLE : l amont repond non modifie, le module rend ok, la ruine reste. Le play etait vert et ne reparait rien. Il faut effacer avant de redemander. Controle negatif : 0 -> 1022 octets, 0 erreur apt. Cinq roles. LA SONDE CORRECTIFS, 23e. Set-OPS desarme unattended-upgrades et applique les correctifs au deploiement - choix defendable, le verrou dpkg a fait decrocher une machine d une reconstruction entiere le matin meme. Mais rien ne disait QUAND le geste etait du : une flotte pouvait deriver des mois en restant verte. Elle mesure les paquets de securite en attente ET depuis quand. Elle ne lance pas apt-get update - une sonde qui rafraichit l index toutes les quinze minutes deviendrait la cause de la panne qu elle surveille. Et le seuil de 72 h est un choix d exploitation, pas une derivation : le mecanisme qui applique les correctifs est un geste humain. P64 REFUSAIT UNE DECLARATION CORRECTE. serveur_debian et serveur_durci sont des roles de declaration pure, sans une tache ; le travail est fait par les roles que leur playbook applique. La preuve exigeait declaration et depot dans le meme role - vrai des vingt-deux premieres sondes, faux des qu une sonde appartient au socle. Une garde qui force a contourner ce qu elle protege est un defaut. Elle suit desormais le playbook du groupe. Mesure : 21/21 machines vertes, 65 preuves, 23 sondes, 0 echec. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-10 17:26:35 -04:00
# ... ET IL FAUT LE RETIRER, PAS SEULEMENT LE REDEMANDER (mesure du 2026-09-10).
#
# Laisser la garde autoriser la tache ne suffit PAS. `get_url` sur une destination qui
# existe emet une requete CONDITIONNELLE : l'amont repond « non modifie », le module rend
# `ok`, et le fichier corrompu reste en place. Mesure : la tache s'executait bien
# (`ok: [infra-mail-01]`), et la cle faisait toujours 0 octet au passage suivant. Un
# `force: yes` manuel l'a reparee en une seconde.
#
# On efface donc la ruine avant de reconstruire. `state: absent` ne mord que sur un
# fichier vide : une cle valide n'est jamais retiree, et la doctrine « on ne retelecharge
# pas ce qu'on possede deja » reste intacte.
- name: Retirer une ressource VIDE avant de la redemander
ansible.builtin.file:
path: "{{ client_pki_depot_cle_fichier }}"
state: absent
when:
- telecharger_la_cle_de_signature_smallste_present.stat.exists
- (telecharger_la_cle_de_signature_smallste_present.stat.size | default(0)) == 0
- name: Telecharger la cle de signature Smallstep
un fichier vide existe, et une sonde pour les correctifs LA GARDE FICHIER ENTIER, en deux corrections. Un telechargement interrompu laisse un fichier de zero octet QUI EXISTE, et toutes les gardes demandaient seulement s il etait la. infra-mail-01 a garde une cle smallstep de 0 octet apres l epreuve hors ligne. La premiere correction n a pas suffi. Ajouter le controle de taille faisait bien s executer la tache - et le fichier faisait toujours 0 octet au passage suivant. get_url sur une destination existante emet une requete CONDITIONNELLE : l amont repond non modifie, le module rend ok, la ruine reste. Le play etait vert et ne reparait rien. Il faut effacer avant de redemander. Controle negatif : 0 -> 1022 octets, 0 erreur apt. Cinq roles. LA SONDE CORRECTIFS, 23e. Set-OPS desarme unattended-upgrades et applique les correctifs au deploiement - choix defendable, le verrou dpkg a fait decrocher une machine d une reconstruction entiere le matin meme. Mais rien ne disait QUAND le geste etait du : une flotte pouvait deriver des mois en restant verte. Elle mesure les paquets de securite en attente ET depuis quand. Elle ne lance pas apt-get update - une sonde qui rafraichit l index toutes les quinze minutes deviendrait la cause de la panne qu elle surveille. Et le seuil de 72 h est un choix d exploitation, pas une derivation : le mecanisme qui applique les correctifs est un geste humain. P64 REFUSAIT UNE DECLARATION CORRECTE. serveur_debian et serveur_durci sont des roles de declaration pure, sans une tache ; le travail est fait par les roles que leur playbook applique. La preuve exigeait declaration et depot dans le meme role - vrai des vingt-deux premieres sondes, faux des qu une sonde appartient au socle. Une garde qui force a contourner ce qu elle protege est un defaut. Elle suit desormais le playbook du groupe. Mesure : 21/21 machines vertes, 65 preuves, 23 sondes, 0 echec. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-10 17:26:35 -04:00
when: not (telecharger_la_cle_de_signature_smallste_present.stat.exists and telecharger_la_cle_de_signature_smallste_present.stat.size | default(0) > 0)
depot de binaires : le site tient ce que les runners allaient chercher Le cache du site couvrait apt ; quatre artefacts arrivaient autrement, parce qu ils ne vivent dans aucun depot apt. Le controleur les tire puis les pousse par SSH. Mesure du 2026-09-12 : le cache du runner du site est ABSENT. Un second locataire monte depuis lui sortait chercher 570 Mo sur codeberg.org, github.com et download.nextcloud.com, alors que le meme ecosysteme ne demandait plus un seul paquet a Debian. Le poste du mainteneur les a depuis toujours : personne ne l avait vu. Pas de relais transparent, et la mesure tranche : github.com redirige vers une URL signee valable une heure, differente a chaque requete. Un cache qui la prend pour cle ne fait jamais mouche. Le relais marcherait pour deux amonts sur quatre. Donc un vrai depot, dans le service qui existe deja. LocalDirs d apt-cacher-ng publie un repertoire du disque sous un prefixe, eprouve AVANT d ecrire le role. Aucun service, aucun port, aucun certificat, aucun flux nouveaux : l ingress 3142 pair flotte couvre exactement ce chemin. Les versions ne sont pas recopiees : le role lit les defauts des quatre consommateurs. Les quatre roles recoivent une tache AJOUTEE, placee avant leur stat de cache — si le depot sert, le stat le voit et la tache amont se saute d elle-meme. Aucune tache existante n a change. P70 exige que tout dest ecrit sous un cache_local figure au depot. Une liste qui suit une autre prend du retard ; celle-ci est nee avec sa garde. Deux marches payees en chemin : - failed_when: false REECRIT le verdict, donc la premiere garde de signature ne gardait rien. Elles mesurent le fichier desormais. - file: state=directory cree les parents en 0750 : apt-cacher-ng, qui ne tourne pas en root, rendait 403 sur chaque fichier. Un chemin se traverse en entier. Verifie sur l infrastructure : 6/6 artefacts servis (200/206) depuis le runner du site ET depuis une machine du locataire a travers la frontiere ; les 6 empreintes SHA-256 sont identiques a celles qui ont construit Chezlepro ; second passage changed=0. make prouver : 69 OK, 0 echec, 1 saute. ansible-lint : 0 failure, profil production. Inclut aussi force: true sur cinq telechargements de cles : une reprise conditionnelle ne reprend rien (304 Not Modified, size 0, attempts 5). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-12 23:45:00 -04:00
# `force` PARCE QU'UNE REPRISE CONDITIONNELLE NE REPREND RIEN (2026-09-12).
#
# Sans lui, `get_url` envoie un `If-Modified-Since` des que le fichier existe. Le cache
# repond `304 Not Modified`, la tache reussit sans rien ecrire, et la boucle `until`
# — qui exige une taille non nulle — retente cinq fois contre un serveur qui repondra
# toujours 304 :
#
# HTTP Error 304: Not Modified size: 0 attempts: 5
#
# C'est la boucle de reprise qui se bat contre elle-meme. Le `when:` ci-dessus garantit
# deja qu'on ne retelecharge pas une cle valide ; `force` ne concerne donc que le cas
# ou l'on a DECIDE d'aller chercher.
ansible.builtin.get_url:
depot de binaires : le site tient ce que les runners allaient chercher Le cache du site couvrait apt ; quatre artefacts arrivaient autrement, parce qu ils ne vivent dans aucun depot apt. Le controleur les tire puis les pousse par SSH. Mesure du 2026-09-12 : le cache du runner du site est ABSENT. Un second locataire monte depuis lui sortait chercher 570 Mo sur codeberg.org, github.com et download.nextcloud.com, alors que le meme ecosysteme ne demandait plus un seul paquet a Debian. Le poste du mainteneur les a depuis toujours : personne ne l avait vu. Pas de relais transparent, et la mesure tranche : github.com redirige vers une URL signee valable une heure, differente a chaque requete. Un cache qui la prend pour cle ne fait jamais mouche. Le relais marcherait pour deux amonts sur quatre. Donc un vrai depot, dans le service qui existe deja. LocalDirs d apt-cacher-ng publie un repertoire du disque sous un prefixe, eprouve AVANT d ecrire le role. Aucun service, aucun port, aucun certificat, aucun flux nouveaux : l ingress 3142 pair flotte couvre exactement ce chemin. Les versions ne sont pas recopiees : le role lit les defauts des quatre consommateurs. Les quatre roles recoivent une tache AJOUTEE, placee avant leur stat de cache — si le depot sert, le stat le voit et la tache amont se saute d elle-meme. Aucune tache existante n a change. P70 exige que tout dest ecrit sous un cache_local figure au depot. Une liste qui suit une autre prend du retard ; celle-ci est nee avec sa garde. Deux marches payees en chemin : - failed_when: false REECRIT le verdict, donc la premiere garde de signature ne gardait rien. Elles mesurent le fichier desormais. - file: state=directory cree les parents en 0750 : apt-cacher-ng, qui ne tourne pas en root, rendait 403 sur chaque fichier. Un chemin se traverse en entier. Verifie sur l infrastructure : 6/6 artefacts servis (200/206) depuis le runner du site ET depuis une machine du locataire a travers la frontiere ; les 6 empreintes SHA-256 sont identiques a celles qui ont construit Chezlepro ; second passage changed=0. make prouver : 69 OK, 0 echec, 1 saute. ansible-lint : 0 failure, profil production. Inclut aussi force: true sur cinq telechargements de cles : une reprise conditionnelle ne reprend rien (304 Not Modified, size 0, attempts 5). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-12 23:45:00 -04:00
force: true
url: "{{ client_pki_depot_cle_url }}"
dest: "{{ client_pki_depot_cle_fichier }}"
owner: root
group: root
mode: "0644"
patient 0 debout — et quatre defauts que seul un ecosysteme DIFFERENT pouvait montrer Quatre machines, zero echec, la forge repond (service actif, ecoute 3000, HTTP 200). forge-01 121 taches, infra-pki-01 109, infra-edge-01 92, infra-dns-01 84. 1. UN TIERS INTERMITTENT ARRETAIT TOUT. `packages.smallstep.com` repond une fois sur deux ; `get_url` abandonne a 10 s sans reprise. Mesure AVANT d'accuser le reseau : DNS resolvait, la poignee TLS aboutissait, 138 Ko depuis deb.debian.org passaient en 0,09 s, et le chemin acceptait 1450 octets en refusant 1500 — l'attendu exact en overlay. Le reseau n'y etait pour rien. Reprises posees sur les trois telechargements du chemin critique (serveur_step_ca, client_pki, serveur_forgejo). Une dizaine d'autres restent sans reprise : liste dans le CHANGELOG. 2. UN `register` A ECRASE UN CHEMIN DE FICHIER. En posant la reprise, j'ai enregistre dans `client_pki_cle`, nom que le role utilisait deja pour la cle privee. L'ecart s'est vu 70 taches plus loin : `step ca certificate` recevait un dict serialise a la place du fichier et refusait « too many positional arguments ». Un register ecrit dans l'espace de noms de TOUT le role. 3. LE SSO SE DECLARAIT AU LIEU DE SE DERIVER. `serveur_forgejo_oidc_actif: true` en dur faisait cabler une source OAuth2 vers un Keycloak inexistant. Derive desormais de l'inventaire. Quatrieme manifestation en deux jours de la meme hypothese — le moteur supposait l'ecosysteme COMPLET — apres les intrants (P32), les bases (P35) et les dependances causales. AUCUN de ces defauts n'etait visible sur Chezlepro, qui porte tout et tournait deja. Ils ne pouvaient apparaitre qu'au premier ecosysteme DIFFERENT. make verifier 41 OK, 0 echec, 0 saute ; make test inchange. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 02:53:44 -04:00
timeout: 30
register: client_pki_cle_depot_telechargee
retries: 5
delay: 6
until: client_pki_cle_depot_telechargee is succeeded and (client_pki_cle_depot_telechargee.size | default(0)) > 0
les cles de signature passent aussi par le cache get_url IGNORE la configuration d apt : le mandataire pose dans /etc/apt/apt.conf.d/ ne vaut que pour apt. Les six cles de signature sortaient donc TOUJOURS en direct, malgre tout le travail sur le remap. Un trou reste ouvert derriere une porte qu on croyait fermee. Et ce n etait pas theorique. Depuis collab-01 : en direct grafana 200 collabora TIMEOUT via le cache collabora 200 La route directe vers Collabora ne passe pas depuis cette zone. Trois cles sur quatre avaient reussi PAR CHANCE, parce que leurs fournisseurs etaient joignables. Le meme geste corrige les deux : plus rien ne sort, et la machine qui n avait pas de route en trouve une. POURQUOI SEULE UNE NAISSANCE POUVAIT LE MONTRER. Mes deploiements de convergence rendaient failed=0 parce que les cles etaient DEJA sur disque et que la tache etait sautee. Le defaut existait depuis le premier commit du remap, invisible a tout deploiement sur une flotte existante. C est l argument de la reconstruction depuis zero, applique a moi-meme. Troisieme reconstruction : 32 minutes, un seul echec - celui-ci. Clonage 3 min 52, zero fatal dans le journal, 963 Mo servis par le cache contre 183 tires de l Internet, soit 81 pourcent servis localement. Et la derive s est effacee toute seule : apt-cacher-ng tournait encore sur forge-01 que plus aucun plan ne declarait. Je proposais de l arreter a la main ; la reconstruction l a fait. Ce qui n est pas au plan n existe pas apres une naissance. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-10 15:41:56 -04:00
# LA CLE PASSE PAR LE CACHE, ELLE AUSSI (mesure du 2026-09-10).
#
# `get_url` ignore la configuration d'apt : le mandataire pose dans
# `/etc/apt/apt.conf.d/` ne vaut que pour apt. Les cles de signature sortaient donc
# TOUJOURS en direct, meme apres que tous les depots soient passes par le cache — un
# trou reste ouvert derriere une porte qu'on croyait fermee.
#
# Et ce n'est pas theorique : depuis `collab-01`, la route directe vers
# `www.collaboraoffice.com` EXPIRE, quand le cache l'atteint sans peine.
#
# en direct grafana 200 collabora TIMEOUT
# via le cache collabora 200
#
# Le meme geste corrige donc les deux : plus rien ne sort, et la machine qui n'avait
# pas de route en trouve une.
#
# DEGRADE, JAMAIS DEVINE : sans cache d'amorcage declare, la valeur est VIDE et
# `get_url` sort en direct comme avant. On n'invente pas un mandataire.
environment:
http_proxy: >-
{{ ('http://' ~ artefacts_amorcage) if (artefacts_amorcage | default('') | string | length > 0) else '' }}
https_proxy: ""
- name: Mesurer la cle de signature Smallstep qui vient d'etre posee
ansible.builtin.stat:
path: "{{ client_pki_depot_cle_fichier }}"
register: cle_smallstep_posee
- name: Exiger une cle NON VIDE avant de s'en servir
# UNE REUSSITE N'EST PAS UN CONTENU (mesure du 2026-09-11). Sur une reconstruction a
# froid, `get_url` a rendu 0 octet sans erreur sur UNE machine des quatorze : le cache
# a servi un 200 au corps vide. La garde « retirer une ressource VIDE » ne mord qu'au
# passage SUIVANT, quand le fichier existe deja — au premier, elle n'a rien a retirer.
#
# Le defaut ne se lisait pas ici. Il se lisait deux cents lignes plus loin, dans un
# `apt` qui accusait la SIGNATURE : « Missing key ..., which is needed to verify
# signature ». Un message qui envoie chercher une cle revoquee chez le fournisseur,
# alors que le fichier local fait zero octet.
ansible.builtin.assert:
that:
- (cle_smallstep_posee.stat.size | default(0)) > 0
fail_msg: >-
La cle de signature Smallstep ({{ client_pki_depot_cle_fichier }}) fait 0 octet.
Le depot a repondu sans corps — cache vide, ou amont injoignable. apt echouera
plus loin en accusant la signature : c'est ICI qu'il faut regarder.
- name: Ajouter le depot apt Smallstep
ansible.builtin.template:
src: smallstep.sources.j2
dest: /etc/apt/sources.list.d/smallstep.sources
owner: root
group: root
mode: "0644"
paquets tiers : passer par le cache du controleur, plus par Internet Trois depots tiers etaient en HTTPS, et client_artefacts pose Acquire::https::Proxy DIRECT — sans quoi le cache du site refuse les tunnels et aucun depot tiers n est joignable. La ligne est juste ; sa consequence ne l avait pas ete vue : ces trois depots CONTOURNENT le cache, et chaque VM neuve allait les chercher sur Internet a sa naissance. step-cli et alloy sont poses par des integrations UNIVERSELLES. Sans lien, une machine neuve n obtenait ni son client d autorite ni ses metriques — elle n entrait dans aucun flux chiffre. Dernier obstacle a une reconstruction hors ligne, et il tenait dans un mot. make cacher-paquets tire les 21 deb aux versions epinglees, empreinte SHA256 verifiee depuis l index du depot. Le role partage paquets_tiers les depose et les installe EN UN SEUL appel a apt — il sait resoudre un ensemble de fichiers locaux qui se dependent, la ou paquet par paquet echouerait sur l ordre (Icinga en apporte dix-sept). Six roles branches ; chacun retombe sur le depot distant pour ce que le cache n a PAS fourni, et rien d autre. Eprouve pour de vrai : packages.smallstep.com renvoye vers 127.0.0.1, step-cli desinstalle, cache local efface. Le role rejoue installe 0.30.6-1 depuis le cache et la machine obtient ses certificats. Zero echec. Deux defauts de mon propre outil, trouves en le construisant : Smallstep sert son index NON COMPRESSE (404 sur .gz) donc step-cli n etait jamais mis en cache ; et un depot injoignable faisait continue AVANT d incrementer le total, si bien que le script rapportait 20 sur 20 alors qu il en manquait un. Une garde qui ne peut pas echouer ne garde rien — troisieme fois cette semaine. Corrige puis eprouve en le faisant echouer. Reste hors ligne : NTP externe, et l expedition des alertes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-09-05 08:22:14 -04:00
# LE CACHE DU CONTROLEUR D'ABORD, LE DEPOT DISTANT ENSUITE (2026-09-03).
#
# `client_pki` est une integration UNIVERSELLE : chaque machine de chaque ecosysteme
# installe `step-cli` ici, a sa naissance. Le depot Smallstep est en HTTPS, et
# `client_artefacts` pose `Acquire::https::Proxy "DIRECT"` — il CONTOURNE donc le cache
# du site et sort sur Internet. Sans lien, une VM neuve n'obtenait pas son client
# d'autorite, donc pas de certificat, donc n'entrait dans aucun flux chiffre.
#
# C'etait le dernier obstacle a une reconstruction hors ligne, et il tenait dans un mot :
# `DIRECT`, pose a juste titre pour une autre raison.
- name: Poser step-cli depuis le cache du controleur, s'il y est
ansible.builtin.include_role:
name: paquets_tiers
vars:
paquets_tiers_noms: "{{ client_pki_paquets }}"
# FILET, ET SEULEMENT SI LE CACHE N'A RIEN DONNE.
#
# `update_cache: true` interroge TOUS les depots configures, Smallstep compris. Hors
# ligne, cette tache echouerait donc APRES que le cache ait deja pose le paquet — le
# deploiement tomberait sur un travail deja fait. Le repli ne doit exister que quand il
# y a quelque chose a rattraper.
#
# Cache vide — poste jamais connecte, paquet retire de la declaration — on retombe sur le
# depot distant comme avant. Degrader, jamais deviner.
- name: Installer depuis le depot distant ce que le cache n'a pas fourni
ansible.builtin.apt:
paquets tiers : passer par le cache du controleur, plus par Internet Trois depots tiers etaient en HTTPS, et client_artefacts pose Acquire::https::Proxy DIRECT — sans quoi le cache du site refuse les tunnels et aucun depot tiers n est joignable. La ligne est juste ; sa consequence ne l avait pas ete vue : ces trois depots CONTOURNENT le cache, et chaque VM neuve allait les chercher sur Internet a sa naissance. step-cli et alloy sont poses par des integrations UNIVERSELLES. Sans lien, une machine neuve n obtenait ni son client d autorite ni ses metriques — elle n entrait dans aucun flux chiffre. Dernier obstacle a une reconstruction hors ligne, et il tenait dans un mot. make cacher-paquets tire les 21 deb aux versions epinglees, empreinte SHA256 verifiee depuis l index du depot. Le role partage paquets_tiers les depose et les installe EN UN SEUL appel a apt — il sait resoudre un ensemble de fichiers locaux qui se dependent, la ou paquet par paquet echouerait sur l ordre (Icinga en apporte dix-sept). Six roles branches ; chacun retombe sur le depot distant pour ce que le cache n a PAS fourni, et rien d autre. Eprouve pour de vrai : packages.smallstep.com renvoye vers 127.0.0.1, step-cli desinstalle, cache local efface. Le role rejoue installe 0.30.6-1 depuis le cache et la machine obtient ses certificats. Zero echec. Deux defauts de mon propre outil, trouves en le construisant : Smallstep sert son index NON COMPRESSE (404 sur .gz) donc step-cli n etait jamais mis en cache ; et un depot injoignable faisait continue AVANT d incrementer le total, si bien que le script rapportait 20 sur 20 alors qu il en manquait un. Une garde qui ne peut pas echouer ne garde rien — troisieme fois cette semaine. Corrige puis eprouve en le faisant echouer. Reste hors ligne : NTP externe, et l expedition des alertes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-09-05 08:22:14 -04:00
name: "{{ client_pki_paquets
| difference((paquets_tiers_disponibles | default({})).keys() | list) }}"
state: present
update_cache: true
paquets tiers : passer par le cache du controleur, plus par Internet Trois depots tiers etaient en HTTPS, et client_artefacts pose Acquire::https::Proxy DIRECT — sans quoi le cache du site refuse les tunnels et aucun depot tiers n est joignable. La ligne est juste ; sa consequence ne l avait pas ete vue : ces trois depots CONTOURNENT le cache, et chaque VM neuve allait les chercher sur Internet a sa naissance. step-cli et alloy sont poses par des integrations UNIVERSELLES. Sans lien, une machine neuve n obtenait ni son client d autorite ni ses metriques — elle n entrait dans aucun flux chiffre. Dernier obstacle a une reconstruction hors ligne, et il tenait dans un mot. make cacher-paquets tire les 21 deb aux versions epinglees, empreinte SHA256 verifiee depuis l index du depot. Le role partage paquets_tiers les depose et les installe EN UN SEUL appel a apt — il sait resoudre un ensemble de fichiers locaux qui se dependent, la ou paquet par paquet echouerait sur l ordre (Icinga en apporte dix-sept). Six roles branches ; chacun retombe sur le depot distant pour ce que le cache n a PAS fourni, et rien d autre. Eprouve pour de vrai : packages.smallstep.com renvoye vers 127.0.0.1, step-cli desinstalle, cache local efface. Le role rejoue installe 0.30.6-1 depuis le cache et la machine obtient ses certificats. Zero echec. Deux defauts de mon propre outil, trouves en le construisant : Smallstep sert son index NON COMPRESSE (404 sur .gz) donc step-cli n etait jamais mis en cache ; et un depot injoignable faisait continue AVANT d incrementer le total, si bien que le script rapportait 20 sur 20 alors qu il en manquait un. Une garde qui ne peut pas echouer ne garde rien — troisieme fois cette semaine. Corrige puis eprouve en le faisant echouer. Reste hors ligne : NTP externe, et l expedition des alertes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-09-05 08:22:14 -04:00
when: (client_pki_paquets
| difference((paquets_tiers_disponibles | default({})).keys() | list)) | length > 0
- name: Creer le repertoire STEPPATH des certificats
ansible.builtin.file:
path: "{{ client_pki_steppath }}/certs"
state: directory
owner: root
group: root
mode: "0755"
- name: Deployer le mot de passe du provisioner
ansible.builtin.copy:
content: "{{ client_pki_provisioner_password }}"
dest: "{{ client_pki_steppath }}/provisioner.pass"
owner: root
group: root
mode: "0600"
no_log: true
# L'AUTORITE EST UN CAS A PART, mais pas une exception. Elle n'a pas a « s'enroler »
# aupres d'elle-meme : sa racine est deja sur son disque, et un bootstrap la ferait
# aller la chercher par le reseau, chez elle, en verifiant une empreinte qu'elle vient
# de produire. Elle a en revanche besoin de CERTIFICATS comme tout le monde — sans quoi
# ses propres services (node_exporter) restent en clair, et `client_metrique` echoue.
#
# La distinction est donc : pas d'enrolement, mais emission locale. C'est ce qui permet
# de retirer l'exemption de `client_pki` sans contredire « l'AC est la source de la
# confiance » — elle l'est, et c'est precisement pourquoi elle peut se signer elle-meme.
- name: Reconnaitre l'hote qui PORTE l'autorite
ansible.builtin.set_fact:
client_pki_est_autorite: "{{ inventory_hostname in (groups['serveur_step_ca'] | default([])) }}"
- name: Etablir la confiance dans l'AC interne (bootstrap + installation racine)
ansible.builtin.command:
cmd: >-
step ca bootstrap
--ca-url {{ client_pki_ca_url }}
--fingerprint {{ client_pki_ca_fingerprint }}
--install --force
creates: "{{ client_pki_steppath }}/certs/root_ca.crt"
environment:
STEPPATH: "{{ client_pki_steppath }}"
when: not client_pki_est_autorite | bool
- name: Poser la racine depuis le disque local (l'autorite ne s'enrole pas aupres d'elle-meme)
ansible.builtin.copy:
src: "{{ serveur_step_ca_steppath | default('/etc/step-ca') }}/certs/root_ca.crt"
dest: "{{ client_pki_steppath }}/certs/root_ca.crt"
remote_src: true
owner: root
group: root
mode: "0644"
when: client_pki_est_autorite | bool
- name: Rendre le certificat racine lisible par tous (cert public, requis par les clients TLS)
ansible.builtin.file:
path: "{{ client_pki_steppath }}/certs/root_ca.crt"
mode: "0644"
devis des certificats : disque contre memoire, et l'AC etait expiree 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>
2026-08-08 07:20:56 -04:00
# La validite, pas seulement la forme. Sans ce controle, un certificat expire mais
# portant les bons SAN ne declenchait AUCUNE re-emission : le role ne savait pas se
# soigner, et sur l'hote de l'autorite — ou le renouvellement automatique etait casse —
# rien ne pouvait plus le rattraper. Constate le 2026-08-08.
- name: Verifier que le certificat d hote est encore valide
ansible.builtin.command:
cmd: "openssl x509 -in {{ client_pki_cert }} -noout -checkend {{ client_pki_marge_renouvellement }}"
register: client_pki_validite
changed_when: false
failed_when: false
- name: Lire les SAN du certificat d'hote existant (detection de derive)
ansible.builtin.command:
cmd: "openssl x509 -in {{ client_pki_cert }} -noout -ext subjectAltName"
register: client_pki_san_actuels
changed_when: false
failed_when: false
# Re-emet si le cert est absent OU si un SAN voulu manque (ex: nouvelle exposition
# ajoutee au plan -> client_pki_sans mis a jour). Plus de garde 'creates' aveugle.
devis des certificats : disque contre memoire, et l'AC etait expiree 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>
2026-08-08 07:20:56 -04:00
- name: Obtenir / re-emettre le certificat d'hote (absent, perime ou SAN derives)
ansible.builtin.command:
# `--ca-url` et `--root` EXPLICITES : sur un hote ordinaire ils sont redondants avec
# le `defaults.json` qu'ecrit `step ca bootstrap`, mais l'autorite ne bootstrape pas
# — elle n'aurait donc aucune de ces deux valeurs. Les nommer ici vaut mieux que de
# dependre d'un fichier ecrit par une etape qu'on saute volontairement.
cmd: >-
step ca certificate {{ client_pki_nom_cert }}
{{ client_pki_cert }} {{ client_pki_cle }}
--ca-url {{ client_pki_ca_url }}
--root {{ client_pki_steppath }}/certs/root_ca.crt
--provisioner {{ client_pki_provisioner }}
--provisioner-password-file {{ client_pki_steppath }}/provisioner.pass
{% for s in client_pki_sans | select | unique %}--san {{ s }} {% endfor %}
--force
environment:
STEPPATH: "{{ client_pki_steppath }}"
when: >-
client_pki_san_actuels.rc != 0
devis des certificats : disque contre memoire, et l'AC etait expiree 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>
2026-08-08 07:20:56 -04:00
or client_pki_validite.rc != 0
or (client_pki_sans | select | unique | reject('equalto', '')
| reject('in', client_pki_san_actuels.stdout | default(''))
| list | length > 0)
changed_when: true
notify: Recharger les consommateurs du cert
site : un SITE a bien un plan — et huit defauts que lui seul pouvait reveler 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>
2026-08-25 13:15:34 -04:00
# POSÉ À CHAQUE PASSAGE, PAS SEULEMENT À L'ÉMISSION.
#
# `step ca certificate` réécrit la clé avec ses propres droits, et le renouvellement
# automatique aussi. Ne régler les droits qu'au moment où le certificat change les
# perdrait au premier renouvellement — une panne qui surviendrait des semaines plus tard,
# sans rapport visible avec cette tâche.
le site reconstruit depuis zero : 7/7, 0 echec, et seize corrections SITE-Chezlepro n avait jamais ete rase. La limite qu on repetait partout — l infrastructure d accueil n a jamais ete reconstruite depuis zero — se lisait comme de la prudence. C etait seize defauts que rien d autre n aurait pu reveler. Un locataire naît dans un monde deja peuple : le site lui fournit paquets, noms, genome, heure et depot. Un site n a personne au-dessus, sauf sa frontiere. Onze des seize murs viennent de la. DEUX CAPACITES QUI N EXISTAIENT PAS. make site-raser — rien ne detruisait les machines du site, donc la limite etait un trou d outillage. make forge-amorcer — la forge naît vide et le runner y clone ; l amorcage part du poste, seul endroit qui detienne alors le genome. TROIS GARDES QUI VERIFIAIENT LA FORME. La plus couteuse des familles : elles donnent l apparence d une verification. Le resolveur comparait des adresses au lieu de mesurer si la resolution aboutit, et protegeait ainsi l etat casse. L administration etait reconnue a son port. Un flux a deux paires n obtenait qu une branche. UN ECART DE SECURITE. Le PostgreSQL du site servait le certificat auto-signe de Debian, sans reseaux autorises ni hostssl — invisible tant qu aucun client n exigeait la verification. Le defaut n a pas casse la construction : la construction a revele le defaut. UN ACCES ACCIDENTEL. Celui de l exploitant tenait au chevauchement d adressage que le renumerotage a supprime. Separer les index n a pas cause le probleme, il a retire le hasard qui le masquait. La sequence du premier jour est ecrite : runbooks §9, avec les seize murs et ce que chacun enseigne. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-12 19:08:31 -04:00
# LE CONSOMMATEUR N'EXISTE PAS ENCORE AU PREMIER JOUR (2026-09-12).
#
# `client_pki` (couche `pki_client`) precede `serveur_forgejo` (couche `apps`), et c'est
# le paquet de Forgejo qui cree le groupe `git`. Sur une machine deja construite le
# groupe est la, herite d'un passage precedent ; a froid il n'existe pas :
#
# chgrp failed: failed to look up group git
#
# ET C'ETAIT UN BLOCAGE CIRCULAIRE, pas un ordre qui converge : l'echec arretait le play
# pour cet hote AVANT la couche qui aurait cree le groupe. Rejouer n'y changeait rien —
# mesure deux fois, le 2026-09-12.
#
# ON NE FABRIQUE PAS LE GROUPE ICI : le paquet du service le creera avec le GID qu'il
# veut, et un groupe pose d'avance l'obligerait a composer avec le notre. On constate
# son absence, on laisse la cle en `root:root` — donc FERMEE, jamais plus ouverte — et
# le passage suivant, une fois le service installe, donne l'acces.
- name: Le groupe qui doit lire la clé existe-t-il ?
ansible.builtin.getent:
database: group
key: "{{ client_pki_cle_groupe }}"
fail_key: false
register: client_pki_groupe_lu
when: client_pki_cle_groupe != 'root'
changed_when: false
site : un SITE a bien un plan — et huit defauts que lui seul pouvait reveler 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>
2026-08-25 13:15:34 -04:00
- name: Donner accès à la clé privée au service qui doit la lire
ansible.builtin.file:
path: "{{ client_pki_cle }}"
owner: root
group: "{{ client_pki_cle_groupe }}"
mode: "{{ client_pki_cle_mode }}"
le site reconstruit depuis zero : 7/7, 0 echec, et seize corrections SITE-Chezlepro n avait jamais ete rase. La limite qu on repetait partout — l infrastructure d accueil n a jamais ete reconstruite depuis zero — se lisait comme de la prudence. C etait seize defauts que rien d autre n aurait pu reveler. Un locataire naît dans un monde deja peuple : le site lui fournit paquets, noms, genome, heure et depot. Un site n a personne au-dessus, sauf sa frontiere. Onze des seize murs viennent de la. DEUX CAPACITES QUI N EXISTAIENT PAS. make site-raser — rien ne detruisait les machines du site, donc la limite etait un trou d outillage. make forge-amorcer — la forge naît vide et le runner y clone ; l amorcage part du poste, seul endroit qui detienne alors le genome. TROIS GARDES QUI VERIFIAIENT LA FORME. La plus couteuse des familles : elles donnent l apparence d une verification. Le resolveur comparait des adresses au lieu de mesurer si la resolution aboutit, et protegeait ainsi l etat casse. L administration etait reconnue a son port. Un flux a deux paires n obtenait qu une branche. UN ECART DE SECURITE. Le PostgreSQL du site servait le certificat auto-signe de Debian, sans reseaux autorises ni hostssl — invisible tant qu aucun client n exigeait la verification. Le defaut n a pas casse la construction : la construction a revele le defaut. UN ACCES ACCIDENTEL. Celui de l exploitant tenait au chevauchement d adressage que le renumerotage a supprime. Separer les index n a pas cause le probleme, il a retire le hasard qui le masquait. La sequence du premier jour est ecrite : runbooks §9, avec les seize murs et ce que chacun enseigne. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-12 19:08:31 -04:00
when:
- client_pki_cle_groupe != 'root' or client_pki_cle_mode != '0600'
- client_pki_cle_groupe == 'root'
or (client_pki_groupe_lu.ansible_facts.getent_group | default({}))
.get(client_pki_cle_groupe) is not none
site : un SITE a bien un plan — et huit defauts que lui seul pouvait reveler 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>
2026-08-25 13:15:34 -04:00
notify: Recharger les consommateurs du cert
le site reconstruit depuis zero : 7/7, 0 echec, et seize corrections SITE-Chezlepro n avait jamais ete rase. La limite qu on repetait partout — l infrastructure d accueil n a jamais ete reconstruite depuis zero — se lisait comme de la prudence. C etait seize defauts que rien d autre n aurait pu reveler. Un locataire naît dans un monde deja peuple : le site lui fournit paquets, noms, genome, heure et depot. Un site n a personne au-dessus, sauf sa frontiere. Onze des seize murs viennent de la. DEUX CAPACITES QUI N EXISTAIENT PAS. make site-raser — rien ne detruisait les machines du site, donc la limite etait un trou d outillage. make forge-amorcer — la forge naît vide et le runner y clone ; l amorcage part du poste, seul endroit qui detienne alors le genome. TROIS GARDES QUI VERIFIAIENT LA FORME. La plus couteuse des familles : elles donnent l apparence d une verification. Le resolveur comparait des adresses au lieu de mesurer si la resolution aboutit, et protegeait ainsi l etat casse. L administration etait reconnue a son port. Un flux a deux paires n obtenait qu une branche. UN ECART DE SECURITE. Le PostgreSQL du site servait le certificat auto-signe de Debian, sans reseaux autorises ni hostssl — invisible tant qu aucun client n exigeait la verification. Le defaut n a pas casse la construction : la construction a revele le defaut. UN ACCES ACCIDENTEL. Celui de l exploitant tenait au chevauchement d adressage que le renumerotage a supprime. Separer les index n a pas cause le probleme, il a retire le hasard qui le masquait. La sequence du premier jour est ecrite : runbooks §9, avec les seize murs et ce que chacun enseigne. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-12 19:08:31 -04:00
- name: Dire que l'accès à la clé attend son consommateur
ansible.builtin.debug:
msg: >-
Le groupe « {{ client_pki_cle_groupe }} » n'existe pas encore : la clé reste en
root:root, donc FERMEE. Le service qui doit la lire n'est pas installé — le
passage suivant, après sa couche, lui donnera l'accès.
when:
- client_pki_cle_groupe != 'root'
- (client_pki_groupe_lu.ansible_facts.getent_group | default({}))
.get(client_pki_cle_groupe) is none
site : un SITE a bien un plan — et huit defauts que lui seul pouvait reveler 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>
2026-08-25 13:15:34 -04:00
# Le certificat est public. `step` l'ecrit en 0600 comme la cle ; on le rend lisible, sans
# quoi un service non-root echoue sur le CERT apres avoir obtenu la CLE — et le message
# parle de permission sur un fichier que rien ne justifie de proteger.
- name: Rendre le certificat d'hôte lisible (il est public par nature)
ansible.builtin.file:
path: "{{ client_pki_cert }}"
owner: root
group: root
mode: "{{ client_pki_cert_mode }}"
notify: Recharger les consommateurs du cert
- name: Deployer l'unite systemd de renouvellement
ansible.builtin.template:
src: cert-renewer@.service.j2
dest: /etc/systemd/system/cert-renewer@.service
owner: root
group: root
mode: "0644"
notify: Recharger systemd
- name: Deployer le minuteur de renouvellement
ansible.builtin.template:
src: cert-renewer@.timer.j2
dest: /etc/systemd/system/cert-renewer@.timer
owner: root
group: root
mode: "0644"
notify: Recharger systemd
- name: Activer le renouvellement automatique du certificat d'hote
ansible.builtin.systemd:
name: "cert-renewer@{{ client_pki_nom_cert }}.timer"
enabled: true
state: started
daemon_reload: true
supervision : la sonde se declare dans le role, comme le flux 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
2026-09-09 21:45:49 -04:00
# --- Sonde de supervision (docs/supervision-conception.md) --------------------------
#
# LE ROLE QUI POSSEDE LA VERITE DEPOSE SA PROPRE SONDE. Il connait ses chemins, ses
# seuils, son consommateur — le superviseur, lui, ne connait que le verdict. C'est la
# meme repartition que pour les flux : le role declare, le moteur derive.
#
# Le porteur (`client_sante`) fait tourner tout ce qui vit dans ce repertoire et pousse
# un resultat passif par sonde. Il n'a pas a savoir ce que celle-ci mesure.
- name: Assurer le repertoire des sondes de supervision
ansible.builtin.file:
path: /usr/local/lib/setops/sondes
state: directory
owner: root
group: root
mode: "0755"
- name: Deposer la sonde du certificat d'hote
ansible.builtin.template:
src: sonde-certificat.sh.j2
dest: /usr/local/lib/setops/sondes/certificat.sh
owner: root
group: root
mode: "0750"