serveur_openldap : TLS via step_ca + organisation en intrant (code prod)

- TLS LDAPS/STARTTLS : pont du cert step_ca (client_pki, root:root 600)
  vers /etc/ldap/tls lisible par openldap ; script + unité path systemd
  qui re-synchronise et recharge slapd au renouvellement ; olcTLS* dans
  cn=config ; SLAPD_SERVICES expose ldaps://. Dégrade proprement sans cert.
- Organisation : intrant chezlepro_organisation (remplace « Exemple Inc »).

Validé statiquement (ansible-lint, syntax) ; déploiement réel à suivre.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-07-02 07:27:43 -04:00
parent 1e2c323156
commit 1edd205d13
8 changed files with 164 additions and 1 deletions

View file

@ -3,6 +3,14 @@
## 2026-07-02
### Ajouté
- **`serveur_openldap` durci pour la prod : TLS via step_ca + organisation en intrant.**
- **TLS (LDAPS + STARTTLS)** : le certificat d'hôte step_ca (déposé par `client_pki`,
`root:root 600`) est synchronisé vers un emplacement lisible par `openldap` (`/etc/ldap/tls`)
par un script + une unité `path` systemd qui **re-synchronise et recharge slapd à chaque
renouvellement** ; `olcTLS*` configuré dans `cn=config`, `SLAPD_SERVICES` expose `ldaps://`.
Dégrade proprement (slapd en clair local) si `client_pki` n'a pas encore posé le cert.
- **Organisation** : nouvel intrant `chezlepro_organisation` (remplace le « Exemple Inc » codé).
- *Écrit en code de prod, éprouvé statiquement ; validation par déploiement réel à suivre.*
- **`docs/courriel-conception.md`** — cadrage du futur service de courriel souverain :
full self-host, suite **Stalwart** (adoptée, enveloppée par un rôle mince), topologie
MX primaire + MX secours, intégration identité (LDAP) / DNS / PKI (Let's Encrypt public

View file

@ -8,7 +8,7 @@ serveur_openldap_service: "slapd"
# Identite de l'annuaire.
serveur_openldap_domaine: "{{ domaine_interne }}"
serveur_openldap_organisation: "Exemple Inc"
serveur_openldap_organisation: "{{ chezlepro_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 }}"
@ -20,3 +20,15 @@ serveur_openldap_admin_password: "{{ vault_openldap_admin | default('') }}" # r
serveur_openldap_ou:
- people
- groups
# --- 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:///"

View file

@ -0,0 +1,7 @@
---
- name: Redemarrer slapd
ansible.builtin.systemd:
name: "{{ serveur_openldap_service }}"
state: restarted
daemon_reload: true
when: not ansible_check_mode

View file

@ -58,3 +58,95 @@
loop_control:
label: "ou={{ item }}"
no_log: true
# --- 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

View file

@ -0,0 +1,12 @@
# Gere par Set-OPS (role serveur_openldap). Ne pas editer a la main.
# Surveille le certificat step_ca : au renouvellement (client_pki), resynchronise
# vers slapd et le recharge.
[Unit]
Description=Set-OPS — surveille le certificat step_ca de slapd
[Path]
PathChanged={{ serveur_openldap_tls_source_cert }}
Unit=setops-slapd-cert-sync.service
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,8 @@
# Gere par Set-OPS (role serveur_openldap). Ne pas editer a la main.
[Unit]
Description=Set-OPS — synchronise le certificat step_ca vers slapd
After=slapd.service
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/setops-slapd-cert-sync

View file

@ -0,0 +1,23 @@
#!/bin/sh
# Gere par Set-OPS (role serveur_openldap). Ne pas editer a la main.
# Synchronise le certificat step_ca (depose par client_pki, root:root 600) vers un
# emplacement lisible par openldap, puis recharge slapd. Idempotent ; appele au
# deploiement ET a chaque renouvellement (unite path).
set -eu
SRC_CERT="{{ serveur_openldap_tls_source_cert }}"
SRC_KEY="{{ serveur_openldap_tls_source_cle }}"
SRC_CA="{{ serveur_openldap_tls_source_ca }}"
DST="{{ serveur_openldap_tls_dir }}"
# Rien a faire tant que client_pki n'a pas depose le certificat d'hote.
[ -f "$SRC_CERT" ] && [ -f "$SRC_KEY" ] || exit 0
install -o openldap -g openldap -m 0644 "$SRC_CERT" "$DST/cert.pem"
install -o openldap -g openldap -m 0640 "$SRC_KEY" "$DST/key.pem"
[ -f "$SRC_CA" ] && install -o openldap -g openldap -m 0644 "$SRC_CA" "$DST/ca.pem"
# Recharge slapd s'il tourne (sans echouer s'il est absent/arrete).
if systemctl --quiet is-active slapd 2>/dev/null; then
systemctl reload slapd 2>/dev/null || systemctl restart slapd
fi

View file

@ -79,6 +79,7 @@ FICHIERS_INTRANTS = {"identite": INTRANTS_IDENTITE, "proxmox": INTRANTS_PROXMOX}
INTRANTS_SCHEMA = [
("domaine_interne", "identite", "constante", "Identité", "Domaine DNS interne", "str"),
("chezlepro_timezone", "identite", "defaut", "Identité", "Fuseau horaire", "str"),
("chezlepro_organisation", "identite", "defaut", "Identité", "Organisation (annuaire LDAP, certificats)", "str"),
("proxmox_api_host", "proxmox", "constante", "Proxmox", "Hôte API Proxmox", "str"),
("proxmox_api_user", "proxmox", "constante", "Proxmox", "Utilisateur API", "str"),
("proxmox_api_port", "proxmox", "constante", "Proxmox", "Port API", "str"),