Set-OPS-Public/roles/serveur_openldap/defaults/main.yml
Daniel Allaire b4e3520561 annuaire : un compte de service par consommateur, et la porte se ferme
Keycloak, Dovecot, Postfix et Icinga Web 2 se liaient TOUS avec cn=admin, le compte
d administration de la base. C est le rootDN : slapd lui fait contourner toutes les
ACL. Un seul secret, quatre services, tous les droits sur l arbre — pour ce qui est,
trois fois sur quatre, une simple lecture.

Et les droits livres par Debian etaient intacts : `to * by * read`. Sur ldap://, sans
s authentifier, une machine du reseau enumerait tous les comptes et toutes les
adresses. Des comptes a droits mesures n auraient rien valu tant que cette ligne
restait : on aurait ferme la porte en laissant la fenetre.

L indice etait deja dans le depot. `validatePasswordPolicy` existe parce que slapd
n applique pas ses controles de qualite au rootDN : la consequence etait compensee,
la cause intacte.

- ou=services, un compte par consommateur, secret propre en voute
- sept regles d acces posees EN ENTIER (state: exact) : l ordre est la regle, et
  inserer c est parier sur ce que le paquet aura mis avant nous
- amorcage_acces garde le compte d administration, NOMME comme l exception : il ne
  consomme pas l annuaire, il le provisionne depuis la socket locale
- la sonde passe de -x a -Y EXTERNAL : elle lisait en anonyme et aurait annonce un
  annuaire VIDE sur un annuaire parfaitement sain
- la rotation du compte d administration devient possible (elle n etait posee qu a
  l installation, par debconf : la voute et slapd divergeaient en silence)

Quatre marches payees en chemin :
1. un cinquieme appelant oublie, dont l echec etait masque par no_log — la garde
   refuse desormais SANS no_log : elle nomme la cle absente, jamais son contenu
2. la federation Keycloak ne reecrivait son bindDn que si l URL ou le mode changeaient
   — nouveau secret, ancien nom, error code 49
3. la rotation placee APRES les taches qui se lient en administrateur
4. ansible-vault et son tube : sortie non bloquante = echec silencieux, la voute
   paraissait tournee et etait identique a l octet

P72 exige que tout role incluant resoudre_annuaire NOMME son compte, et qu aucun sauf
amorcage_acces ne nomme admin. Eprouvee dans les deux sens.

Verifie sur l infrastructure : chaque compte lit ce qu il doit, aucun ne voit les
autres, la lecture anonyme rend 0 entree, et les quatre services repondent (doveadm
user, postmap -q, decouverte OIDC 200, portier SSO 200).

vault_openldap_admin et vault_ldap_bind_postfix renouveles : les deux avaient transite
en clair par une session d exploitation. Les anciennes valeurs rendent Invalid
credentials (49).

make prouver : 71 OK, 0 echec, 1 saute. ansible-lint : 0 failure, profil production.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-13 13:36:38 -04:00

160 lines
8.5 KiB
YAML

