Compare commits

...

4 commits

Author SHA1 Message Date
acc3f83574 openldap : le refus nomme le compte sans secret, la censure partait avec
`no_log` protege une VALEUR ; cette assertion n en imprime aucune — son message
ne porte que des NOMS, celui du compte et celui de la cle attendue. La censure
ne cachait donc rien a personne, et supprimait le seul texte capable de dire
LEQUEL des quatre comptes manque.

Ce que le deploiement rendait a la place, une fois par compte absent :
« failed: [<hote>] (item=None) => {"censored": …} ». L etiquette de boucle
etant censuree elle aussi, quatre comptes donnaient trois echecs indistincts.
Le meme raisonnement a deja ete tenu pour le compte de liaison de l annuaire.

Assisted-by: Claude Opus 5
2026-09-29 06:27:28 -04:00
a6907fed19 clonage : serialise par stockage, le parallelisme reste par sequence
Proxmox tient un verrou PAR STOCKAGE pendant l allocation du disque. Les clones
partaient a quatre de front (`flotte-creer PARALLELE`), se le disputaient, et
celui qui attendait trop rendait « clone failed: can't lock file … - got
timeout » — un message qui nomme le verrou, jamais le parallelisme qui l a
provoque.

A deux VM rien ne paraissait ; a cinq, une creation sur cinq echouait et la
reconstruction s arretait. La cle du verrou porte le noeud ET le stockage : deux
stockages distincts clonent toujours de front, et seule la section qui contend
est prise une a la fois, dans l ordre ou la sequence du plan la demande.

Assisted-by: Claude Opus 5
2026-09-29 06:01:14 -04:00
1cb85094ea client_pki : la cle de signature vient du depot, non d un CDN tiers
Le depot porte deja cette cle : `serveur_step_ca` la pose depuis ses `files/`,
et les deux fichiers ont le meme SHA-256. Le client la telechargeait.

Une flotte d AMORCAGE n a ni serveur d artefacts ni cache, donc le schema reste
`https` et chaque hote sort en direct ; le telechargement echouait trois
amorcages sur quatre, sur une bordure amont que rien ici ne controle.

Le repli reste. Verifie sur une grappe jetable : la copie joue sur l hote qui
echouait, le telechargement est saute, et le CDN n est appele aucune fois.

Assisted-by: Claude Opus 5
2026-09-29 06:01:14 -04:00
bff1396af3 resoudre_annuaire : un plan muet fait refuser, plus de nom devine
Le defaut `id-ldap-01` tenait des que le plan se taisait : la derivation depuis
`applications.<app>.hote` etait sautee, et le role batissait `ldaps://<ce nom>`
— une machine qui n est dans aucun plan. Le consommateur echouait au premier
bind, apres un deploiement passe au vert de bout en bout.

La derivation avait ete ajoutee pour corriger exactement cela ; le defaut est
reste et ramenait l ancien comportement. Il est desormais vide, et une
assertion refuse AU DEPLOIEMENT. Une instance garde le droit de poser l hote
explicitement : ce qui disparait est la valeur DEVINEE, pas la surcharge.

Assisted-by: Claude Opus 5
2026-09-29 04:16:02 -04:00
7 changed files with 96 additions and 5 deletions

View file

@ -1456,6 +1456,18 @@ verifier-deploiement: ansible-runtime ## Verifie l'etat de la flotte apres deplo
ansible-playbook -i $(INVENTAIRE_PRODUCTION) "$$playbook" --limit "$(HOTE)" --check --diff; \
done
# LE CLONAGE SE SERIALISE PAR STOCKAGE, et c'est la granularite de Proxmox
# lui-meme : il tient un verrou PAR STOCKAGE pendant l'allocation du disque. Deux
# clones simultanes sur le meme stockage se le disputent, et celui qui attend trop
# rend « clone failed: can't lock file '/var/lock/pve-manager/pve-storage-<nom>' -
# got timeout » — un message qui nomme le verrou, jamais le parallelisme qui l'a
# provoque. A deux VM rien ne parait ; a cinq, une creation sur cinq echoue, et la
# reconstruction s'arrete sans que rien ne dise pourquoi elle tenait a deux.
#
# LA CLE PORTE LE NOEUD ET LE STOCKAGE, donc deux stockages distincts clonent
# TOUJOURS en parallele : `flotte-creer PARALLELE=n` garde tout son sens, et seule
# la section qui contend est prise un a la fois, dans l'ordre ou la sequence du
# plan la demande.
cloner-vm: ansible-runtime ## Clone une VM depuis le gabarit dore — HOTE=<nom> VMID=<id>
@if [[ -z "$(HOTE)" || -z "$(VMID)" ]]; then \
printf '%s\n' 'Refus: relancer avec HOTE=nom VMID=id_clone.'; \
@ -1530,7 +1542,8 @@ cloner-vm: ansible-runtime ## Clone une VM depuis le gabarit dore — HOTE=<nom>
;; \
esac; \
fi; \
ansible-playbook -i localhost, $(PLAYBOOK_PROXMOX_CLONER_VM) "$${vault_args[@]}" "$${extra_vars[@]}"
verrou="$${TMPDIR:-/tmp}/setops-clone-$$(printf '%s_%s' '$(NOEUD_PROXMOX)' '$(STOCKAGE_PROXMOX)' | tr -c 'A-Za-z0-9._-' '_').lock"; \
flock "$$verrou" ansible-playbook -i localhost, $(PLAYBOOK_PROXMOX_CLONER_VM) "$${vault_args[@]}" "$${extra_vars[@]}"
creer-vm: _instance-requise ## Cree une VM et attend qu'elle soit joignable — HOTE=<nom>
@set -e; \

