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>
113 lines
4.6 KiB
YAML
113 lines
4.6 KiB
YAML
---
|
|
# Devis des certificats — RELEVE seul, sur toute la flotte. Ne modifie rien (D-23).
|
|
#
|
|
# Le defaut qu'il traque est connu et recurrent : un certificat renouvele SUR DISQUE
|
|
# mais toujours servi PERIME depuis la memoire du service, tant que nginx / postfix /
|
|
# dovecot / slapd n'ont pas ete recharges. Rien ne le signale — le fichier est bon,
|
|
# `step` est content, et le navigateur voit un certificat expire.
|
|
#
|
|
# La methode ne suppose rien du service : on releve l'empreinte du certificat sur
|
|
# disque, puis l'empreinte REELLEMENT SERVIE par chaque port TLS en ecoute. Un port
|
|
# qui sert un certificat portant le meme sujet mais une autre empreinte tient une
|
|
# copie perimee en memoire.
|
|
#
|
|
# make certificats-plan
|
|
|
|
- name: Devis des certificats — disque contre mémoire
|
|
hosts: client_pki
|
|
become: true
|
|
gather_facts: true
|
|
|
|
vars:
|
|
devis_certificats_sortie: "{{ playbook_dir }}/../../instance/devis-certificats.json"
|
|
|
|
tasks:
|
|
# PRECEDENCE — `include_vars` au niveau du play PRIME sur les `group_vars`.
|
|
# Charger les defauts d'un role tels quels ferait donc mentir le devis : il
|
|
# comparerait au defaut du depot au lieu de la valeur reellement declaree par
|
|
# l'inventaire. Mesure du 2026-08-08 : le premier jet rapportait
|
|
# `client_pki_reload_services: []` sur les 14 hotes alors que quatre groupes le
|
|
# declarent. Le devis portait exactement le defaut qu'il est cense traquer.
|
|
#
|
|
# On retient donc ce que l'inventaire declare AVANT de charger les defauts, puis
|
|
# on le reimpose. (Charger sous un `name:` ne marche pas : les defauts se citent
|
|
# entre eux — `client_pki_cert` reference `client_pki_steppath` — et ces
|
|
# references ne se resolvent plus une fois enfermees dans un dictionnaire.)
|
|
- name: Retenir ce que l'inventaire déclare pour client_pki
|
|
ansible.builtin.set_fact:
|
|
devis_inventaire: >-
|
|
{{ hostvars[inventory_hostname] | dict2items
|
|
| selectattr('key', 'match', '^client_pki_') | items2dict }}
|
|
|
|
- name: Charger les défauts du rôle client_pki
|
|
ansible.builtin.include_vars:
|
|
dir: "{{ playbook_dir }}/../../roles/client_pki/defaults"
|
|
|
|
# Cle par cle : `set_fact` ne prend pas un dictionnaire entier en argument libre —
|
|
# il l'accepte sans erreur et n'en fait rien. C'est ainsi que le premier correctif
|
|
# a paru fonctionner tout en laissant le devis mentir.
|
|
- name: Rendre le dernier mot à l'inventaire
|
|
ansible.builtin.set_fact:
|
|
"{{ item.key }}": "{{ item.value }}"
|
|
loop: "{{ devis_inventaire | dict2items }}"
|
|
loop_control:
|
|
label: "{{ item.key }}"
|
|
|
|
- name: Relever le certificat sur disque
|
|
ansible.builtin.command:
|
|
argv:
|
|
- openssl
|
|
- x509
|
|
- -in
|
|
- "{{ client_pki_cert }}"
|
|
- -noout
|
|
- -subject
|
|
- -fingerprint
|
|
- -enddate
|
|
- -issuer
|
|
register: devis_cert_disque
|
|
changed_when: false
|
|
failed_when: false
|
|
|
|
- name: Relever les ports TLS en écoute et le certificat que chacun sert
|
|
ansible.builtin.shell:
|
|
executable: /bin/bash
|
|
cmd: |
|
|
set -uo pipefail
|
|
for P in $(ss -H -ltn 2>/dev/null | awk '{print $4}' | sed 's/.*://' | sort -un); do
|
|
# STARTTLS n'est pas optionnel sur ces ports : sans le dialecte, la poignee
|
|
# de main n'a pas lieu et on conclurait a tort que le port ne sert rien.
|
|
case "$P" in
|
|
25|587) ARG="-starttls smtp" ;;
|
|
143) ARG="-starttls imap" ;;
|
|
110) ARG="-starttls pop3" ;;
|
|
389) ARG="-starttls ldap" ;;
|
|
*) ARG="" ;;
|
|
esac
|
|
OUT=$(timeout 5 openssl s_client -connect 127.0.0.1:"$P" $ARG \
|
|
-servername {{ ansible_fqdn }} </dev/null 2>/dev/null \
|
|
| openssl x509 -noout -subject -fingerprint -enddate 2>/dev/null)
|
|
if [ -n "$OUT" ]; then
|
|
echo "PORT=$P"
|
|
echo "$OUT"
|
|
fi
|
|
done
|
|
register: devis_cert_servis
|
|
changed_when: false
|
|
|
|
- name: Déposer le relevé sur le contrôleur
|
|
ansible.builtin.copy:
|
|
dest: "{{ devis_certificats_sortie }}.{{ inventory_hostname }}"
|
|
mode: "0600"
|
|
content: >-
|
|
{{ {
|
|
'hote': inventory_hostname,
|
|
'fqdn': ansible_fqdn,
|
|
'chemin': client_pki_cert,
|
|
'disque': devis_cert_disque.stdout | default(''),
|
|
'disque_rc': devis_cert_disque.rc | default(1),
|
|
'servis': devis_cert_servis.stdout | default(''),
|
|
'recharges_declares': client_pki_reload_services,
|
|
} | to_nice_json }}
|
|
delegate_to: localhost
|
|
become: false
|