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>
138 lines
5.5 KiB
YAML
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"
|