View file

@ -67,6 +67,21 @@ client_pki_provisioner_password: "{{ vault_step_ca_provisioner_password | defaul
# Depot apt officiel Smallstep (partage avec serveur_step_ca).
client_pki_depot_cle_url: "{{ client_pki_depot_schema }}://packages.smallstep.com/keys/apt/repo-signing-key.gpg"
client_pki_depot_cle_fichier: "/etc/apt/keyrings/smallstep.asc"
# LA MEME CLE QUE `serveur_step_ca` POSE, et c'est le DEPOT qui la porte : les deux
# fichiers ont le meme SHA-256. La chercher ici d'abord evite de sortir sur un CDN tiers
# pour des octets qu'on possede.
#
# CE N'EST PAS THEORIQUE POUR UNE FLOTTE D'AMORCAGE. Elle n'a par definition ni serveur
# d'artefacts ni cache, donc `client_pki_depot_schema` reste `https` et chaque hote sort
# EN DIRECT ; le telechargement echouait alors trois amorcages sur quatre, avec cinq
# reprises, sur une bordure amont que rien ici ne controle.
#
# Le chemin traverse `serveur_step_ca` plutot que de dupliquer le fichier : deux copies
# d'une meme cle divergent le jour ou l'amont la remplace, et rien ne dirait laquelle
# fait foi.
client_pki_depot_cle_locale: >-
{{ role_path }}/../serveur_step_ca/files/smallstep-repo-signing-key.asc
client_pki_depot_uri: "{{ client_pki_depot_schema }}://packages.smallstep.com/stable/debian"
client_pki_depot_suite: "debs"

View file

@ -103,8 +103,36 @@
- telecharger_la_cle_de_signature_smallste_present.stat.exists
- (telecharger_la_cle_de_signature_smallste_present.stat.size | default(0)) == 0
- name: Voir si le depot porte la cle de signature Smallstep
ansible.builtin.stat:
path: "{{ client_pki_depot_cle_locale }}"
delegate_to: localhost
become: false
changed_when: false
check_mode: false
register: client_pki_cle_depot_locale
# LE DEPOT D'ABORD, LE RESEAU ENSUITE. `serveur_step_ca` pose deja cette cle depuis ses
# `files/` sans rien demander a personne ; le client la telechargeait. Les deux fichiers
# sont identiques — meme SHA-256 — si bien que la sortie n'achetait rien et exposait
# chaque amorcage a une bordure tierce.
- name: Poser la cle de signature Smallstep depuis le depot
ansible.builtin.copy:
src: "{{ client_pki_depot_cle_locale }}"
dest: "{{ client_pki_depot_cle_fichier }}"
owner: root
group: root
mode: "0644"
when:
- not (telecharger_la_cle_de_signature_smallste_present.stat.exists and telecharger_la_cle_de_signature_smallste_present.stat.size | default(0) > 0)
- client_pki_cle_depot_locale.stat.exists
# LE REPLI RESTE, et il n'est pas mort : un depot qui ne porterait plus ce fichier doit
# encore pouvoir amorcer. Ce qui change est l'ORDRE — on ne sort que faute de mieux.
- name: Telecharger la cle de signature Smallstep
when: not (telecharger_la_cle_de_signature_smallste_present.stat.exists and telecharger_la_cle_de_signature_smallste_present.stat.size | default(0) > 0)
when:
- not (telecharger_la_cle_de_signature_smallste_present.stat.exists and telecharger_la_cle_de_signature_smallste_present.stat.size | default(0) > 0)
- not client_pki_cle_depot_locale.stat.exists
# `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

