Set-OPS-Public/roles/serveur_openldap/tasks/main.yml

310 lines
11 KiB
YAML
Raw Normal View History

---
- name: Exiger le mot de passe admin LDAP (Vault)
ansible.builtin.assert:
that:
- serveur_openldap_admin_password | length > 0
fail_msg: "serveur_openldap_admin_password est requis (a fournir via Ansible Vault)."
- name: Installer debconf-utils (requis par le module debconf, fournit debconf-get-selections)
ansible.builtin.apt:
name: debconf-utils
state: present
update_cache: true
cache_valid_time: 3600
- name: Preconfigurer slapd (debconf)
ansible.builtin.debconf:
name: slapd
question: "{{ item.question }}"
vtype: "{{ item.vtype }}"
value: "{{ item.value }}"
loop:
- { question: "slapd/no_configuration", vtype: "boolean", value: "false" }
- { question: "slapd/domain", vtype: "string", value: "{{ serveur_openldap_domaine }}" }
- { question: "shared/organization", vtype: "string", value: "{{ serveur_openldap_organisation }}" }
- { question: "slapd/backend", vtype: "select", value: "MDB" }
- { question: "slapd/purge_database", vtype: "boolean", value: "true" }
- { question: "slapd/move_old_database", vtype: "boolean", value: "true" }
- { question: "slapd/allow_ldap_v2", vtype: "boolean", value: "false" }
loop_control:
label: "{{ item.question }}"
- name: Preconfigurer le mot de passe admin slapd (debconf)
ansible.builtin.debconf:
name: slapd
question: "{{ item }}"
vtype: password
value: "{{ serveur_openldap_admin_password }}"
loop:
- slapd/password1
- slapd/password2
no_log: true
- name: Installer OpenLDAP
ansible.builtin.apt:
name: "{{ serveur_openldap_paquets }}"
state: present
update_cache: true
cache_valid_time: 3600
- name: Activer et demarrer slapd
when: not ansible_check_mode
ansible.builtin.systemd:
name: "{{ serveur_openldap_service }}"
enabled: true
state: started
- name: Creer les unites organisationnelles de base
community.general.ldap_entry:
dn: "ou={{ item }},{{ serveur_openldap_base_dn }}"
objectClass: organizationalUnit
server_uri: "ldapi:///"
bind_dn: "{{ serveur_openldap_admin_dn }}"
bind_pw: "{{ serveur_openldap_admin_password }}"
loop: "{{ serveur_openldap_ou }}"
loop_control:
label: "ou={{ item }}"
no_log: true
# --- Politique de mot de passe (overlay ppolicy) ------------------------------
# Sans cet overlay, `pwdReset` n'existe pas dans le schema : `amorcage_acces` ne peut
# pas forcer le changement du jeton d'amorcage, et rien ne contraint la qualite ni ne
# verrouille apres des echecs. Voir docs/autorisation.md §3.
- name: Ppolicy — Charger le module
community.general.ldap_attrs:
dn: "cn=module{0},cn=config"
attributes:
olcModuleLoad: "ppolicy"
state: present
server_uri: "ldapi:///"
when: serveur_openldap_ppolicy_actif | bool
# Le DN de la base varie (`olcDatabase={1}mdb` en general, mais l'index n'est pas
# garanti) : on le LIT au lieu de le supposer.
- name: Ppolicy — Trouver la base mdb
community.general.ldap_search:
dn: "cn=config"
scope: onelevel
filter: "(olcDatabase={1}mdb)"
attrs:
- dn
server_uri: "ldapi:///"
register: serveur_openldap_base_mdb
changed_when: false
when: serveur_openldap_ppolicy_actif | bool
- name: Ppolicy — Retenir le DN de la base
ansible.builtin.set_fact:
serveur_openldap_mdb_dn: >-
{{ (serveur_openldap_base_mdb.results | first).dn }}
when:
- serveur_openldap_ppolicy_actif | bool
- (serveur_openldap_base_mdb.results | default([]) | length) > 0
identite : une declaration de politique de mot de passe, deux executants Quatre defauts mesures dans l'integration Keycloak/LDAP, meme famille : une valeur declaree d'un cote, consommee de l'autre, rien qui verifie la jonction. 1. Aucune regle ne s'appliquait sur le chemin d'un vrai utilisateur. Sonde : « abcd » refuse par l'operation etendue LDAP, accepte par Keycloak (204), puis actif pour l'authentification. Keycloak ecrivait userPassword en direct (ppolicy aveugle) et le realm n'avait aucune passwordPolicy. 2. ldap_entry ne fait que CREER : la politique etait figee a sa creation. Le depot disait pwdMustChange TRUE, le serveur FALSE — une reconstruction from-zero aurait ressuscite la boucle du 2026-08-07. ldap_attrs state=exact reconcilie la politique et l'overlay (DN lu, pas devine). 3. syncRegistrations absent : un compte cree dans Keycloak n'atteignait jamais ou=people — acces web, aucune boite, invisible du modele de groupes. 4. Le prenom pointait sur cn (nom complet) : « Administrateur systeme systeme ». Ajoute roles/resoudre_politique_mdp : LA declaration, traduite en pwdPolicy, passwordPolicy et anti-force-brute. Les deux roles la consomment sans la redeclarer. usePasswordModifyExtendedOp ET validatePasswordPolicy : la seconde est porteuse, Keycloak se liant en rootDN et slapd n'appliquant pas ses controles de qualite au rootDN. La premiere seule aurait paru juste sans tenir. Verification : abcd -> 400 « minimum length 12 » et absent de LDAP ; mot de passe conforme -> 204 puis ldapwhoami accepte ; POST users -> 201 ET present dans ou=people. Second passage des deux playbooks : changed=0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 06:28:10 -04:00
- name: Ppolicy — Résoudre la politique (déclaration unique)
ansible.builtin.include_role:
name: resoudre_politique_mdp
when: serveur_openldap_ppolicy_actif | bool
- name: Ppolicy — Creer l unite des politiques
community.general.ldap_entry:
dn: "ou=policies,{{ serveur_openldap_base_dn }}"
objectClass: organizationalUnit
server_uri: "ldapi:///"
bind_dn: "{{ serveur_openldap_admin_dn }}"
bind_pw: "{{ serveur_openldap_admin_password }}"
no_log: true
when: serveur_openldap_ppolicy_actif | bool
# L'overlay AVANT la politique : `olcPPolicyDefault` reference une entree qui doit
# exister, mais l'overlay doit etre en place pour que le schema pwd* soit connu.
# L'ordre inverse echouerait des deux cotes.
- name: Ppolicy — Activer l overlay sur la base
community.general.ldap_entry:
dn: "olcOverlay=ppolicy,{{ serveur_openldap_mdb_dn }}"
objectClass:
- olcOverlayConfig
- olcPPolicyConfig
attributes:
olcOverlay: "ppolicy"
olcPPolicyDefault: "cn=default,ou=policies,{{ serveur_openldap_base_dn }}"
olcPPolicyHashCleartext: "{{ 'TRUE' if serveur_openldap_ppolicy_hash_cleartext | bool else 'FALSE' }}"
olcPPolicyUseLockout: "{{ 'TRUE' if serveur_openldap_ppolicy_use_lockout | bool else 'FALSE' }}"
server_uri: "ldapi:///"
when:
- serveur_openldap_ppolicy_actif | bool
- serveur_openldap_mdb_dn is defined
- name: Ppolicy — Creer la politique par defaut
community.general.ldap_entry:
dn: "cn=default,ou=policies,{{ serveur_openldap_base_dn }}"
objectClass:
- pwdPolicy
- person
- top
attributes: >-
identite : une declaration de politique de mot de passe, deux executants Quatre defauts mesures dans l'integration Keycloak/LDAP, meme famille : une valeur declaree d'un cote, consommee de l'autre, rien qui verifie la jonction. 1. Aucune regle ne s'appliquait sur le chemin d'un vrai utilisateur. Sonde : « abcd » refuse par l'operation etendue LDAP, accepte par Keycloak (204), puis actif pour l'authentification. Keycloak ecrivait userPassword en direct (ppolicy aveugle) et le realm n'avait aucune passwordPolicy. 2. ldap_entry ne fait que CREER : la politique etait figee a sa creation. Le depot disait pwdMustChange TRUE, le serveur FALSE — une reconstruction from-zero aurait ressuscite la boucle du 2026-08-07. ldap_attrs state=exact reconcilie la politique et l'overlay (DN lu, pas devine). 3. syncRegistrations absent : un compte cree dans Keycloak n'atteignait jamais ou=people — acces web, aucune boite, invisible du modele de groupes. 4. Le prenom pointait sur cn (nom complet) : « Administrateur systeme systeme ». Ajoute roles/resoudre_politique_mdp : LA declaration, traduite en pwdPolicy, passwordPolicy et anti-force-brute. Les deux roles la consomment sans la redeclarer. usePasswordModifyExtendedOp ET validatePasswordPolicy : la seconde est porteuse, Keycloak se liant en rootDN et slapd n'appliquant pas ses controles de qualite au rootDN. La premiere seule aurait paru juste sans tenir. Verification : abcd -> 400 « minimum length 12 » et absent de LDAP ; mot de passe conforme -> 204 puis ldapwhoami accepte ; POST users -> 201 ET present dans ou=people. Second passage des deux playbooks : changed=0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 06:28:10 -04:00
{{ {'cn': 'default', 'sn': 'default'}
| combine(resoudre_politique_mdp_ldap, serveur_openldap_ppolicy_extra) }}
server_uri: "ldapi:///"
bind_dn: "{{ serveur_openldap_admin_dn }}"
bind_pw: "{{ serveur_openldap_admin_password }}"
no_log: true
when: serveur_openldap_ppolicy_actif | bool
identite : une declaration de politique de mot de passe, deux executants Quatre defauts mesures dans l'integration Keycloak/LDAP, meme famille : une valeur declaree d'un cote, consommee de l'autre, rien qui verifie la jonction. 1. Aucune regle ne s'appliquait sur le chemin d'un vrai utilisateur. Sonde : « abcd » refuse par l'operation etendue LDAP, accepte par Keycloak (204), puis actif pour l'authentification. Keycloak ecrivait userPassword en direct (ppolicy aveugle) et le realm n'avait aucune passwordPolicy. 2. ldap_entry ne fait que CREER : la politique etait figee a sa creation. Le depot disait pwdMustChange TRUE, le serveur FALSE — une reconstruction from-zero aurait ressuscite la boucle du 2026-08-07. ldap_attrs state=exact reconcilie la politique et l'overlay (DN lu, pas devine). 3. syncRegistrations absent : un compte cree dans Keycloak n'atteignait jamais ou=people — acces web, aucune boite, invisible du modele de groupes. 4. Le prenom pointait sur cn (nom complet) : « Administrateur systeme systeme ». Ajoute roles/resoudre_politique_mdp : LA declaration, traduite en pwdPolicy, passwordPolicy et anti-force-brute. Les deux roles la consomment sans la redeclarer. usePasswordModifyExtendedOp ET validatePasswordPolicy : la seconde est porteuse, Keycloak se liant en rootDN et slapd n'appliquant pas ses controles de qualite au rootDN. La premiere seule aurait paru juste sans tenir. Verification : abcd -> 400 « minimum length 12 » et absent de LDAP ; mot de passe conforme -> 204 puis ldapwhoami accepte ; POST users -> 201 ET present dans ou=people. Second passage des deux playbooks : changed=0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 06:28:10 -04:00
# `ldap_entry` ne fait que CREER : l'entree restait figee a ce qu'elle valait le
# jour de sa creation, et toute modification ulterieure de la declaration etait
# ignoree en silence. Constate le 2026-08-08 — le depot annoncait
# `pwdMustChange: TRUE`, le serveur portait `FALSE` (corrige a la main apres la
# boucle de changement de mot de passe du 2026-08-07), et une reconstruction
# from-zero aurait ressuscite le defaut. Il faut donc RECONCILIER en plus.
- name: Ppolicy — Reconcilier la politique par defaut
community.general.ldap_attrs:
dn: "cn=default,ou=policies,{{ serveur_openldap_base_dn }}"
attributes: "{{ resoudre_politique_mdp_ldap | combine(serveur_openldap_ppolicy_extra) }}"
state: exact
server_uri: "ldapi:///"
bind_dn: "{{ serveur_openldap_admin_dn }}"
bind_pw: "{{ serveur_openldap_admin_password }}"
no_log: true
when: serveur_openldap_ppolicy_actif | bool
# Meme raison : l'overlay non plus n'etait pose qu'a la creation. Son DN porte un
# index d'ordre (`{0}`, `{1}`…) attribue par slapd — on le LIT, on ne le devine pas.
- name: Ppolicy — Trouver le DN de l overlay
community.general.ldap_search:
dn: "{{ serveur_openldap_mdb_dn }}"
scope: onelevel
filter: "(olcOverlay=*ppolicy)"
attrs:
- dn
server_uri: "ldapi:///"
register: serveur_openldap_overlay_ppolicy
changed_when: false
when:
- serveur_openldap_ppolicy_actif | bool
- serveur_openldap_mdb_dn is defined
- name: Ppolicy — Reconcilier les reglages de l overlay
community.general.ldap_attrs:
dn: "{{ (serveur_openldap_overlay_ppolicy.results | first).dn }}"
attributes:
olcPPolicyDefault: "cn=default,ou=policies,{{ serveur_openldap_base_dn }}"
olcPPolicyHashCleartext: "{{ 'TRUE' if serveur_openldap_ppolicy_hash_cleartext | bool else 'FALSE' }}"
olcPPolicyUseLockout: "{{ 'TRUE' if serveur_openldap_ppolicy_use_lockout | bool else 'FALSE' }}"
state: exact
server_uri: "ldapi:///"
when:
- serveur_openldap_ppolicy_actif | bool
- (serveur_openldap_overlay_ppolicy.results | default([]) | length) > 0
# --- TLS (LDAPS + STARTTLS) via le certificat step_ca depose par client_pki ---
- name: TLS — creer le repertoire pont lisible par openldap
ansible.builtin.file:
path: "{{ serveur_openldap_tls_dir }}"
state: directory
owner: openldap
group: openldap
mode: "0750"
when: serveur_openldap_tls_actif | bool
- name: TLS — deployer le script de synchronisation du certificat
ansible.builtin.template:
src: setops-slapd-cert-sync.sh.j2
dest: /usr/local/sbin/setops-slapd-cert-sync
owner: root
group: root
mode: "0755"
when: serveur_openldap_tls_actif | bool
- name: TLS — deployer les unites de synchronisation (service + path)
ansible.builtin.template:
src: "{{ item.s }}"
dest: "{{ item.d }}"
owner: root
group: root
mode: "0644"
loop:
- { s: "setops-slapd-cert-sync.service.j2", d: "/etc/systemd/system/setops-slapd-cert-sync.service" }
- { s: "setops-slapd-cert-sync.path.j2", d: "/etc/systemd/system/setops-slapd-cert-sync.path" }
loop_control:
label: "{{ item.d | basename }}"
when: serveur_openldap_tls_actif | bool
- name: TLS — synchroniser le certificat maintenant (si client_pki l'a depose)
ansible.builtin.command: /usr/local/sbin/setops-slapd-cert-sync
changed_when: false
when:
- serveur_openldap_tls_actif | bool
- not ansible_check_mode
- name: TLS — activer la surveillance du certificat (renouvellement)
ansible.builtin.systemd:
name: setops-slapd-cert-sync.path
enabled: true
state: started
daemon_reload: true
when:
- serveur_openldap_tls_actif | bool
- not ansible_check_mode
- name: TLS — verifier que le certificat pont est en place
ansible.builtin.stat:
path: "{{ serveur_openldap_tls_dir }}/cert.pem"
register: serveur_openldap_cert_pont
when: serveur_openldap_tls_actif | bool
- name: TLS — avertir si le certificat n'est pas encore disponible
ansible.builtin.debug:
msg: >-
client_pki n'a pas encore depose de certificat sur ce noeud
({{ serveur_openldap_tls_source_cert }}). TLS slapd NON configure : deploie
l'integration client_pki sur ce noeud, puis relance le deploiement.
when:
- serveur_openldap_tls_actif | bool
- not ansible_check_mode
- not (serveur_openldap_cert_pont.stat.exists | default(false))
- name: TLS — configurer slapd (olcTLS* dans cn=config)
community.general.ldap_attrs:
dn: cn=config
server_uri: ldapi:///
attributes:
olcTLSCACertificateFile: "{{ serveur_openldap_tls_dir }}/ca.pem"
olcTLSCertificateFile: "{{ serveur_openldap_tls_dir }}/cert.pem"
olcTLSCertificateKeyFile: "{{ serveur_openldap_tls_dir }}/key.pem"
state: exact
when:
- serveur_openldap_tls_actif | bool
- not ansible_check_mode
- serveur_openldap_cert_pont.stat.exists | default(false)
notify: Redemarrer slapd
- name: TLS — exposer LDAPS (SLAPD_SERVICES)
ansible.builtin.lineinfile:
path: /etc/default/slapd
regexp: '^SLAPD_SERVICES='
line: 'SLAPD_SERVICES="{{ serveur_openldap_services }}"'
when:
- serveur_openldap_tls_actif | bool
- serveur_openldap_cert_pont.stat.exists | default(false)
notify: Redemarrer slapd
sondes : les cinq qui manquaient le plus (autorite, base, annuaire, zones, edge) CORRECTION DE COMPTE D ABORD. J avais annonce cinq groupes sans sonde. La mesure en donne VINGT-SEPT : j avais compte ceux que j avais en tete, pas ceux que le depot contient. Il en reste vingt-deux. LES CINQ, PAR ORDRE DE DEGAT SILENCIEUX. autorite (step_ca) — la plus urgente : nos certificats vivent 24 h, une AC muette ne casse rien aujourd hui et casse TOUT demain, d un coup, sur les 21 machines. Elle surveille aussi l expiration de la RACINE, que personne ne regarde parce qu elle vit des annees (relevee a 3642 jours). base (postgresql) — une VRAIE requete, pas pg_isready : celui-ci dit que le port repond, pas que la base sert. Plus le compte des connexions : a saturation, chaque application tombe sans que la base ait l air morte. annuaire (openldap) — elle COMPTE les entrees. Un annuaire vide repond success a tout : le mensonge des sauvegardes vides, vert et sans contenu. zones (powerdns) — un autoritatif sans zone repond NXDOMAIN a tout, ce qui se lit comme « ce nom n existe pas ». edge (nginx) — elle valide la configuration SUR DISQUE : nginx sert la derniere valide, et une configuration cassee ne se voit qu au prochain demarrage, souvent des mois plus tard. Les cinq eprouvees vertes sur le sain puis rouges PAR PARAMETRE, sans toucher a un service. Etat : 20 sondes sur 19 roles, 23 services distincts, 70 instances, 67 au vert. Les trois autres sont connues : deux sauvegardes sans donnee a emporter, et Loki en delai de stabilisation apres redeploiement. make prouver : CONFORME, 64 OK, 0 echec, 0 saute. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-10 03:47:03 -04:00
# --- Sonde de supervision (docs/supervision-conception.md) --------------------------
# Le role qui possede la verite depose sa propre sonde ; le porteur (`client_sante`) la
# fait tourner et pousse le verdict, sans savoir ce qu'elle mesure.
- name: Assurer le repertoire des sondes de supervision
ansible.builtin.file:
path: /usr/local/lib/setops/sondes
state: directory
owner: root
group: root
mode: "0755"
- name: Deposer la sonde « annuaire »
ansible.builtin.template:
src: sonde-annuaire.sh.j2
dest: /usr/local/lib/setops/sondes/annuaire.sh
owner: root
group: root
mode: "0750"