diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fbaf53..ecc9401 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,15 @@ connecté à PostgreSQL (**87 tables** du realm master écrites), **token admin obtenu** (auth adossée à la BD). Lacunes connues (documentées `catalogue-services.md`) : fédération LDAP et edge nginx pas encore câblés. Reste : DRY du bloc de résolution BD (keycloak/forgejo/icinga). +- **`serveur_keycloak` — fédération LDAP (modèle d'identité A) : automatisée et prouvée.** Le rôle + configure, via `kcadm` (idempotent), un realm applicatif (`serveur_keycloak_realm`, déf. + `chezlepro`) et un **provider de stockage LDAP READ_ONLY** vers OpenLDAP (LDAPS, `uid`/`entryUUID`, + `inetOrgPerson`). TLS LDAPS validé via le **truststore système** (`truststore-paths` → + `/etc/ssl/certs/ca-certificates.crt`, racine step_ca posée par **client_pki**, désormais requis + sur le nœud). Secrets par `environment` + `no_log`. **Éprouvé avant codification** puis prouvé + par le rôle : un utilisateur LDAP (`testmail`) obtient un token via le realm (HTTP 200), et le + redéploiement est **idempotent** (`changed=0`). Nouveau : `tasks/federation-ldap.yml`. Reste : + edge nginx (accès HTTPS par nom), mappers d'attributs/groupes fins. ## 2026-07-02 diff --git a/roles/serveur_keycloak/defaults/main.yml b/roles/serveur_keycloak/defaults/main.yml index 3150a56..4db9608 100644 --- a/roles/serveur_keycloak/defaults/main.yml +++ b/roles/serveur_keycloak/defaults/main.yml @@ -19,3 +19,19 @@ serveur_keycloak_db_host: "10.0.13.11" # Secrets (Ansible Vault) — obligatoires. serveur_keycloak_admin_user: "admin" serveur_keycloak_admin_password: "{{ vault_keycloak_admin | default('') }}" # rempli depuis la voute (vault_keycloak_admin) + +# --- Fédération LDAP : OpenLDAP source de vérité (modèle d'identité A) --- +# Keycloak lit l'annuaire (READ_ONLY) ; les users LDAP se connectent via le SSO. +# Requiert client_pki sur ce nœud (racine step_ca dans le bundle système pour LDAPS). +serveur_keycloak_ldap_federation: true +serveur_keycloak_realm: "chezlepro" +serveur_keycloak_ldap_nom: "openldap" +serveur_keycloak_ldap_url: "ldaps://id-ldap-01.{{ domaine_interne }}:636" +serveur_keycloak_ldap_users_dn: "ou=people,dc={{ domaine_interne.split('.') | join(',dc=') }}" +serveur_keycloak_ldap_bind_dn: "cn=admin,dc={{ domaine_interne.split('.') | join(',dc=') }}" +serveur_keycloak_ldap_bind_password: "{{ vault_openldap_admin | default('') }}" +serveur_keycloak_ldap_username_attr: "uid" +serveur_keycloak_ldap_uuid_attr: "entryUUID" +serveur_keycloak_ldap_object_classes: "inetOrgPerson" +# Bundle d'AC de confiance (contient la racine step_ca, posée par client_pki) pour LDAPS. +serveur_keycloak_truststore_path: "/etc/ssl/certs/ca-certificates.crt" diff --git a/roles/serveur_keycloak/tasks/federation-ldap.yml b/roles/serveur_keycloak/tasks/federation-ldap.yml new file mode 100644 index 0000000..8054784 --- /dev/null +++ b/roles/serveur_keycloak/tasks/federation-ldap.yml @@ -0,0 +1,58 @@ +--- +# Fédération LDAP via kcadm — idempotent. Crée le realm applicatif + le provider de +# stockage LDAP (OpenLDAP, READ_ONLY). Éprouvé avant codification (testmail se connecte). +- name: Attendre que Keycloak réponde + ansible.builtin.wait_for: + host: 127.0.0.1 + port: 8080 + timeout: 120 + when: not ansible_check_mode + +- name: Configurer le realm et la fédération LDAP (kcadm, idempotent) + ansible.builtin.shell: + cmd: | + set -eu + KC={{ serveur_keycloak_home }}/bin/kcadm.sh + CHANGED=0 + "$KC" config credentials --server http://localhost:8080 --realm master \ + --user {{ serveur_keycloak_admin_user }} --password "$KC_ADMIN_PW" >/dev/null + if ! "$KC" get realms/{{ serveur_keycloak_realm }} >/dev/null 2>&1; then + "$KC" create realms -s realm={{ serveur_keycloak_realm }} -s enabled=true >/dev/null + CHANGED=1 + fi + RID=$("$KC" get realms/{{ serveur_keycloak_realm }} --fields id 2>/dev/null \ + | grep -oiE '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}' | head -1) + if ! "$KC" get components -r {{ serveur_keycloak_realm }} -q name={{ serveur_keycloak_ldap_nom }} 2>/dev/null \ + | grep -q '"{{ serveur_keycloak_ldap_nom }}"'; then + "$KC" create components -r {{ serveur_keycloak_realm }} \ + -s name={{ serveur_keycloak_ldap_nom }} -s providerId=ldap \ + -s providerType=org.keycloak.storage.UserStorageProvider -s parentId="$RID" \ + -s 'config.enabled=["true"]' \ + -s 'config.vendor=["other"]' \ + -s 'config.editMode=["READ_ONLY"]' \ + -s 'config.importEnabled=["true"]' \ + -s 'config.connectionUrl=["{{ serveur_keycloak_ldap_url }}"]' \ + -s 'config.usersDn=["{{ serveur_keycloak_ldap_users_dn }}"]' \ + -s 'config.bindDn=["{{ serveur_keycloak_ldap_bind_dn }}"]' \ + -s "config.bindCredential=[\"$LDAP_BIND_PW\"]" \ + -s 'config.authType=["simple"]' \ + -s 'config.searchScope=["2"]' \ + -s 'config.usernameLDAPAttribute=["{{ serveur_keycloak_ldap_username_attr }}"]' \ + -s 'config.rdnLDAPAttribute=["{{ serveur_keycloak_ldap_username_attr }}"]' \ + -s 'config.uuidLDAPAttribute=["{{ serveur_keycloak_ldap_uuid_attr }}"]' \ + -s 'config.userObjectClasses=["{{ serveur_keycloak_ldap_object_classes }}"]' \ + -s 'config.useTruststoreSpi=["always"]' \ + -s 'config.connectionPooling=["true"]' >/dev/null + CHANGED=1 + fi + [ "$CHANGED" = 1 ] && echo SETOPS_CHANGED || echo SETOPS_OK + environment: + KC_ADMIN_PW: "{{ serveur_keycloak_admin_password }}" + LDAP_BIND_PW: "{{ serveur_keycloak_ldap_bind_password }}" + register: serveur_keycloak_fed + changed_when: "'SETOPS_CHANGED' in serveur_keycloak_fed.stdout" + no_log: true + retries: 5 + delay: 6 + until: serveur_keycloak_fed.rc == 0 + when: not ansible_check_mode diff --git a/roles/serveur_keycloak/tasks/main.yml b/roles/serveur_keycloak/tasks/main.yml index 6b9534e..ed8a183 100644 --- a/roles/serveur_keycloak/tasks/main.yml +++ b/roles/serveur_keycloak/tasks/main.yml @@ -128,3 +128,7 @@ enabled: true state: started daemon_reload: true + +- name: Configurer la federation LDAP (realm + provider OpenLDAP) + ansible.builtin.include_tasks: federation-ldap.yml + when: serveur_keycloak_ldap_federation | bool diff --git a/roles/serveur_keycloak/templates/keycloak.conf.j2 b/roles/serveur_keycloak/templates/keycloak.conf.j2 index ef93818..0628f2d 100644 --- a/roles/serveur_keycloak/templates/keycloak.conf.j2 +++ b/roles/serveur_keycloak/templates/keycloak.conf.j2 @@ -8,3 +8,8 @@ db-username={{ serveur_keycloak_entree.proprietaire }} hostname={{ serveur_keycloak_hostname }} proxy-headers=xforwarded http-enabled=true +{% if serveur_keycloak_ldap_federation %} + +# Truststore (racine step_ca via le bundle systeme) pour valider LDAPS vers OpenLDAP. +truststore-paths={{ serveur_keycloak_truststore_path }} +{% endif %}