2026-06-24 20:17:46 -04:00
|
|
|
---
|
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.
|
|
|
|
|
|
2026-07-07 06:45:12 -04:00
|
|
|
# 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.
|
|
|
|
|
- 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] }}"
|
|
|
|
|
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)
|
2026-06-24 20:17:46 -04:00
|
|
|
ansible.builtin.assert:
|
|
|
|
|
that:
|
|
|
|
|
- client_pki_ca_fingerprint | length > 0
|
|
|
|
|
- client_pki_provisioner_password | length > 0
|
|
|
|
|
fail_msg: >-
|
2026-07-07 06:45:12 -04:00
|
|
|
Empreinte du root CA indisponible (step-ca joignable et déployé ?) ;
|
|
|
|
|
client_pki_provisioner_password requis (via Ansible Vault).
|
2026-06-24 20:17:46 -04:00
|
|
|
|
|
|
|
|
- name: Assurer le repertoire des trousseaux apt
|
|
|
|
|
ansible.builtin.file:
|
|
|
|
|
path: /etc/apt/keyrings
|
|
|
|
|
state: directory
|
|
|
|
|
owner: root
|
|
|
|
|
group: root
|
|
|
|
|
mode: "0755"
|
|
|
|
|
|
2026-08-09 14:05:25 -04:00
|
|
|
# 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.
|
|
|
|
|
- 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.
|
2026-06-24 20:17:46 -04:00
|
|
|
- name: Telecharger la cle de signature Smallstep
|
2026-08-09 14:05:25 -04:00
|
|
|
when: not telecharger_la_cle_de_signature_smallste_present.stat.exists
|
2026-06-24 20:17:46 -04:00
|
|
|
ansible.builtin.get_url:
|
|
|
|
|
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
|
2026-06-24 20:17:46 -04:00
|
|
|
|
|
|
|
|
- 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
|
2026-06-24 20:17:46 -04:00
|
|
|
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) }}"
|
2026-06-24 20:17:46 -04:00
|
|
|
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
|
2026-06-24 20:17:46 -04:00
|
|
|
|
|
|
|
|
- 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
|
|
|
|
|
|
deployer : l'ordre des couches, et l'autorite qui se signe elle-meme
`make deployer` triait les groupes alphabetiquement apres le socle :
`client_metrique` passait avant `serveur_step_ca`, donc exigeait un certificat
que l'autorite, pas encore deployee, ne pouvait pas avoir emis — sur l'hote de
l'AC lui-meme. `docs/couches-deploiement.yml` existe pour definir cet ordre et
dit « integrations deployees en dernier » ; `make site` le lit, `deployer` ne
l'avait jamais lu. Meme registre desormais.
Deux politiques universelles se contredisaient : `client_pki` exemptait l'AC,
`client_metrique` refuse toute exemption. L'exemption confondait « ne pas
s'enroler » et « ne pas avoir de certificat ». `client_pki` distingue les deux
chemins : bootstrap pour les autres, EMISSION LOCALE sur l'AC. L'exemption
disparait, la doctrine reste.
Effet de bord instructif : `step ca bootstrap` ecrit aussi le defaults.json qui
porte l'URL de l'AC. En sautant le bootstrap on perdait l'information sans le
voir. `--ca-url` et `--root` sont explicites pour tous les hotes.
Mesure : infra-pki-01 et infra-dns-01 entierement deployees, aucun echec,
step-ca health=ok, powerdns resout la zone souveraine, et les deux hotes portent
un certificat de l'AC interne.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 23:57:01 -04:00
|
|
|
# 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([])) }}"
|
|
|
|
|
|
2026-06-24 20:17:46 -04:00
|
|
|
- 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 }}"
|
deployer : l'ordre des couches, et l'autorite qui se signe elle-meme
`make deployer` triait les groupes alphabetiquement apres le socle :
`client_metrique` passait avant `serveur_step_ca`, donc exigeait un certificat
que l'autorite, pas encore deployee, ne pouvait pas avoir emis — sur l'hote de
l'AC lui-meme. `docs/couches-deploiement.yml` existe pour definir cet ordre et
dit « integrations deployees en dernier » ; `make site` le lit, `deployer` ne
l'avait jamais lu. Meme registre desormais.
Deux politiques universelles se contredisaient : `client_pki` exemptait l'AC,
`client_metrique` refuse toute exemption. L'exemption confondait « ne pas
s'enroler » et « ne pas avoir de certificat ». `client_pki` distingue les deux
chemins : bootstrap pour les autres, EMISSION LOCALE sur l'AC. L'exemption
disparait, la doctrine reste.
Effet de bord instructif : `step ca bootstrap` ecrit aussi le defaults.json qui
porte l'URL de l'AC. En sautant le bootstrap on perdait l'information sans le
voir. `--ca-url` et `--root` sont explicites pour tous les hotes.
Mesure : infra-pki-01 et infra-dns-01 entierement deployees, aucun echec,
step-ca health=ok, powerdns resout la zone souveraine, et les deux hotes portent
un certificat de l'AC interne.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 23:57:01 -04:00
|
|
|
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
|
2026-06-24 20:17:46 -04:00
|
|
|
|
2026-07-04 18:45:16 -04:00
|
|
|
- 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"
|
|
|
|
|
|
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
|
|
|
|
|
|
2026-07-05 18:43:23 -04:00
|
|
|
- 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.
|
2026-08-08 07:20:56 -04:00
|
|
|
- name: Obtenir / re-emettre le certificat d'hote (absent, perime ou SAN derives)
|
2026-06-24 20:17:46 -04:00
|
|
|
ansible.builtin.command:
|
deployer : l'ordre des couches, et l'autorite qui se signe elle-meme
`make deployer` triait les groupes alphabetiquement apres le socle :
`client_metrique` passait avant `serveur_step_ca`, donc exigeait un certificat
que l'autorite, pas encore deployee, ne pouvait pas avoir emis — sur l'hote de
l'AC lui-meme. `docs/couches-deploiement.yml` existe pour definir cet ordre et
dit « integrations deployees en dernier » ; `make site` le lit, `deployer` ne
l'avait jamais lu. Meme registre desormais.
Deux politiques universelles se contredisaient : `client_pki` exemptait l'AC,
`client_metrique` refuse toute exemption. L'exemption confondait « ne pas
s'enroler » et « ne pas avoir de certificat ». `client_pki` distingue les deux
chemins : bootstrap pour les autres, EMISSION LOCALE sur l'AC. L'exemption
disparait, la doctrine reste.
Effet de bord instructif : `step ca bootstrap` ecrit aussi le defaults.json qui
porte l'URL de l'AC. En sautant le bootstrap on perdait l'information sans le
voir. `--ca-url` et `--root` sont explicites pour tous les hotes.
Mesure : infra-pki-01 et infra-dns-01 entierement deployees, aucun echec,
step-ca health=ok, powerdns resout la zone souveraine, et les deux hotes portent
un certificat de l'AC interne.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 23:57:01 -04:00
|
|
|
# `--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.
|
2026-06-24 20:17:46 -04:00
|
|
|
cmd: >-
|
|
|
|
|
step ca certificate {{ client_pki_nom_cert }}
|
|
|
|
|
{{ client_pki_cert }} {{ client_pki_cle }}
|
deployer : l'ordre des couches, et l'autorite qui se signe elle-meme
`make deployer` triait les groupes alphabetiquement apres le socle :
`client_metrique` passait avant `serveur_step_ca`, donc exigeait un certificat
que l'autorite, pas encore deployee, ne pouvait pas avoir emis — sur l'hote de
l'AC lui-meme. `docs/couches-deploiement.yml` existe pour definir cet ordre et
dit « integrations deployees en dernier » ; `make site` le lit, `deployer` ne
l'avait jamais lu. Meme registre desormais.
Deux politiques universelles se contredisaient : `client_pki` exemptait l'AC,
`client_metrique` refuse toute exemption. L'exemption confondait « ne pas
s'enroler » et « ne pas avoir de certificat ». `client_pki` distingue les deux
chemins : bootstrap pour les autres, EMISSION LOCALE sur l'AC. L'exemption
disparait, la doctrine reste.
Effet de bord instructif : `step ca bootstrap` ecrit aussi le defaults.json qui
porte l'URL de l'AC. En sautant le bootstrap on perdait l'information sans le
voir. `--ca-url` et `--root` sont explicites pour tous les hotes.
Mesure : infra-pki-01 et infra-dns-01 entierement deployees, aucun echec,
step-ca health=ok, powerdns resout la zone souveraine, et les deux hotes portent
un certificat de l'AC interne.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 23:57:01 -04:00
|
|
|
--ca-url {{ client_pki_ca_url }}
|
|
|
|
|
--root {{ client_pki_steppath }}/certs/root_ca.crt
|
2026-06-24 20:17:46 -04:00
|
|
|
--provisioner {{ client_pki_provisioner }}
|
|
|
|
|
--provisioner-password-file {{ client_pki_steppath }}/provisioner.pass
|
2026-07-02 00:00:10 -04:00
|
|
|
{% for s in client_pki_sans | select | unique %}--san {{ s }} {% endfor %}
|
2026-06-24 20:17:46 -04:00
|
|
|
--force
|
|
|
|
|
environment:
|
|
|
|
|
STEPPATH: "{{ client_pki_steppath }}"
|
2026-07-05 18:43:23 -04:00
|
|
|
when: >-
|
|
|
|
|
client_pki_san_actuels.rc != 0
|
2026-08-08 07:20:56 -04:00
|
|
|
or client_pki_validite.rc != 0
|
2026-07-05 18:43:23 -04:00
|
|
|
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
|
2026-06-24 20:17:46 -04:00
|
|
|
|
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.
|
|
|
|
|
- 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 }}"
|
|
|
|
|
when: client_pki_cle_groupe != 'root' or client_pki_cle_mode != '0600'
|
|
|
|
|
notify: Recharger les consommateurs du cert
|
|
|
|
|
|
|
|
|
|
# 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
|
|
|
|
|
|
2026-06-24 20:17:46 -04:00
|
|
|
- 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
|