View file

@ -32,7 +32,7 @@ d'annuaire.
## Variables
| Variable | Défaut | Rôle |
| --- | --- | --- |
| `resoudre_annuaire_hote` | `id-ldap-01` | **Le** point où le nom d'hôte est fixé (surchargeable par instance) |
| `resoudre_annuaire_hote` | `""` | Dérivé du PLAN (`applications.<app>.hote`) ; surchargeable par instance. Vide et non dérivé, le rôle REFUSE plutôt que de deviner |
| `resoudre_annuaire_port` | `636` | Port LDAPS |
| `resoudre_annuaire_encryption` | `ldaps` | Schéma de l'URI |
| `resoudre_annuaire_bind_cn` | `admin` | CN du compte de bind |

View file

@ -5,7 +5,19 @@
# aucune machine du plan (l'annuaire vit sur `idm-01`), et Postfix ne pouvait pas se lier.
# Constaté le 2026-08-07. Le repli ci-dessous ne sert que si le plan est muet.
resoudre_annuaire_app: "openldap"
resoudre_annuaire_hote: "id-ldap-01"
# VIDE, ET C'EST LE DEFAUT LUI-MEME QUI ETAIT LE PIEGE. Un nom ecrit ici tient des
# que le plan se tait : la derivation est sautee, le role bâtit
# `ldaps://<ce nom>.<domaine>`, et le consommateur echoue a l'execution sur
# « Unable to bind » en nommant une machine qui n'est dans aucun plan.
#
# La correction qui a introduit la derivation depuis le plan a laisse ce defaut en
# place ; il ramene donc l'ancien comportement chaque fois que l'application n'est
# pas au plan. Vide, l'assertion qui suit refuse AU DEPLOIEMENT, la ou le nom
# devine ne se voyait qu'au premier bind.
#
# Une instance garde le droit de le poser explicitement : ce qui disparait est la
# valeur DEVINEE, pas la surcharge.
resoudre_annuaire_hote: ""
resoudre_annuaire_port: 636
resoudre_annuaire_encryption: "ldaps"
# COMPTE DE LIAISON — CHAQUE CONSOMMATEUR A LE SIEN (2026-09-13).

View file

@ -37,6 +37,17 @@
resoudre_annuaire_hote: "{{ (applications | default({})).get(resoudre_annuaire_app, {}).get('hote') }}"
when: ((applications | default({})).get(resoudre_annuaire_app, {}).get('hote') | default('', true)) | length > 0
- name: Exiger que le plan nomme l'hôte de l'annuaire
ansible.builtin.assert:
that:
- resoudre_annuaire_hote | length > 0
fail_msg: >-
L'application « {{ resoudre_annuaire_app }} » n'est pas au plan, et aucune
instance ne pose `resoudre_annuaire_hote` : rien ne nomme l'annuaire. Le rôle
qui le demande ne peut pas s'y lier. Un nom deviné ici produirait
« Unable to bind » sur une machine qui n'est dans aucun plan — et le
déploiement passerait au VERT jusque-là.
# set_fact (pas les defaults) pour que ces valeurs persistent dans le rôle consommateur
# après include_role (les defaults du rôle inclus ne fuient pas hors de son exécution).
- name: Dériver la connexion à l'annuaire

View file

@ -149,6 +149,19 @@
#
# Quatre services interrogeaient l'annuaire avec son compte d'administration. Voir
# `defaults/main.yml` pour le raisonnement ; ici on cree les comptes et on pose les droits.
# UNE GARDE CENSUREE NE GARDE PAS. `no_log` protege legitimement une VALEUR ; ici
# la tache n'en imprime aucune — son `fail_msg` ne porte que des NOMS, celui du
# compte et celui de la cle attendue. La censure ne cachait donc rien a personne,
# et supprimait le seul message capable de dire LEQUEL des quatre comptes manque.
#
# Ce que le deploiement rendait a sa place, une fois par compte absent :
#
# failed: [<hote>] (item=None) => {"censored": "the output has been hidden
# due to the fact that 'no_log: true' was specified for this result"}
#
# « item=None » parce que l'etiquette de boucle est censuree elle aussi : quatre
# comptes, trois echecs, et rien pour les distinguer. Le meme raisonnement a deja
# ete tenu pour le compte de liaison de `resoudre_annuaire`.
- name: Exiger un secret pour chaque compte de service
ansible.builtin.assert:
that:
@ -160,7 +173,6 @@
loop: "{{ serveur_openldap_comptes_service }}"
loop_control:
label: "{{ item.cn }}"
no_log: true
- name: Creer les comptes de service de l'annuaire
community.general.ldap_entry: