--- # Amorcage des acces — D-67. Voir defaults/main.yml pour la doctrine, et # docs/autorisation.md §6 pour le runbook de reprise. - name: Résoudre la connexion à l'annuaire ansible.builtin.include_role: name: resoudre_annuaire - name: Exiger le secret d'amorçage et le mot de passe d'annuaire (Vault) ansible.builtin.assert: that: - amorcage_acces_secret | length > 0 - amorcage_acces_admin_password | length > 0 fail_msg: >- `vault_sysadmin_amorcage` et `vault_openldap_admin` sont requis (voûte). Le premier est un jeton d'amorçage à usage unique — pas un mot de passe permanent : voir docs/autorisation.md §6. when: amorcage_acces_actif | bool # --- LE CŒUR DU RÔLE : on REGARDE avant d'agir -------------------------------- # La condition n'est pas la conformité, c'est l'EXISTENCE. Un compte présent est # laissé strictement intact, quel que soit son état — c'est ce qui distingue ce # rôle de tous les autres du dépôt (D-67). - name: Le compte d'amorçage existe-t-il déjà ? community.general.ldap_search: dn: "ou=people,{{ resoudre_annuaire_base_dn }}" scope: onelevel filter: "(uid={{ amorcage_acces_uid }})" server_uri: "ldapi:///" bind_dn: "{{ resoudre_annuaire_bind_dn }}" bind_pw: "{{ amorcage_acces_admin_password }}" register: amorcage_acces_existant changed_when: false no_log: true when: amorcage_acces_actif | bool - name: Retenir si l'amorçage a déjà eu lieu ansible.builtin.set_fact: amorcage_acces_deja_fait: >- {{ (amorcage_acces_existant.results | default([]) | length) > 0 }} when: amorcage_acces_actif | bool - name: Annoncer que l'amorçage est déjà fait (aucune action) ansible.builtin.debug: msg: >- Compte `{{ amorcage_acces_uid }}` déjà présent : Set-OPS n'y touche pas. Les habilitations sont gouvernées par une personne (D-67) — voir docs/autorisation.md §6. when: - amorcage_acces_actif | bool - amorcage_acces_deja_fait | bool # --- Tout ce qui suit ne s'exécute QUE si le compte est absent ----------------- # Verifie ICI et non plus haut : un ecosysteme deja amorce ne doit pas se mettre # a echouer parce qu'on a durci la regle apres coup. - name: Exiger une adresse de courriel pour le compte d'amorçage ansible.builtin.assert: that: - amorcage_acces_courriel | length > 0 fail_msg: >- `amorcage_acces_courriel` est vide. Ce compte est le seul recours quand plus rien d'autre ne repond : sans adresse joignable, « mot de passe oublie » ne mene nulle part et la reprise repasse par un tiers qui manipule le mot de passe de quelqu'un d'autre. Declarer une adresse que la personne lit DEJA, hors de l'ecosysteme qu'on amorce. when: - amorcage_acces_actif | bool - not (amorcage_acces_deja_fait | bool) # Le mot de passe est HACHÉ par slapd lui-même. `ldap_entry` écrirait la chaîne # littéralement : le jeton d'amorçage se retrouverait en clair dans la base, lisible # par quiconque peut lire l'annuaire. - name: Hacher le jeton d'amorçage (slappasswd, SSHA) ansible.builtin.command: argv: - slappasswd - "-h" - "{SSHA}" - "-s" - "{{ amorcage_acces_secret }}" register: amorcage_acces_hache changed_when: false no_log: true when: - amorcage_acces_actif | bool - not amorcage_acces_deja_fait | bool # Le changement forcé s'appuie sur `pwdReset`, qui vient de l'overlay `ppolicy`. # On le DÉTECTE au lieu de le supposer : sans l'overlay, l'attribut est inconnu du # schéma et l'entrée entière est rejetée — panne obscure, et le compte n'existe pas # du tout. Mesuré le 2026-08-07 : seul `back_mdb` était chargé. - name: L'overlay ppolicy est-il chargé (attribut `pwdReset` connu) ? ansible.builtin.command: argv: - ldapsearch - "-Y" - EXTERNAL - "-H" - "ldapi:///" - "-b" - "cn=schema,cn=config" - "(olcAttributeTypes=*pwdReset*)" - dn register: amorcage_acces_ppolicy changed_when: false failed_when: false when: - amorcage_acces_actif | bool - not amorcage_acces_deja_fait | bool - name: Retenir si le changement forcé est applicable ansible.builtin.set_fact: amorcage_acces_reset_possible: >- {{ amorcage_acces_changement_force | bool and 'dn:' in (amorcage_acces_ppolicy.stdout | default('')) }} when: - amorcage_acces_actif | bool - not amorcage_acces_deja_fait | bool - name: Créer le compte d'amorçage du sysadmin community.general.ldap_entry: dn: "uid={{ amorcage_acces_uid }},ou=people,{{ resoudre_annuaire_base_dn }}" objectClass: - inetOrgPerson - organizationalPerson - person - top attributes: uid: "{{ amorcage_acces_uid }}" cn: "{{ amorcage_acces_nom }}" sn: "{{ amorcage_acces_nom.split(' ') | last }}" # `cn` porte le nom complet ; le prenom a son propre attribut, et c'est lui # que Keycloak doit lire — sinon il affiche le nom entier suivi du nom de # famille. Derive comme `sn`, par le meme decoupage. givenName: "{{ (amorcage_acces_nom.split(' ')[:-1] | join(' ')) | default(amorcage_acces_nom, true) }}" userPassword: "{{ amorcage_acces_hache.stdout | trim }}" pwdReset: "{{ 'TRUE' if amorcage_acces_reset_possible | bool else omit }}" mail: "{{ amorcage_acces_courriel | default(omit, true) }}" server_uri: "ldapi:///" bind_dn: "{{ resoudre_annuaire_bind_dn }}" bind_pw: "{{ amorcage_acces_admin_password }}" no_log: true when: - amorcage_acces_actif | bool - not amorcage_acces_deja_fait | bool # `groupOfNames` EXIGE au moins un membre : un groupe vide est refusé par le # schéma — ce qui tombe bien, un groupe d'habilitation sans personne n'aurait # aucun sens à l'amorçage. - name: Créer le groupe d'habilitation avec le sysadmin pour membre community.general.ldap_entry: dn: "cn={{ amorcage_acces_groupe }},ou=groups,{{ resoudre_annuaire_base_dn }}" objectClass: - groupOfNames - top attributes: cn: "{{ amorcage_acces_groupe }}" member: "uid={{ amorcage_acces_uid }},ou=people,{{ resoudre_annuaire_base_dn }}" description: >- Habilitation d'administration. Amorcé par Set-OPS, gouverné ensuite par une personne — le dépôt ne réconcilie pas ses membres (D-67). server_uri: "ldapi:///" bind_dn: "{{ resoudre_annuaire_bind_dn }}" bind_pw: "{{ amorcage_acces_admin_password }}" no_log: true when: - amorcage_acces_actif | bool - not amorcage_acces_deja_fait | bool - name: Annoncer la reprise en main ansible.builtin.debug: msg: >- {{ [ 'Accès d amorçage créé : uid=' ~ amorcage_acces_uid ~ ', groupe=' ~ amorcage_acces_groupe ~ '.', 'Mot de passe en voûte sous `vault_sysadmin_amorcage`.', (amorcage_acces_reset_possible | bool) | ternary('Changement FORCÉ à la première ouverture (ppolicy actif).', 'ATTENTION : rien ne force le changement — l overlay ppolicy n est pas chargé. ' ~ 'Le changer à la main dès la première connexion (docs/autorisation.md §6).'), 'À partir d ici, Set-OPS ne touche plus aux habilitations (D-67).' ] }} when: - amorcage_acces_actif | bool - not amorcage_acces_deja_fait | bool