---
serveur_openldap_paquets:
- slapd
- ldap-utils
- python3-ldap # requis par community.general.ldap_entry sur la cible
serveur_openldap_service: "slapd"
# Identite de l'annuaire.
serveur_openldap_domaine: "{{ domaine_interne }}"
serveur_openldap_organisation: "{{ organisation | default('Organisation') }}"
# Base DN derivee du domaine interne (ex. acme.local -> dc=acme,dc=local).
serveur_openldap_base_dn: "dc={{ serveur_openldap_domaine.split('.') | join(',dc=') }}"
serveur_openldap_admin_dn: "cn=admin,{{ serveur_openldap_base_dn }}"
# Secret OBLIGATOIRE (Ansible Vault).
serveur_openldap_admin_password: "{{ vault_openldap_admin | default('') }}" # rempli depuis la voute (vault_openldap_admin)
# Unites organisationnelles de base.
serveur_openldap_ou:
- people
- groups
# LES COMPTES DE SERVICE VIVENT A PART (2026-09-13). Melanges aux personnes, ils
# apparaitraient dans chaque recherche d'usager — et un annuaire ou « qui travaille
# ici » retourne des demons ne se lit plus.
- services
# --- QUI SE LIE A L'ANNUAIRE, ET AVEC QUELS DROITS (2026-09-13) ----------------------
#
# AVANT : les quatre consommateurs — Keycloak, Dovecot, Postfix, Icinga Web 2 — se liaient
# tous avec `cn=admin`, le compte d'administration de la base. C'est le rootDN : slapd lui
# fait CONTOURNER TOUTES LES ACL, y compris ses propres controles de qualite de mot de
# passe. Un seul secret, quatre services, tous les droits sur l'arbre entier — pour des
# operations qui sont, pour trois d'entre eux, de simples LECTURES.
#
# L'INDICE ETAIT DEJA DANS LE DEPOT. `serveur_keycloak_ldap_durcissement` porte
# `validatePasswordPolicy: true` avec ce motif : « Keycloak se lie en rootDN, et slapd
# n'applique pas ses controles de qualite au rootDN ». On avait compense la consequence
# sans traiter la cause. Avec un compte ordinaire, l'annuaire redevient juge de sa propre
# politique — le reglage reste, mais il n'est plus la seule barriere.
#
# `ecriture: true` n'est accorde qu'a ce qui DOIT ecrire. Keycloak est le seul : il porte
# les mots de passe a travers (edit_mode WRITABLE) et cree les comptes que le web ouvre
# (syncRegistrations). Les trois autres lisent.
serveur_openldap_comptes_service:
- cn: keycloak
secret: vault_ldap_bind_keycloak
ecriture: true
raison: "Federation OIDC : ecrit les mots de passe a travers et cree les comptes."
- cn: dovecot
secret: vault_ldap_bind_dovecot
ecriture: false
raison: "Qui a droit a sa boite aux lettres."
- cn: postfix
secret: vault_ldap_bind_postfix
ecriture: false
raison: "A qui livrer un courriel."
- cn: icingaweb2
secret: vault_ldap_bind_icingaweb2
ecriture: false
raison: "Qui peut voir la supervision."
serveur_openldap_services_dn: "ou=services,{{ serveur_openldap_base_dn }}"
# Empreinte du mot de passe d'administration APPLIQUE. `slappasswd` sale son condensat :
# comparer ce que slapd detient ne dit rien. On garde donc l'empreinte de la valeur
# voulue, exactement comme la federation Keycloak le fait pour sa liaison.
serveur_openldap_marqueur_admin: "/etc/ldap/.setops-admin-sha256"
serveur_openldap_admin_empreinte: "{{ serveur_openldap_admin_password | hash('sha256') }}"
# L'ANNUAIRE ETAIT LISIBLE PAR N'IMPORTE QUI (mesure du 2026-09-13).
#
# La regle livree par Debian est `to * by * read` : sur `ldap://`, sans s'authentifier,
# une machine du reseau enumerait tous les comptes et toutes les adresses. Des comptes de
# service a droits mesures n'auraient servi a rien tant que cette ligne restait — on
# aurait ferme la porte en laissant la fenetre.
#
# `false` restaure le comportement d'origine, pour un ecosysteme qui aurait une raison
# explicite de publier son annuaire. Ce n'est pas le cas par defaut.
serveur_openldap_lecture_anonyme: false
# L'identite du root LOCAL sur le socket unix. La sonde de cet annuaire l'utilise :
# elle compte les entrees sans reseau et sans secret. Fermer la lecture sans lui accorder
# ce droit rendrait la sonde aveugle — elle annoncerait un annuaire vide, c'est-a-dire la
# panne la plus grave, sur un annuaire parfaitement sain.
serveur_openldap_root_local_dn: "gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth"
# Raccourcis lisibles, pour que les regles ci-dessous se lisent comme des phrases.
serveur_openldap_ecrivains: >-
{% for c in serveur_openldap_comptes_service if c.ecriture | default(false) -%}
by dn.exact="cn={{ c.cn }},{{ serveur_openldap_services_dn }}" write {% endfor %}
serveur_openldap_lecteurs: >-
{% for c in serveur_openldap_comptes_service if not (c.ecriture | default(false)) -%}
by dn.exact="cn={{ c.cn }},{{ serveur_openldap_services_dn }}" read {% endfor %}
serveur_openldap_root_local: 'by dn.exact="{{ serveur_openldap_root_local_dn }}" read'
# Ce que voit une identite qui n'est designee par aucune regle precedente.
serveur_openldap_reste: "{{ 'by * read' if serveur_openldap_lecture_anonyme | bool else 'by users read by * none' }}"
# LES DROITS, DANS L'ORDRE OU slapd LES LIT. Voir tasks/main.yml pour ce que chaque
# numero protege — et pourquoi deplacer {0} ferme l'annuaire a tout le monde.
serveur_openldap_acces:
- '{0}to attrs=userPassword by self write by anonymous auth {{ serveur_openldap_ecrivains }} by * none'
- '{1}to attrs=shadowLastChange by self write by * read'
- '{2}to dn.subtree="{{ serveur_openldap_services_dn }}" {{ serveur_openldap_root_local }} by * none'
- >-
{3}to dn.subtree="ou=people,{{ serveur_openldap_base_dn }}"
{{ serveur_openldap_ecrivains }} {{ serveur_openldap_lecteurs }}
by self read {{ serveur_openldap_root_local }} by * none
- >-
{4}to dn.subtree="ou=groups,{{ serveur_openldap_base_dn }}"
{{ serveur_openldap_ecrivains }} {{ serveur_openldap_lecteurs }}
by users read {{ serveur_openldap_root_local }} by * none
- '{5}to dn.base="" by * read'
- '{6}to * {{ serveur_openldap_root_local }} {{ serveur_openldap_reste }}'
# --- TLS (LDAPS + STARTTLS) via le certificat d'hote step_ca ---
# Requiert l'integration client_pki sur le noeud (fournit le cert + le renouvellement).
serveur_openldap_tls_actif: true
# Certificat SOURCE depose par client_pki (root:root 600, illisible par openldap).
serveur_openldap_tls_source_cert: "/etc/step/certs/{{ ansible_fqdn | default(ansible_hostname) }}.crt"
serveur_openldap_tls_source_cle: "/etc/step/certs/{{ ansible_fqdn | default(ansible_hostname) }}.key"
serveur_openldap_tls_source_ca: "/etc/step/certs/root_ca.crt"
# Emplacement PONT, lisible par openldap ; resynchronise a chaque renouvellement.
serveur_openldap_tls_dir: "/etc/ldap/tls"
# Services slapd exposes (ldaps ajoute pour le TLS ; ldapi pour l'admin local).
serveur_openldap_services: "ldap:/// ldaps:/// ldapi:///"
# --- Politique de mot de passe (overlay ppolicy) ------------------------------
# Sans cet overlay, `pwdReset` n'existe pas dans le schema : le changement force a
# la premiere ouverture est impossible, et rien ne contraint la qualite ni ne
# verrouille apres des echecs repetes. Mesure le 2026-08-07 : seul `back_mdb`
# etait charge. Voir docs/autorisation.md §3.
#
# Depuis OpenLDAP 2.5, le schema ppolicy est INTEGRE au module : aucun fichier
# `.ldif` a charger, contrairement a 2.4.
serveur_openldap_ppolicy_actif: true
# `pwdMustChange` est ce qui donne son effet a `pwdReset` : sans lui, marquer une
# entree n'oblige a rien. Les deux vont ensemble.
# Les REGLES elles-memes ne sont plus declarees ici : elles vivent dans
# `resoudre_politique_mdp`, qui les traduit AUSSI pour Keycloak. Tant qu'elles
# n'existaient que de ce cote, Keycloak n'en validait aucune et le chemin que
# prend un vrai utilisateur ne verifiait rien (mesure du 2026-08-08).
# Laisser vide ; ne surcharger que pour ajouter un attribut pwd* hors politique.
serveur_openldap_ppolicy_extra: {}
# Reglages de l'OVERLAY lui-meme (pas de la politique). `HashCleartext` hache un
# mot de passe recu en clair au lieu de le stocker tel quel : filet de securite si
# un client oublie de hacher.
# `UseLockout` laisse volontairement a FALSE : dire « ce compte est verrouille »
# renseigne un attaquant sur l'existence du compte. L'utilisateur legitime voit
# « identifiants invalides » et attend la duree de verrouillage.
serveur_openldap_ppolicy_hash_cleartext: true
serveur_openldap_ppolicy_use_lockout: false
# --- Sonde de supervision -----------------------------------------------------------
serveur_openldap_sonde_base: "{{ serveur_openldap_base_dn | default('dc=chezlepro,dc=internal') }}"
# UN MINIMUM, PAS UNE EGALITE : l'annuaire grandit. Ce chiffre dit « il reste peuple »,
# pas « il contient exactement ceci ». Trop haut, il crierait a chaque menage.
serveur_openldap_sonde_minimum: 5