reconstruction from-zero : sept defauts, tous invisibles sur une flotte debout

Chezlepro reconstruit depuis zero en 10.17.x.x. Sept defauts sont tombes, et
AUCUN n'etait detectable autrement — c'est tout l'argument de la reconstruction
comme preuve.

  1. aucune route vers le tenant sur le poste (posee a la main, jamais persistee)
  2. backup-01 exigeait l'AC d'Icinga — dependance non declaree
  3. ma correction inversait les couches : REFUSEE PAR P08 avant d'entrer
  4. base Forgejo a moitie initialisee (sequelle de l'arret du n°2)
  5. /etc/hosts : nom court avant le FQDN -> hostname -f faux sur les 14 machines
  6. API Icinga jamais activee (garde `creates:` d'api setup)
  7. restic refuse tout le lot si un chemin declare manque

LE CINQUIEME DEPASSAIT ICINGA. hostname -f rend le PREMIER nom : toute la flotte
se croyait appelee « mon-01 ». Visible sur le certificat d'Icinga, mais le meme
piege attendait Postfix (myhostname), les journaux, les certificats. FQDN d'abord.

CE QU'ON NE CORRIGE PAS : icinga2 api setup REECRIT NodeName d'apres le nom court
et nomme ses certificats d'apres lui. Aligne avant, il est ecrase ; aligne apres,
les certificats portent le mauvais nom. On adopte sa convention : le depot appelle
l'API par le nom court, celui que le certificat porte.

serveur_icinga_node_name derive desormais de l'INVENTAIRE, pas d'ansible_fqdn —
un fait qui depend du resolveur et rendait « mon-01 » alors que le FQDN etait bon.

Le commentaire ecrit pour avertir qu'une sequence accolade-diese casse les
gabarits Jinja contenait cette sequence, et cassait le gabarit. Neuf hotes en
echec pour un avertissement mal redige.

Sept devis CONFORME, prouver 36/36, make test 0. Neuf sauvegardes reussies.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-08-13 03:36:56 -04:00
parent 03aa035cab
commit f8a84b78d5
8 changed files with 214 additions and 31 deletions

View file

@ -1,5 +1,50 @@
# CHANGELOG — Set-OPS # CHANGELOG — Set-OPS
## 2026-08-13 — Reconstruction complète en `10.17.x.x` : sept défauts, tous invisibles avant
Chezlepro reconstruit **depuis zéro** sur l'adressage dérivé sans décalage. Sept défauts
sont tombés, et **aucun n'était détectable sur une flotte déjà debout** — c'est tout
l'argument de la reconstruction comme preuve.
| # | Défaut | Pourquoi il était invisible |
|---|---|---|
| 1 | aucune route vers le tenant sur le poste | posée à la main un jour, jamais persistée ; disparue à la réactivation de l'interface |
| 2 | `backup-01` exigeait l'AC d'Icinga | l'AC existait déjà sur l'ancienne flotte |
| 3 | ma correction inversait les couches | **refusée par P08** avant d'entrer au dépôt |
| 4 | base Forgejo à moitié initialisée | séquelle de l'arrêt du défaut n°2 |
| 5 | **`/etc/hosts` : nom court avant le FQDN** | `hostname -f` faux sur **les quatorze machines, depuis toujours** |
| 6 | API Icinga jamais activée | la garde `creates:` d'`api setup` la saute dès que le fichier existe |
| 7 | restic refuse tout le lot si un chemin manque | `/srv/web` n'existe qu'après la première webapp |
### Le cinquième dépassait Icinga
`/etc/hosts` déclarait `10.17.18.21 backup-01 backup-01.chezlepro.internal`. Or
`hostname -f` rend le **premier** nom : toute la flotte se croyait appelée `mon-01`, jamais
`mon-01.chezlepro.internal`. Conséquence visible sur Icinga (certificat `CN=mon-01`
invérifiable en appelant par le FQDN) — mais le même piège attendait Postfix (`myhostname`),
les journaux et tout ce qui se nomme ainsi. **FQDN d'abord, nom court en alias.**
### Ce qu'on ne corrige pas : la convention d'Icinga
`icinga2 api setup` **réécrit** `NodeName` d'après le nom court et nomme ses certificats
d'après lui. Aligné avant, il est écrasé ; aligné après, les certificats portent le mauvais
nom. On a essayé les deux. **On adopte donc sa convention** : le dépôt appelle l'API par le
nom court, celui que le certificat porte, résolu par le plancher `/etc/hosts`.
`serveur_icinga_node_name` dérive désormais de l'**inventaire** et non d'`ansible_fqdn` —
un fait qui dépend du résolveur de la machine, et qui a rendu `mon-01` alors que le FQDN
était correct.
> **Une leçon presque comique.** Le commentaire écrit pour avertir qu'une séquence
> accolade-dièse casse les gabarits Jinja… contenait cette séquence, et cassait le gabarit.
> Neuf hôtes en échec pour un avertissement mal rédigé.
### Verdict
**Sept devis CONFORME**, `prouver` 36/36, `make test` 0. Neuf sauvegardes réussies, cinq
sans objet, et la supervision reçoit les rapports du dépôt avec vérification du pair.
## 2026-08-12 — D-79 : l'hyperviseur filtre encore en iptables *legacy* ## 2026-08-12 — D-79 : l'hyperviseur filtre encore en iptables *legacy*
Question de l'exploitant : *« pourquoi les règles de sécurité au pare-feu global, alors Question de l'exploitant : *« pourquoi les règles de sécurité au pare-feu global, alors

View file

@ -0,0 +1,72 @@
# Preuve de conformite — Set-OPS — 2026-08-13
> Genere par `make prouver` (`scripts/prouver.py`). **Rejouable** : relancer
> reproduit ce rapport. Chaque preuve rejoue l'outillage existant du depot ;
> aucune validation n'est reimplementee ici. Voir le mode d'emploi :
> [`docs/audit/README.md`](README.md), et le registre trace :
> [`docs/audit/affirmations.md`](affirmations.md).
- **Instance** : `instance` — inventaire `instance/inventories/principal/hosts.yml`
- **Verdict** : ✅ CONFORME (36 OK · 0 echec · 0 saute)
## Preuves
| # | Preuve | Affirmations | Statut | Detail |
|---|---|---|---|---|
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK |  |
| P02 | Tests unitaires (inventory_host) | — | ✅ OK | >>> le verrou tient : aucune VM n'aurait ete touchee |
| P03 | Diff-vide du plan — TOUTES les instances | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | 3 instance(s) verifiee(s) — OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre : plan et inventaire applique coincident. |
| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | |
| P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | |
| P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. |
| P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check). |
| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 30 groupes classes, aucun cycle, aucune arete en arriere. |
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 29 rôles, 77 flux, schéma + matrice OK. |
| P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). |
| P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | playbook: playbooks/proxmox/cloner_vm_debian.yml |
| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. |
| P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. |
| P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. |
| P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. |
| P16 | Inventaire Ansible complet (--list) | AFF-030 | ✅ OK | 14 hotes, 31 groupes (inventaire dechiffre et parse). |
| P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. |
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 26 secret(s) exige(s), tous presents. Voute reelle : 29 cle(s), aucun manque. |
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 28 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. |
| P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) : adressage 100% derive du seed index. |
| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 2 instance(s) federee(s), aucun index en collision. |
| P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (20 sections). |
| P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 6 reseau(x), aucune collision avec la plage tenant. |
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | CONFORME : frontiere nord/sud, 39 regles, 12 routes, admin=10.0.0.0/24,192.168.254.2/32,192.168.255.2/32. |
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 2 tenant(s), 40 groupe(s), 66 regle(s). |
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 14 hote(s) x 4 integration(s) universelle(s) : aucune lacune, aucune recopie (1 exemption(s) derivee(s) du service rendu). |
| P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 0 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. |
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 2 pool(s) Proxmox, 28 VM placee(s), aucun nom ni VMID en collision. |
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 23 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 12, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 2 zone(s), 12 VNet(s), 12 sous-reseau(x), aucune collision. |
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 44 scripts expliques et atteignables, 92 cibles make documentees, 54 roles avec README. |
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 35 exigence(s) de role, toutes satisfaites (126 cle(s) declaree(s) par l'instance). |
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 32 revendication(s) de port, aucune collision entre roles co-localises (33 groupes). |
| P34 | Chaque document declare son lecteur | — | ✅ OK | 39 document(s) declarent leur lecteur (16 genere(s) exempte(s)). |
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 5 application(s) exigeant une base l'ont toutes (4 entree(s) au registre). |
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 9 hote(s) detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). |
## Couverture des affirmations ✅ du registre
Chaque affirmation ✅ automatisable est couverte par la preuve indiquee ci-dessus.
Les ✅ **structurelles/doctrinales** non rejouables par une commande (ex. AFF-005
`make`=aide, AFF-014 ciblage groupe, AFF-024 `instancier-appliquer`, AFF-051 autorite
d'AGENTS.md, AFF-073/075 gardes `make`, AFF-090 wiki) ont ete verifiees a l'audit ;
elles restent hors du harnais recurrent (rien d'executable a rejouer).
## Declarations d'intention (⚪ invérifiables localement — assumees)
Ces affirmations ne sont pas rejouables hors production ; elles sont **assumees**
comme declarations d'intention, non comme preuves :
- **AFF-036** — « testables avec `--check` autant que possible » : verifiable seulement
contre une flotte vivante.
- **AFF-091** — contenu pedagogique du wiki : affirmations conceptuelles.
- **AFF-096** — « GUI 100 % francais » : revue exhaustive des libelles rendus, non automatisee.
- **AFF-007** — hote d'exemple `web-frontal-01` : placeholder assume.
_Rapport genere le 2026-08-13._

View file

@ -24,7 +24,30 @@ CHEMINS=(
{% endfor %} {% endfor %}
{% endfor %} {% endfor %}
) )
restic backup --host "{{ inventory_hostname }}" --tag setops "${CHEMINS[@]}" # Un chemin DECLARE peut ne pas exister encore : `/srv/web` avant la premiere webapp,
# le stockage de Nextcloud avant son installation. Restic refuse alors TOUT le lot
# (« all source directories/files do not exist ») et la sauvegarde entiere echoue —
# constate le 2026-08-13 sur trois hotes d'une flotte neuve.
# On ne sauvegarde donc que ce qui existe, et on le DIT quand il n'y a rien : c'est a la
# supervision de signaler un depot vide, pas au script de mourir.
# NOTE : ne JAMAIS ecrire la longueur d'un tableau bash dans ce gabarit — la sequence
# accolade-diese y ouvre un commentaire Jinja et casse le rendu. On compte a la main.
# (Ce commentaire a lui-meme casse le gabarit une fois, en citant la sequence.)
PRESENTS=()
N_PRESENTS=0
for c in "${CHEMINS[@]}"; do
if [[ -e "$c" ]]; then
PRESENTS+=("$c")
N_PRESENTS=$((N_PRESENTS + 1))
else
echo "absent, ignore : $c" >&2
fi
done
if (( N_PRESENTS == 0 )); then
echo "aucun chemin declare n'existe encore sur cet hote — rien a sauvegarder." >&2
exit 0
fi
restic backup --host "{{ inventory_hostname }}" --tag setops "${PRESENTS[@]}"
# 4. Rétention. # 4. Rétention.
restic forget {{ client_backup_retention }} --prune restic forget {{ client_backup_retention }} --prune

View file

@ -6,9 +6,15 @@ ff02::1 ip6-allnodes
ff02::2 ip6-allrouters ff02::2 ip6-allrouters
# --- Écosystème (dérivé du plan ; chaque hôte → son IP réelle) --- # --- Écosystème (dérivé du plan ; chaque hôte → son IP réelle) ---
# LE FQDN EN PREMIER, le nom court en alias. `hostname -f` rend le PREMIER nom trouve
# pour l'adresse de la machine : avec le nom court en tete, toute la flotte se croyait
# appelee « mon-01 » et non « mon-01.chezlepro.internal ». Consequence mesuree le
# 2026-08-12 : `icinga2 api setup` emettait un certificat CN=mon-01/SAN=mon-01, que
# personne ne pouvait verifier en appelant par le FQDN. Le meme piege attend tout ce qui
# se nomme par `hostname -f` — Postfix (myhostname), les journaux, les certificats.
{% set fleet = (groups.get('hotes_actifs', []) + groups.get('hotes_planifies', [])) | unique | sort %} {% set fleet = (groups.get('hotes_actifs', []) + groups.get('hotes_planifies', [])) | unique | sort %}
{% for h in fleet if hostvars[h].ansible_host is defined %} {% for h in fleet if hostvars[h].ansible_host is defined %}
{{ "%-15s" | format(hostvars[h].ansible_host) }} {{ h }}{{ (' ' ~ h ~ '.' ~ hosts_statiques_domaine) if hosts_statiques_domaine else '' }} {{ "%-15s" | format(hostvars[h].ansible_host) }} {{ (h ~ '.' ~ hosts_statiques_domaine ~ ' ') if hosts_statiques_domaine else '' }}{{ h }}
{% endfor %} {% endfor %}
{% if hosts_statiques_publier_expositions | default(false) and hosts_statiques_expositions %} {% if hosts_statiques_publier_expositions | default(false) and hosts_statiques_expositions %}

View file

@ -82,16 +82,29 @@
mode: "0600" mode: "0600"
no_log: true no_log: true
# L'API Icinga presente un certificat emis par l'AC d'Icinga (il refuse d'en servir un # L'AC d'Icinga sert a VERIFIER le pair en rapportant (il refuse de servir un certificat
# autre : il renouvelle tout ce qui expire sous 30 jours, or nos certificats vivent 24 h). # qu'il n'a pas emis). Mais le depot est un SERVICE et la supervision une APPLICATION :
# On recupere donc CETTE AC pour verifier le pair — plutot que de sauter la verification. # la couche `services` precede `apps`, donc Icinga n'existe pas encore au premier
# deploiement. Exiger son AC ici inversait le graphe — P08 l'a refuse.
#
# On la prend donc SI ELLE EXISTE, et c'est `serveur_icinga` qui la POUSSE quand il monte
# (roles/serveur_icinga/tasks/main.yml). Le rapporteur echoue bruyamment tant qu'elle
# manque — et il n'y a de toute facon personne a qui rapporter.
- name: L'AC d'Icinga est-elle deja disponible ?
ansible.builtin.stat:
path: "{{ serveur_backup_icinga_ca_source }}"
delegate_to: "{{ serveur_backup_icinga_hote }}"
register: serveur_backup_ca_presente
- name: Récupérer l'AC d'Icinga depuis l'hôte de supervision - name: Récupérer l'AC d'Icinga depuis l'hôte de supervision
when: serveur_backup_ca_presente.stat.exists
ansible.builtin.slurp: ansible.builtin.slurp:
src: "{{ serveur_backup_icinga_ca_source }}" src: "{{ serveur_backup_icinga_ca_source }}"
delegate_to: "{{ serveur_backup_icinga_hote }}" delegate_to: "{{ serveur_backup_icinga_hote }}"
register: serveur_backup_ca_icinga register: serveur_backup_ca_icinga
- name: Déposer l'AC d'Icinga pour la vérification du pair - name: Déposer l'AC d'Icinga pour la vérification du pair
when: serveur_backup_ca_presente.stat.exists
ansible.builtin.copy: ansible.builtin.copy:
content: "{{ serveur_backup_ca_icinga.content | b64decode }}" content: "{{ serveur_backup_ca_icinga.content | b64decode }}"
dest: "{{ serveur_backup_ca_verification }}" dest: "{{ serveur_backup_ca_verification }}"

View file

@ -18,7 +18,7 @@
set -uo pipefail set -uo pipefail
RACINE="{{ serveur_backup_racine }}" RACINE="{{ serveur_backup_racine }}"
API="https://{{ serveur_backup_icinga_hote }}.{{ domaine_interne }}:5665" API="https://{{ serveur_backup_icinga_hote }}:5665" # nom COURT : c'est celui du certificat
TTL={{ serveur_backup_ttl_icinga }} TTL={{ serveur_backup_ttl_icinga }}
AGE_WARN={{ serveur_backup_age_warn_h }} AGE_WARN={{ serveur_backup_age_warn_h }}
AGE_CRIT={{ serveur_backup_age_crit_h }} AGE_CRIT={{ serveur_backup_age_crit_h }}

View file

@ -42,8 +42,15 @@ serveur_icinga_db_ca: "/etc/step/certs/root_ca.crt"
# NodeName aligne sur le FQDN : le certificat auto-emis porte NodeName en CN et en SAN, # NodeName aligne sur le FQDN : le certificat auto-emis porte NodeName en CN et en SAN,
# et c'est par le FQDN qu'on appelle l'API. Sans cet alignement, aucune verification ne # et c'est par le FQDN qu'on appelle l'API. Sans cet alignement, aucune verification ne
# peut reussir. # peut reussir.
serveur_icinga_node_name: "{{ ansible_fqdn | default(ansible_hostname) }}" # DERIVE DE L'INVENTAIRE, pas de `ansible_fqdn`. Le fait depend du resolveur de la
# machine et de la forme de /etc/hosts — il a rendu « mon-01 » alors que le FQDN etait
# correct (2026-08-12), produisant un certificat SAN=mon-01 que personne ne pouvait
# verifier en appelant par le nom complet. L'inventaire, lui, est la meme source que
# celle dont `serveur_backup` compose son URL : les deux ne peuvent pas diverger.
serveur_icinga_node_name: "{{ inventory_hostname }}.{{ domaine_interne }}"
serveur_icinga_ca: "/var/lib/icinga2/ca/ca.crt" serveur_icinga_ca: "/var/lib/icinga2/ca/ca.crt"
# Ou le depot de sauvegarde attend cette AC (cf. serveur_backup_ca_verification).
serveur_icinga_ca_destination_depot: "/etc/setops/icinga-ca.crt"
# --- Supervision des sauvegardes (resultats passifs pousses par le depot) --- # --- Supervision des sauvegardes (resultats passifs pousses par le depot) ---
# Le controle porte sur la VERITE DE TERRAIN (l'instantane cote depot), pas sur l'unite # Le controle porte sur la VERITE DE TERRAIN (l'instantane cote depot), pas sur l'unite

View file

@ -50,6 +50,16 @@
serveur_icinga_db_host: "{{ resoudre_base_db_host }}" serveur_icinga_db_host: "{{ resoudre_base_db_host }}"
no_log: true no_log: true
# --- Identite TLS de l'API (5665) ---
# ON NE TOUCHE PAS A `NodeName`, et c'est une lecon payee cher (2026-08-12).
# `icinga2 api setup` REECRIT lui-meme NodeName d'apres le nom COURT de la machine, puis
# nomme ses certificats d'apres lui : CN=mon-01, SAN=DNS:mon-01. Toute tentative de
# l'aligner sur le FQDN est effacee au passage suivant — on l'a essaye avant et apres
# `api setup`, sans succes.
#
# Plutot que de lutter contre sa convention, on l'ADOPTE : le consommateur appelle l'API
# par le nom que le certificat porte (`serveur_backup` compose son URL sur le nom court).
# Le plancher `/etc/hosts` deploye par `hosts_statiques` le resout sur chaque machine.
- name: Configurer l'API Icinga 2 - name: Configurer l'API Icinga 2
ansible.builtin.command: ansible.builtin.command:
cmd: icinga2 api setup cmd: icinga2 api setup
@ -89,30 +99,15 @@
no_log: true no_log: true
notify: Redemarrer icingadb notify: Redemarrer icingadb
# --- Identite TLS de l'API (5665) --- # `icinga2 api setup` active la fonctionnalite EN MEME TEMPS qu'il pose les certificats,
# ARBITRAGE DU 2026-08-11, apres mesure. On a voulu servir ici le certificat step-ca, pour # et sa garde `creates:` la saute des que le fichier existe. Consequence mesuree le
# que les consommateurs verifient le pair au lieu de faire `curl -k`. Icinga le REFUSE # 2026-08-12 : apres un nettoyage des certificats, l'API restait DESACTIVEE — icinga2
# structurellement, et le dit : # demarrait, se declarait `active`, et n'ecoutait sur rien. Exiger l'activation
# # separement rend l'etat independant de l'ordre des nettoyages.
# information/ApiListener: Our certificate will expire soon, but we own the CA. Renewing. - name: API — exiger que la fonctionnalite soit activee
# ansible.builtin.command:
# Les certificats Set-OPS vivent 24 H ; Icinga renouvelle tout ce qui expire sous 30 jours cmd: icinga2 feature enable api
# et, possedant une AC, re-emet avec la sienne. Il ecrasait donc le certificat step-ca a creates: /etc/icinga2/features-enabled/api.conf
# chaque demarrage. Ce n'est pas reparable par configuration.
#
# CE QU'ON FAIT A LA PLACE : Icinga garde son AC — un domaine de confiance FERME, ce qui
# est legitime — et le consommateur (serveur_backup) VERIFIE le pair contre cette AC-la.
# Le pair est authentifie ; seule la racine differe. Le `-k` disparait, ce qui etait le
# vrai probleme.
#
# NodeName reste aligne sur le FQDN : le certificat qu'Icinga s'emet porte NodeName en CN
# ET en SAN, et c'est par le FQDN qu'on l'appelle. Sans cet alignement, le SAN vaudrait
# « mon-01 » et toute verification echouerait.
- name: TLS — aligner NodeName sur le FQDN (identite de l'API)
ansible.builtin.lineinfile:
path: /etc/icinga2/constants.conf
regexp: '^const NodeName\s*='
line: 'const NodeName = "{{ serveur_icinga_node_name }}"'
notify: Redemarrer icinga2 notify: Redemarrer icinga2
- name: API — durcir l'ApiListener (aucune config ni commande acceptee) - name: API — durcir l'ApiListener (aucune config ni commande acceptee)
@ -179,3 +174,25 @@
loop: loop:
- "{{ serveur_icinga_service_icinga2 }}" - "{{ serveur_icinga_service_icinga2 }}"
- "{{ serveur_icinga_service_icingadb }}" - "{{ serveur_icinga_service_icingadb }}"
# --- Publier l'AC vers le depot de sauvegarde ---
# Le depot rapporte l'etat des instantanes a cette API et doit VERIFIER le pair. Il lui
# faut donc cette AC — mais lui est un SERVICE et nous une APPLICATION : il se deploie
# AVANT nous, et exiger son attente inverserait le graphe des couches (refuse par P08 le
# 2026-08-12). C'est donc a NOUS de la lui porter, une fois que `icinga2 api setup` l'a
# creee. Sens correct : l'application rejoint le service, jamais l'inverse.
- name: Publier l'AC d'Icinga vers le depot de sauvegarde
when: groups['serveur_backup'] | default([]) | length > 0
ansible.builtin.slurp:
src: "{{ serveur_icinga_ca }}"
register: serveur_icinga_ca_contenu
- name: Deposer l'AC sur le depot de sauvegarde
when: groups['serveur_backup'] | default([]) | length > 0
ansible.builtin.copy:
content: "{{ serveur_icinga_ca_contenu.content | b64decode }}"
dest: "{{ serveur_icinga_ca_destination_depot }}"
owner: root
group: root
mode: "0644"
delegate_to: "{{ groups['serveur_backup'] | first }}"