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:
parent
03aa035cab
commit
f8a84b78d5
8 changed files with 214 additions and 31 deletions
45
CHANGELOG.md
45
CHANGELOG.md
|
|
@ -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
|
||||||
|
|
|
||||||
72
docs/audit/preuve-2026-08-13.md
Normal file
72
docs/audit/preuve-2026-08-13.md
Normal 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 | [0m[0m |
|
||||||
|
| 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._
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 %}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 }}"
|
||||||
|
|
|
||||||
|
|
@ -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 }}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 }}"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue