Set-OPS-Public/playbooks/maintenance/devis-identite.yml
Daniel Allaire 5aa5f2e479 devis d'identite : comparer le deploye au declare
Constat de l'exploitant : « ca fait beaucoup de trucs incoherents qu'on
debusque ensemble ». Il y a une raison mesurable — les 30 preuves de
prouver.py sont STATIQUES (0 appel reseau, 0 ssh, 0 ansible). Elles montrent
que le depot est coherent avec lui-meme ; aucune ne demande au systeme
deploye s'il ressemble a ce que le depot annonce. Les quatre defauts du jour
vivaient tous la.

La classe statique est presque epuisee : recensement des motifs « cree mais
ne reconcilie jamais » -> amorcage_acces (delibere, D-67), serveur_openldap
(corrige le matin), et un seul reste reel (rbac-oidc.yml). Une preuve
statique de plus aurait rapporte une ligne.

Le patron devis/applicateur (D-23/D-24) existait deja pour les quatre
pare-feu, jamais pour les services. make identite-plan l'y porte :
- playbooks/maintenance/devis-identite.yml RELEVE le declare et le reel
- scripts/devis_identite.py COMPARE (le raisonnement n'a rien a faire en
  Jinja ; le depot a deja cette forme pour les devis reseau)
- le declare n'est jamais recopie : defauts du role + resolveurs. Un devis
  qui redeclare ce qu'il verifie ne verifie rien.

Verifie dans les deux sens : CONFORME sur le systeme reel ; sur un releve ou
les quatre defauts du jour sont rejoues plus deux regressions, 6 divergences
listees et code de sortie 1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 06:55:06 -04:00

138 lines
5.5 KiB
YAML

---
# Devis d'identite — RELEVE seul. Ne modifie rien (D-23 : un devis se lit d'abord).
#
# Le playbook collecte deux dictionnaires — ce que le depot DECLARE, ce que la machine
# PORTE — et les depose en JSON sur le controleur. La comparaison est faite par
# `scripts/devis_identite.py` : elle n'a rien a faire en Jinja, et le depot a deja
# cette forme (devis_opnsense, devis_sdn : Python raisonne, Ansible releve).
#
# Pourquoi ce devis existe. Les 30 preuves de `prouver.py` sont STATIQUES : elles
# montrent que le depot est coherent avec lui-meme. Aucune ne demande au systeme
# deploye s'il ressemble a ce que le depot annonce. Les quatre defauts du 2026-08-08
# etaient tous de ce second type — chacun trouve en relisant apres avoir ecrit,
# aucun signale par un test.
#
# make identite-plan
- name: Devis d'identité — relever le déclaré et le réel
hosts: serveur_keycloak
become: true
gather_facts: false
vars:
kc: "http://localhost:8080"
devis_identite_sortie: "{{ playbook_dir }}/../../instance/devis-identite.json"
tasks:
# Les valeurs DECLAREES vivent dans les defauts du role. On les charge plutot que
# de les recopier ici : un devis qui redeclare ce qu'il verifie ne verifie rien.
- name: Charger les valeurs déclarées par le rôle Keycloak
ansible.builtin.include_vars:
dir: "{{ playbook_dir }}/../../roles/serveur_keycloak/defaults"
- name: Résoudre la politique de mot de passe (déclaration unique)
ansible.builtin.include_role:
name: resoudre_politique_mdp
# Meme raison : les DN de l'annuaire ne se recopient pas, ils se resolvent.
- name: Résoudre la connexion à l'annuaire
ansible.builtin.include_role:
name: resoudre_annuaire
- name: Charger le registre des applications
ansible.builtin.include_vars:
file: "{{ setops_plan_dir }}/applications.yml"
- name: Obtenir un jeton d'administration
ansible.builtin.uri:
url: "{{ kc }}/realms/master/protocol/openid-connect/token"
method: POST
body_format: form-urlencoded
body:
grant_type: password
client_id: admin-cli
username: "{{ serveur_keycloak_admin_user }}"
password: "{{ serveur_keycloak_admin_password }}"
register: devis_jeton
no_log: true
- name: Lire le realm
ansible.builtin.uri:
url: "{{ kc }}/admin/realms/{{ serveur_keycloak_realm }}"
headers:
Authorization: "Bearer {{ devis_jeton.json.access_token }}"
register: devis_realm
no_log: true
- name: Lire la fédération LDAP
ansible.builtin.uri:
url: "{{ kc }}/admin/realms/{{ serveur_keycloak_realm }}/components?type=org.keycloak.storage.UserStorageProvider"
headers:
Authorization: "Bearer {{ devis_jeton.json.access_token }}"
register: devis_fed
no_log: true
- name: Lire les mappeurs de la fédération
ansible.builtin.uri:
url: >-
{{ kc }}/admin/realms/{{ serveur_keycloak_realm }}/components?parent={{
devis_fed.json[0].id }}&type=org.keycloak.storage.ldap.mappers.LDAPStorageMapper
headers:
Authorization: "Bearer {{ devis_jeton.json.access_token }}"
register: devis_mappeurs
no_log: true
- name: Lire la politique de mot de passe de l'annuaire
community.general.ldap_search:
dn: "ou=policies,{{ resoudre_annuaire_base_dn }}"
scope: onelevel
filter: "(objectClass=pwdPolicy)"
server_uri: "ldapi:///"
register: devis_ppolicy
changed_when: false
# Un compte sans adresse ne peut pas recevoir de lien de reinitialisation : pour
# lui, « mot de passe oublie » est un ecran qui ne mene nulle part, et le seul
# recours redevient l'exploitant manipulant le mot de passe d'autrui.
- name: Lire les comptes de l'annuaire
community.general.ldap_search:
dn: "{{ resoudre_annuaire_users_dn }}"
scope: onelevel
filter: "(objectClass=inetOrgPerson)"
attrs: ["uid", "mail", "givenName"]
server_uri: "ldapi:///"
register: devis_comptes
changed_when: false
- name: Déposer le relevé sur le contrôleur
ansible.builtin.copy:
dest: "{{ devis_identite_sortie }}"
mode: "0600"
content: "{{ {
'hote': inventory_hostname,
'realm': serveur_keycloak_realm,
'declare': {
'passwordPolicy': resoudre_politique_mdp_keycloak,
'brute': resoudre_politique_mdp_brute,
'ppolicy': resoudre_politique_mdp_ldap,
'durcissement': serveur_keycloak_ldap_durcissement,
'mappeurs': serveur_keycloak_ldap_attributs,
'editMode': serveur_keycloak_ldap_edit_mode,
'resetPasswordAllowed': serveur_keycloak_reset_mot_de_passe | bool,
'smtpHost': applications[serveur_keycloak_smtp_app].hote ~ '.' ~ domaine_interne,
},
'reel': {
'realm': devis_realm.json,
'federation': devis_fed.json[0].config,
'mappeurs': dict(devis_mappeurs.json | map(attribute='name') | list
| zip(devis_mappeurs.json | map(attribute='config') | list)),
'ppolicy': (devis_ppolicy.results | first) | default({}),
'comptes': devis_comptes.results,
},
} | to_nice_json }}"
delegate_to: localhost
become: false
- name: Indiquer le relevé
ansible.builtin.debug:
msg: "Relevé déposé : {{ devis_identite_sortie }} — comparaison par scripts/devis_identite.py"