Set-OPS-Public/roles/resoudre_base/tasks/main.yml
Daniel Allaire c308c9c5b0 TLS des bases : le consommateur suit son serveur, il ne le devine plus
La supervision du SITE etait morte depuis 11:10 et rien ne le disait. icingadb
refuse par pg_hba — hostssl impose cote serveur, connexion en clair cote client.
icinga2 tournait, redis tournait, les sondes poussaient, et rien n atteignait la
base : les verdicts se calculaient dans le vide.

La cause est une seconde liste tenue a la main. tls_force allume hostssl ; chaque
consommateur avait SON interrupteur a allumer dans les group_vars. Chezlepro
avait les trois, le site avait le premier. serveur_forgejo disait pire que rien :
sslmode disable ecrit en dur, le contraire de ce que le serveur imposait.

resoudre_base expose resoudre_base_db_tls_force, lu dans les hostvars de la
machine qui PORTE la base. Les trois interrupteurs en derivent. Un serveur qui
ne declare rien ne force rien : on ne casse pas un ecosysteme qui n a pas
bascule.

P78 refuse une valeur ecrite chez un consommateur, et nomme les deux roles sans
reglage TLS plutot que de rendre un vert muet sur eux.

Apres : icingadb active, TLSv1.3 vu par PostgreSQL, 16 hotes et 88 services en
base. Les cinq sondes des marqueurs du site, INCONNU faute de deploiement,
rapportent leur phrase.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-14 18:42:21 -04:00

96 lines
5.1 KiB
YAML

---
# Rôle utilitaire (pas un groupe déployable) : résout l'entrée de base d'un
# consommateur depuis le registre partagé plan/bases-donnees.yml.
#
# Entrée : resoudre_base_groupe — le groupe consommateur (ex. serveur_keycloak).
# Sorties (facts) :
# resoudre_base_entree — l'entrée du registre {base, proprietaire, secret, serveur, ...}
# resoudre_base_db_password — mot de passe déréférencé depuis la voûte (no_log)
# resoudre_base_db_host — hôte du serveur de base
# resoudre_base_db_port — port du serveur de base (défaut 5432)
#
# Le secret ne quitte jamais le rôle consommateur (déréférencé par lookup('vars', ...)).
- name: Charger le registre des bases applicatives
ansible.builtin.include_vars:
file: "{{ setops_plan_dir }}/bases-donnees.yml"
# Le `consommateur` d'une base n'est PAS toujours un groupe : le validateur du registre
# (inventory_rules.py) accepte deux portées — `groupe` désigne un groupe opérationnel,
# `application` désigne une APPLICATION de plan/applications.yml. Chercher uniquement le
# nom du groupe faisait donc échouer toute base de portée `application` : Keycloak
# déclare `consommateur: keycloak`, le rôle demandait `serveur_keycloak`.
#
# Le lien entre les deux est DÉCLARÉ — chaque application nomme son `groupe`. On le suit
# plutôt que de retirer un préfixe à la main : une convention de nommage se contredit un
# jour, une déclaration se corrige.
- name: Charger le registre des applications (lien application → groupe)
ansible.builtin.include_vars:
file: "{{ setops_plan_dir }}/applications.yml"
- name: Établir les noms sous lesquels ce groupe peut être consommateur
ansible.builtin.set_fact:
resoudre_base_noms: >-
{{ [resoudre_base_groupe] + ((applications | default({})) | dict2items
| selectattr('value.groupe', 'defined')
| selectattr('value.groupe', 'equalto', resoudre_base_groupe)
| map(attribute='key') | list) }}
- name: Résoudre l'entrée de base (par consommateur, groupe ou application)
ansible.builtin.set_fact:
resoudre_base_entree: >-
{{ ((bases_donnees | default({})) | dict2items
| selectattr('value.consommateur', 'defined')
| selectattr('value.consommateur', 'in', resoudre_base_noms)
| map(attribute='value') | list | first) | default({}) }}
- name: Exiger une entrée de registre et son secret
ansible.builtin.assert:
that:
- resoudre_base_entree.base is defined
- resoudre_base_entree.proprietaire is defined
- resoudre_base_entree.secret is defined
fail_msg: >-
Aucune base dont le `consommateur` soit l'un de {{ resoudre_base_noms }}
dans instance/plan/bases-donnees.yml (portée `groupe` ou `application`).
- name: Résoudre le mot de passe de base (Vault)
ansible.builtin.set_fact:
resoudre_base_db_password: "{{ lookup('vars', resoudre_base_entree.secret) }}"
no_log: true
- name: Résoudre l'hôte (FQDN) et le port du serveur de base (registre)
ansible.builtin.set_fact:
# FQDN (pas le nom court) : non ambigu en fédération (data-sql-01 existe chez
# plusieurs tenants) + nom canonique pour la vérif TLS. Résolu par le plancher /etc/hosts.
resoudre_base_db_host: "{{ (serveurs_bd | default({}))[resoudre_base_entree.serveur].hote }}.{{ domaine_interne }}"
resoudre_base_db_port: "{{ (serveurs_bd | default({}))[resoudre_base_entree.serveur].port | default(5432) }}"
# LA POSTURE TLS DU SERVEUR, LUE CHEZ LE SERVEUR (2026-09-14).
#
# CE QUE ÇA A COÛTÉ. `serveur_postgresql_tls_force` pose `hostssl` dans `pg_hba` : toute
# connexion non chiffrée est refusée. Chaque consommateur avait alors SON propre
# interrupteur — `serveur_icinga_db_tls`, `serveur_keycloak_db_sslmode`,
# `serveur_forgejo_db_sslmode` — qu'il fallait allumer à la main, dans les `group_vars`,
# en même temps que celui du serveur.
#
# Chezlepro les avait tous les trois. Le site n'avait que celui du serveur. `icingadb` y
# est donc tombé le 2026-09-14 à 11:10 et n'est jamais remonté :
#
# aucune entrée dans pg_hba.conf pour l'hôte « 10.37.36.11 »,
# utilisateur « icingadb », base « icingadb », aucun chiffrement
#
# Une liste qui suit une autre prend du retard. Ici la seconde liste était tenue à la
# main dans un dépôt d'écosystème, et rien ne la comparait à la première.
#
# ON LIT DONC LA POSTURE CHEZ CELUI QUI LA DÉCIDE. Le serveur de base est un hôte de
# l'inventaire ; ce qu'il applique est dans ses `hostvars`. Un consommateur n'a plus à
# savoir qu'il doit chiffrer : il le déduit de ce que sert son serveur.
#
# `default(false)` : un serveur qui ne déclare rien ne force rien, et le consommateur
# reste en clair — c'était l'état de toute la flotte avant le zéro-confiance, et on ne
# casse pas un écosystème qui n'a pas encore basculé.
- name: Le serveur de base exige-t-il des connexions chiffrées ?
ansible.builtin.set_fact:
resoudre_base_db_tls_force: >-
{{ (hostvars[(serveurs_bd | default({}))[resoudre_base_entree.serveur].hote]
| default({})).get('serveur_postgresql_tls_force', false) | bool }}