identite : « mot de passe oublie » — raccord SMTP derive du plan
Le realm portait une politique d'acces complete et aucun moyen d'ecrire a qui que ce soit. Tout oubli remontait donc a l'exploitant, qui n'avait d'autre choix que de manipuler le mot de passe d'autrui. - serveur_keycloak/tasks/courriel-realm.yml : reconcilie smtpServer et resetPasswordAllowed ; hote du relais DERIVE de applications.postfix.hote, et refus explicite si le plan ne declare pas de MTA. - passe par l'API d'administration : kcadm.sh accepte les deux formes -s sur une map, sort en succes et n'ecrit rien (smtpServer reste vide). - amorcage_acces_courriel redevient a declarer : cette adresse designe une personne, hors du systeme qu'on amorce ; une boite interne serait illisible tant qu'on n'a pas l'acces qu'on cherche justement a recuperer. - autorisation.md §6.6 : le mecanisme, ses deux conditions, et l'ecart d'adresse laisse par l'ancien mode READ_ONLY de la federation. Preuve : banniere SMTP lue depuis idm-01, RCPT TO accepte, execute-actions-email declenche, MTA en starttls -> relay=mx.chezlepro.ca status=sent (250). Second deploiement changed=0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
4642ffec0a
commit
775df924cb
8 changed files with 273 additions and 5 deletions
48
CHANGELOG.md
48
CHANGELOG.md
|
|
@ -1,5 +1,49 @@
|
||||||
# CHANGELOG — Set-OPS
|
# CHANGELOG — Set-OPS
|
||||||
|
|
||||||
|
## 2026-08-08 — « Mot de passe oublié » : Keycloak sait enfin envoyer
|
||||||
|
|
||||||
|
Le realm affichait une politique d'accès complète et **aucun moyen d'écrire à qui que ce
|
||||||
|
soit** : `smtpServer` vide, `resetPasswordAllowed` à `false`. Conséquence concrète — tout
|
||||||
|
oubli de mot de passe remontait à l'exploitant, qui n'avait alors d'autre choix que de
|
||||||
|
manipuler le mot de passe de quelqu'un d'autre. C'est précisément ce que « une identité,
|
||||||
|
une personne » (§3 d'`autorisation.md`) cherche à écarter.
|
||||||
|
|
||||||
|
**Ce qui est ajouté.** `serveur_keycloak/tasks/courriel-realm.yml` réconcilie la strophe
|
||||||
|
courriel du realm et le drapeau « mot de passe oublié ». L'hôte du relais est **dérivé du
|
||||||
|
plan** (`applications.postfix.hote`) : aucun nom de machine n'est écrit. Si le plan ne
|
||||||
|
déclare pas de MTA, le rôle **refuse** — un écran qui promet un courriel que personne
|
||||||
|
n'enverrait serait pire que pas d'écran du tout.
|
||||||
|
|
||||||
|
**`kcadm.sh` ne sait pas écrire une map, et ne le dit pas.** Sur `smtpServer`, les deux
|
||||||
|
formes documentées — `-s smtpServer.host=…` et `-s 'smtpServer={"host":…}'` — sortent en
|
||||||
|
**succès, sans rien écrire**. Le champ est resté `{ }` après deux déploiements verts. La
|
||||||
|
tâche passe donc par l'API d'administration (`uri`), qui répond `204` et écrit vraiment.
|
||||||
|
Même famille que le reste de ce journal : une valeur déclarée d'un côté, jamais vérifiée
|
||||||
|
de l'autre. Ce qui l'a rattrapée, c'est d'avoir relu l'état après l'avoir posé — pas le
|
||||||
|
code de retour.
|
||||||
|
|
||||||
|
**L'adresse de l'amorçage ne se dérive pas.** J'avais d'abord posé
|
||||||
|
`{{ amorcage_acces_uid }}@{{ domaine_interne }}` comme défaut : c'est un piège. Cette
|
||||||
|
adresse désigne une **personne**, donc quelque chose d'extérieur au système qu'on amorce,
|
||||||
|
et une boîte interne n'est pas lisible tant qu'on n'a pas justement l'accès qu'on essaie de
|
||||||
|
récupérer. `amorcage_acces_courriel` redevient donc à déclarer, et le rôle refuse de créer
|
||||||
|
le compte sans elle — mais seulement à la création, pour qu'un écosystème déjà amorcé ne se
|
||||||
|
mette pas à échouer parce qu'on a durci la règle après coup.
|
||||||
|
|
||||||
|
**Un reliquat de `READ_ONLY` mis au jour.** Le compte `sysadmin` portait
|
||||||
|
`sysadmin@chezlepro.ca` dans Keycloak et **rien** dans LDAP. L'adresse avait été saisie
|
||||||
|
dans la console de compte quand la fédération était encore en lecture seule : Keycloak
|
||||||
|
l'avait gardée pour lui, l'annuaire ne l'a jamais reçue, et les deux côtés ont affiché des
|
||||||
|
valeurs différentes sans que rien ne le signale. Corrigé dans LDAP (source de vérité) puis
|
||||||
|
resynchronisé ; consigné au runbook (§6.6) parce que d'autres comptes créés avant la
|
||||||
|
bascule en `WRITABLE` peuvent porter le même écart.
|
||||||
|
|
||||||
|
**Preuve de bout en bout**, et pas un `connect()` : bannière SMTP lue depuis `idm-01`,
|
||||||
|
`RCPT TO` accepté, puis un vrai `execute-actions-email` déclenché — journal du MTA :
|
||||||
|
`starttls=1`, `to=<sysadmin@chezlepro.ca>, relay=mx.chezlepro.ca[69.70.26.53]:25,
|
||||||
|
status=sent (250 2.0.0 Ok)`. Le second déploiement rapporte `changed=0` : la tâche
|
||||||
|
réconcilie, elle ne réécrit pas.
|
||||||
|
|
||||||
## 2026-08-06 — le chemin nord-sud devient dérivable
|
## 2026-08-06 — le chemin nord-sud devient dérivable
|
||||||
|
|
||||||
### `vault_openldap_admin` — quatre consommateurs et deux pièges
|
### `vault_openldap_admin` — quatre consommateurs et deux pièges
|
||||||
|
|
@ -70,13 +114,13 @@ pu s'authentifier pour poser la nouvelle valeur.
|
||||||
```
|
```
|
||||||
|
|
||||||
C'est une **procédure**, pas un redéploiement — et la même contrainte vaut pour
|
C'est une **procédure**, pas un redéploiement — et la même contrainte vaut pour
|
||||||
`vault_openldap_admin`, qui reste à faire. Consigné au runbook (§6.7), avec le rappel qu'une
|
`vault_openldap_admin`, qui reste à faire. Consigné au runbook (§6.8), avec le rappel qu'une
|
||||||
vérification n'est pas un message de succès.
|
vérification n'est pas un message de succès.
|
||||||
|
|
||||||
|
|
||||||
### La rotation des comptes de secours devient possible — elle ne l'était pas
|
### La rotation des comptes de secours devient possible — elle ne l'était pas
|
||||||
|
|
||||||
Le runbook §6.6 promettait de régénérer les comptes de secours ; **le code ne savait pas le
|
Le runbook §6.7 promettait de régénérer les comptes de secours ; **le code ne savait pas le
|
||||||
faire**. Les trois rôles ne posaient le mot de passe qu'à la *création* :
|
faire**. Les trois rôles ne posaient le mot de passe qu'à la *création* :
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
|
||||||
66
docs/audit/preuve-2026-08-08.md
Normal file
66
docs/audit/preuve-2026-08-08.md
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
# Preuve de conformite — Set-OPS — 2026-08-08
|
||||||
|
|
||||||
|
> Genere par `make prouver` (`scripts/prouver.py`). **Rejouable** : relancer
|
||||||
|
> reproduit ce rapport. Chaque preuve rejoue l'outillage existant du depot ;
|
||||||
|
> aucune validation n'est reimplementee ici. Voir le mode d'emploi :
|
||||||
|
> [`docs/audit/README.md`](README.md), et le registre trace :
|
||||||
|
> [`docs/audit/affirmations.md`](affirmations.md).
|
||||||
|
|
||||||
|
- **Instance** : `instance` — inventaire `instance/inventories/principal/hosts.yml`
|
||||||
|
- **Verdict** : ✅ CONFORME (29 OK · 0 echec · 1 saute)
|
||||||
|
|
||||||
|
## Preuves
|
||||||
|
|
||||||
|
| # | Preuve | Affirmations | Statut | Detail |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK | |
|
||||||
|
| P02 | Tests unitaires (inventory_host) | — | ✅ OK | 5 tests passes. |
|
||||||
|
| P03 | Diff-vide du plan (inventaire genere) | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | DIFF VIDE : le plan reproduit exactement l'inventaire actuel. Bascule possible. |
|
||||||
|
| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | |
|
||||||
|
| P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | |
|
||||||
|
| P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. |
|
||||||
|
| P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check). |
|
||||||
|
| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 30 groupes classes, aucun cycle, aucune arete en arriere. |
|
||||||
|
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 29 rôles, 74 flux, schéma + matrice OK. |
|
||||||
|
| P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). |
|
||||||
|
| P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | playbook: playbooks/proxmox/cloner_vm_debian.yml |
|
||||||
|
| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. |
|
||||||
|
| P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. |
|
||||||
|
| P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. |
|
||||||
|
| P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. |
|
||||||
|
| P16 | Inventaire Ansible complet (--list) | AFF-030 | ⚪ SAUTE | Voute chiffree sans ANSIBLE_VAULT_PASSWORD_FILE (prerequis AFF-026). |
|
||||||
|
| P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. |
|
||||||
|
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 25 secret(s) exige(s), tous presents. (Voute reelle non lisible ici : verification sautee.) |
|
||||||
|
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 28 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. |
|
||||||
|
| P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) : adressage 100% derive du seed index. |
|
||||||
|
| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 2 instance(s) federee(s), aucun index en collision. |
|
||||||
|
| P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (19 sections). |
|
||||||
|
| P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 6 reseau(x), aucune collision avec la plage tenant. |
|
||||||
|
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | CONFORME : frontiere nord/sud, 31 regles, 2 routes, admin=10.0.0.0/24,192.168.254.2/32,192.168.255.2/32. |
|
||||||
|
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 2 tenant(s), 38 groupe(s), 64 regle(s). |
|
||||||
|
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 14 hote(s) x 4 integration(s) universelle(s) : aucune lacune, aucune recopie (1 exemption(s) derivee(s) du service rendu). |
|
||||||
|
| P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 0 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. |
|
||||||
|
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 2 pool(s) Proxmox, 28 VM placee(s), aucun nom ni VMID en collision. |
|
||||||
|
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 23 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 12, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
|
||||||
|
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 2 zone(s), 12 VNet(s), 12 sous-reseau(x), aucune collision. |
|
||||||
|
|
||||||
|
## Couverture des affirmations ✅ du registre
|
||||||
|
|
||||||
|
Chaque affirmation ✅ automatisable est couverte par la preuve indiquee ci-dessus.
|
||||||
|
Les ✅ **structurelles/doctrinales** non rejouables par une commande (ex. AFF-005
|
||||||
|
`make`=aide, AFF-014 ciblage groupe, AFF-024 `instancier-appliquer`, AFF-051 autorite
|
||||||
|
d'AGENTS.md, AFF-073/075 gardes `make`, AFF-090 wiki) ont ete verifiees a l'audit ;
|
||||||
|
elles restent hors du harnais recurrent (rien d'executable a rejouer).
|
||||||
|
|
||||||
|
## Declarations d'intention (⚪ invérifiables localement — assumees)
|
||||||
|
|
||||||
|
Ces affirmations ne sont pas rejouables hors production ; elles sont **assumees**
|
||||||
|
comme declarations d'intention, non comme preuves :
|
||||||
|
|
||||||
|
- **AFF-036** — « testables avec `--check` autant que possible » : verifiable seulement
|
||||||
|
contre une flotte vivante.
|
||||||
|
- **AFF-091** — contenu pedagogique du wiki : affirmations conceptuelles.
|
||||||
|
- **AFF-096** — « GUI 100 % francais » : revue exhaustive des libelles rendus, non automatisee.
|
||||||
|
- **AFF-007** — hote d'exemple `web-frontal-01` : placeholder assume.
|
||||||
|
|
||||||
|
_Rapport genere le 2026-08-08._
|
||||||
|
|
@ -186,7 +186,39 @@ occ user:resetpassword
|
||||||
Et si LDAP lui-même est en panne, ces trois commandes fonctionnent quand même : elles ne
|
Et si LDAP lui-même est en panne, ces trois commandes fonctionnent quand même : elles ne
|
||||||
dépendent ni de Keycloak, ni de l'annuaire. C'est le sens de D-40.
|
dépendent ni de Keycloak, ni de l'annuaire. C'est le sens de D-40.
|
||||||
|
|
||||||
### 6.6 Ce que tu dois changer en priorité
|
### 6.6 « Mot de passe oublié » — pour ne pas dépendre de toi
|
||||||
|
|
||||||
|
Un utilisateur qui oublie son mot de passe ne doit pas avoir à t'appeler : la seule issue
|
||||||
|
serait alors que tu manipules le mot de passe de quelqu'un d'autre, ce que « une identité,
|
||||||
|
une personne » cherche précisément à écarter. L'écran de connexion du realm porte donc le
|
||||||
|
lien **Forgot password?**, et Keycloak envoie lui-même le courriel.
|
||||||
|
|
||||||
|
Deux conditions, toutes deux tenues par le code :
|
||||||
|
|
||||||
|
- **Un relais SMTP.** `serveur_keycloak` le dérive du plan (`applications.postfix.hote`) —
|
||||||
|
aucun nom de machine n'est écrit nulle part. Le MTA accepte les hôtes du supernet sans
|
||||||
|
authentification (`mynetworks`) et `idm-01` porte déjà `client_smtp`, donc le flux existe.
|
||||||
|
Si le plan ne déclare pas de MTA, le rôle **refuse** plutôt que d'afficher un écran qui
|
||||||
|
promet un courriel que personne n'enverrait.
|
||||||
|
- **Une adresse sur le compte.** C'est la seule valeur de l'amorçage qui ne se dérive pas :
|
||||||
|
elle désigne une personne, donc quelque chose d'extérieur au système qu'on amorce.
|
||||||
|
`amorcage_acces_courriel` doit être déclarée, et le rôle refuse de créer le compte sans
|
||||||
|
elle. Une adresse `sysadmin@<domaine_interne>` serait un piège : elle est servie par une
|
||||||
|
boîte que tu ne peux pas encore lire.
|
||||||
|
|
||||||
|
**Attention à un reliquat.** Tant que la fédération LDAP était en `READ_ONLY`, une adresse
|
||||||
|
saisie dans la console de compte restait dans la base de Keycloak sans jamais atteindre
|
||||||
|
l'annuaire. Les deux côtés pouvaient donc afficher des adresses différentes sans que rien
|
||||||
|
ne le signale. En `WRITABLE` (le mode actuel) le problème ne se reproduit pas, mais les
|
||||||
|
comptes créés avant peuvent encore porter l'écart. Pour le voir et le réduire :
|
||||||
|
|
||||||
|
```
|
||||||
|
ldapsearch -x -H ldapi:/// -D "cn=admin,$BASE" -W -b "uid=<uid>,ou=people,$BASE" mail
|
||||||
|
# puis, si les deux diffèrent : corriger dans LDAP (source de vérité), et resynchroniser
|
||||||
|
# Identity providers → LDAP → Sync all users
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.7 Ce que tu dois changer en priorité
|
||||||
|
|
||||||
1. **Le mot de passe d'amorçage** — imposé dès la première connexion, tu n'as pas le
|
1. **Le mot de passe d'amorçage** — imposé dès la première connexion, tu n'as pas le
|
||||||
choix (§3).
|
choix (§3).
|
||||||
|
|
@ -194,7 +226,7 @@ dépendent ni de Keycloak, ni de l'annuaire. C'est le sens de D-40.
|
||||||
déploiement y a eu accès. Les régénérer transfère réellement le contrôle.
|
déploiement y a eu accès. Les régénérer transfère réellement le contrôle.
|
||||||
3. **Le mot de passe de la voûte** elle-même, si le dépôt change de mains.
|
3. **Le mot de passe de la voûte** elle-même, si le dépôt change de mains.
|
||||||
|
|
||||||
### 6.7 Faire tourner un secret : l'ordre n'est pas indifférent
|
### 6.8 Faire tourner un secret : l'ordre n'est pas indifférent
|
||||||
|
|
||||||
**Les comptes de secours des services** (`vault_grafana_admin`, `vault_forgejo_admin`,
|
**Les comptes de secours des services** (`vault_grafana_admin`, `vault_forgejo_admin`,
|
||||||
`vault_nextcloud_admin`) se régénèrent en voûte, puis un redéploiement les applique. Les rôles
|
`vault_nextcloud_admin`) se régénèrent en voûte, puis un redéploiement les applique. Les rôles
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,15 @@ amorcage_acces_actif: true
|
||||||
# Le compte d'amorcage. `uid` est l'identifiant de connexion.
|
# Le compte d'amorcage. `uid` est l'identifiant de connexion.
|
||||||
amorcage_acces_uid: "sysadmin"
|
amorcage_acces_uid: "sysadmin"
|
||||||
amorcage_acces_nom: "Administrateur systeme"
|
amorcage_acces_nom: "Administrateur systeme"
|
||||||
amorcage_acces_courriel: "" # facultatif ; vide = pas d'attribut mail
|
# A DECLARER — la seule valeur de ce role qui ne peut PAS se deriver. Elle
|
||||||
|
# designe une personne, qui est par definition hors du systeme qu'on amorce :
|
||||||
|
# une adresse `{{ amorcage_acces_uid }}@{{ '{{' }} domaine_interne {{ '}}' }}`
|
||||||
|
# serait servie par une boite que l'exploitant ne peut pas encore lire.
|
||||||
|
#
|
||||||
|
# Sans elle, « mot de passe oublie » (serveur_keycloak) affiche un ecran qui
|
||||||
|
# promet un courriel dont l'adresse n'existe nulle part, et le seul recours
|
||||||
|
# redevient l'exploitant manipulant le mot de passe d'autrui.
|
||||||
|
amorcage_acces_courriel: ""
|
||||||
|
|
||||||
# Le groupe qui porte l'habilitation. Les services le reconnaissent par
|
# Le groupe qui porte l'habilitation. Les services le reconnaissent par
|
||||||
# `meta/acces.yml` — eux sont reconcilies, l'appartenance ne l'est pas.
|
# `meta/acces.yml` — eux sont reconcilies, l'appartenance ne l'est pas.
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,22 @@
|
||||||
|
|
||||||
# --- Tout ce qui suit ne s'exécute QUE si le compte est absent -----------------
|
# --- 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
|
# 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
|
# littéralement : le jeton d'amorçage se retrouverait en clair dans la base, lisible
|
||||||
# par quiconque peut lire l'annuaire.
|
# par quiconque peut lire l'annuaire.
|
||||||
|
|
|
||||||
|
|
@ -120,3 +120,25 @@ serveur_keycloak_amorcage_uid: "{{ amorcage_acces_uid | default('sysadmin') }}"
|
||||||
# Empreinte du mot de passe de LIAISON LDAP applique — Keycloak le masque, on ne
|
# Empreinte du mot de passe de LIAISON LDAP applique — Keycloak le masque, on ne
|
||||||
# peut donc pas le comparer. Permet sa rotation. Voir tasks/federation-ldap.yml.
|
# peut donc pas le comparer. Permet sa rotation. Voir tasks/federation-ldap.yml.
|
||||||
serveur_keycloak_marqueur_bind: "/etc/keycloak/.setops-bind-sha256"
|
serveur_keycloak_marqueur_bind: "/etc/keycloak/.setops-bind-sha256"
|
||||||
|
|
||||||
|
# --- Courriel du realm : « mot de passe oublie » ------------------------------
|
||||||
|
# Sans SMTP, `resetPasswordAllowed` ne sert a rien — Keycloak n'a aucun moyen
|
||||||
|
# d'envoyer le lien. Et sans « mot de passe oublie », chaque oubli passe par
|
||||||
|
# l'exploitant, qui manipule alors le mot de passe de quelqu'un d'autre : ce que
|
||||||
|
# « une identite, une personne » cherche precisement a eviter.
|
||||||
|
#
|
||||||
|
# Le relais est le MTA du tenant, DERIVE du plan (`applications.postfix.hote`) :
|
||||||
|
# il accepte les hotes du supernet sans authentification (`mynetworks`), et
|
||||||
|
# `idm-01` porte deja `client_smtp` — le flux existe.
|
||||||
|
serveur_keycloak_smtp_actif: true
|
||||||
|
serveur_keycloak_smtp_app: "postfix"
|
||||||
|
serveur_keycloak_smtp_port: 25
|
||||||
|
serveur_keycloak_smtp_starttls: true
|
||||||
|
serveur_keycloak_smtp_expediteur: "identite@{{ domaine_interne }}"
|
||||||
|
serveur_keycloak_smtp_nom_expediteur: "{{ organisation | default('Set-OPS') }}"
|
||||||
|
# Repli : utilise seulement si le plan ne declare pas d'application `postfix`.
|
||||||
|
serveur_keycloak_smtp_hote: ""
|
||||||
|
|
||||||
|
# « Mot de passe oublie » sur l'ecran de connexion. Exige le SMTP ci-dessus et une
|
||||||
|
# adresse `mail` sur le compte — sans elle, Keycloak n'a nulle part ou ecrire.
|
||||||
|
serveur_keycloak_reset_mot_de_passe: true
|
||||||
|
|
|
||||||
76
roles/serveur_keycloak/tasks/courriel-realm.yml
Normal file
76
roles/serveur_keycloak/tasks/courriel-realm.yml
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
---
|
||||||
|
# Courriel du realm + « mot de passe oublie ». Voir defaults/main.yml.
|
||||||
|
#
|
||||||
|
# API d'administration et NON `kcadm.sh` : sur `smtpServer` (une map), kcadm
|
||||||
|
# accepte `-s smtpServer.host=…` COMME `-s 'smtpServer={"host":…}'`, sort en
|
||||||
|
# succes, n'ecrit rien — le champ restait `{ }` apres deux deploiements verts.
|
||||||
|
# Constate le 2026-08-08. Meme famille que le reste : une valeur declaree d'un
|
||||||
|
# cote, jamais verifiee de l'autre.
|
||||||
|
|
||||||
|
- name: Dériver l'hôte du relais SMTP depuis le plan
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
serveur_keycloak_smtp_hote: "{{ applications[serveur_keycloak_smtp_app].hote ~ '.' ~ domaine_interne }}"
|
||||||
|
when:
|
||||||
|
- serveur_keycloak_smtp_actif | bool
|
||||||
|
- ((applications | default({})).get(serveur_keycloak_smtp_app, {}).get('hote') | default('')) | length > 0
|
||||||
|
|
||||||
|
- name: Exiger un relais SMTP résolu
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- serveur_keycloak_smtp_hote | length > 0
|
||||||
|
fail_msg: >-
|
||||||
|
Aucun relais SMTP : l'application « {{ serveur_keycloak_smtp_app }} » n'est pas
|
||||||
|
déclarée au plan et `serveur_keycloak_smtp_hote` est vide. Activer
|
||||||
|
« mot de passe oublié » sans relais donnerait un écran qui promet un courriel
|
||||||
|
que personne n'enverrait.
|
||||||
|
when: serveur_keycloak_smtp_actif | bool
|
||||||
|
|
||||||
|
- name: Obtenir un jeton d'administration
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "http://localhost:8080/realms/master/protocol/openid-connect/token"
|
||||||
|
method: POST
|
||||||
|
body_format: form-urlencoded
|
||||||
|
body:
|
||||||
|
grant_type: password
|
||||||
|
client_id: admin-cli
|
||||||
|
username: "{{ serveur_keycloak_admin_user }}"
|
||||||
|
password: "{{ serveur_keycloak_admin_password }}"
|
||||||
|
register: serveur_keycloak_smtp_jeton
|
||||||
|
no_log: true
|
||||||
|
|
||||||
|
- name: Lire la strophe courriel en place
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "http://localhost:8080/admin/realms/{{ serveur_keycloak_realm }}"
|
||||||
|
headers:
|
||||||
|
Authorization: "Bearer {{ serveur_keycloak_smtp_jeton.json.access_token }}"
|
||||||
|
register: serveur_keycloak_realm_actuel
|
||||||
|
no_log: true
|
||||||
|
|
||||||
|
- name: Composer la strophe courriel attendue
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
serveur_keycloak_smtp_attendu:
|
||||||
|
host: "{{ serveur_keycloak_smtp_hote }}"
|
||||||
|
port: "{{ serveur_keycloak_smtp_port | string }}"
|
||||||
|
from: "{{ serveur_keycloak_smtp_expediteur }}"
|
||||||
|
fromDisplayName: "{{ serveur_keycloak_smtp_nom_expediteur }}"
|
||||||
|
starttls: "{{ serveur_keycloak_smtp_starttls | bool | string | lower }}"
|
||||||
|
auth: "false"
|
||||||
|
ssl: "false"
|
||||||
|
|
||||||
|
- name: Configurer le courriel du realm et le « mot de passe oublié »
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "http://localhost:8080/admin/realms/{{ serveur_keycloak_realm }}"
|
||||||
|
method: PUT
|
||||||
|
status_code: [204]
|
||||||
|
headers:
|
||||||
|
Authorization: "Bearer {{ serveur_keycloak_smtp_jeton.json.access_token }}"
|
||||||
|
body_format: json
|
||||||
|
body:
|
||||||
|
smtpServer: "{{ serveur_keycloak_smtp_attendu }}"
|
||||||
|
resetPasswordAllowed: "{{ serveur_keycloak_reset_mot_de_passe | bool }}"
|
||||||
|
no_log: true
|
||||||
|
changed_when: true
|
||||||
|
when: >-
|
||||||
|
(serveur_keycloak_realm_actuel.json.smtpServer | default({})) != serveur_keycloak_smtp_attendu
|
||||||
|
or (serveur_keycloak_realm_actuel.json.resetPasswordAllowed | default(false) | bool)
|
||||||
|
!= (serveur_keycloak_reset_mot_de_passe | bool)
|
||||||
|
|
@ -163,6 +163,10 @@
|
||||||
ansible.builtin.include_tasks: federation-ldap.yml
|
ansible.builtin.include_tasks: federation-ldap.yml
|
||||||
when: serveur_keycloak_ldap_federation | bool
|
when: serveur_keycloak_ldap_federation | bool
|
||||||
|
|
||||||
|
- name: Configurer le courriel du realm (« mot de passe oublié »)
|
||||||
|
ansible.builtin.include_tasks: courriel-realm.yml
|
||||||
|
when: serveur_keycloak_smtp_actif | bool
|
||||||
|
|
||||||
- name: Projeter les groupes LDAP (mapper + roles de groupe)
|
- name: Projeter les groupes LDAP (mapper + roles de groupe)
|
||||||
ansible.builtin.include_tasks: groupes-ldap.yml
|
ansible.builtin.include_tasks: groupes-ldap.yml
|
||||||
when:
|
when:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue