diff --git a/CHANGELOG.md b/CHANGELOG.md index 98c190b..65f03cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,35 @@ ## 2026-08-06 — le chemin nord-sud devient dérivable +### `resoudre_idp` — le nom inventé était dans **quatre** rôles + +`forge-01` a échoué sur `serveur_forgejo_oidc_discovery`, qui pointait +`https://keycloak.` — le nom que rien ne publie. J'avais corrigé exactement ça dans +`serveur_oauth2_proxy` quelques heures plus tôt, en croyant régler un cas isolé. + +Il était en fait dans **quatre** rôles : forgejo, grafana, nextcloud, oauth2-proxy. Chacun +fabriquait le même nom par la même convention, et chacun aurait échoué au même endroit — +`oauth2-proxy` l'a fait le premier parce qu'il a été déployé le premier. + +Une cinquième copie corrigée à la main aurait divergé comme les quatre autres. D'où +`roles/resoudre_idp` : il lit l'exposition déclarée au plan et rend `resoudre_idp_hote`, +`resoudre_idp_base`, `resoudre_idp_discovery`. Les trois rôles restants l'appellent ; leur +défaut ne garde plus qu'un **repli** nommé, jamais la valeur de travail. + +Vérifié en base sur `forge-01` : + +``` +OpenIDConnectAutoDiscoveryURL : https://auth.chezlepro.internal/realms/chezlepro/… +GroupClaimName : 'groups' AdminGroup : 'sysadmin' +``` + +Le câblage par groupe et l'URL correcte, sur un service déployé pour la première fois. + +**La leçon est la même que pour `resoudre_annuaire` hier**, et elle mérite d'être dite deux +fois : corriger la valeur là où elle échoue ne corrige que là. Ce sont les autres copies, +silencieuses, qui coûtent la journée suivante. + + ### Forgejo et Nextcloud câblés — avant d'être déployés Les deux services n'existaient pas encore : les câbler maintenant vaut mieux que les corriger diff --git a/roles/resoudre_idp/README.md b/roles/resoudre_idp/README.md new file mode 100644 index 0000000..7f0cf7e --- /dev/null +++ b/roles/resoudre_idp/README.md @@ -0,0 +1,23 @@ +# resoudre_idp — l'URL publique du fournisseur d'identite + +Resout `auth.` depuis l'exposition que le plan declare, au lieu de fabriquer +`keycloak.` par convention. + +## Pourquoi + +Quatre roles construisaient le nom a la main. Aucun ne le publiait : le DNS resout +`auth`, nginx sert `auth`, et Keycloak s'annonce sous `auth`. Un client qui interroge +`keycloak.` echoue — ou recoit un issuer qui ne correspond pas au sien +(« issuer did not match the issuer returned by provider »). + +Corrige une premiere fois dans `serveur_oauth2_proxy`, puis retrouve a l'identique +dans trois autres roles. Une convention de nommage se contredit ; une declaration se +corrige. + +## Sorties + +| Fait | Exemple | +|---|---| +| `resoudre_idp_hote` | `auth.chezlepro.internal` | +| `resoudre_idp_base` | `https://auth.chezlepro.internal/realms/chezlepro` | +| `resoudre_idp_discovery` | `…/.well-known/openid-configuration` | diff --git a/roles/resoudre_idp/defaults/main.yml b/roles/resoudre_idp/defaults/main.yml new file mode 100644 index 0000000..838d573 --- /dev/null +++ b/roles/resoudre_idp/defaults/main.yml @@ -0,0 +1,5 @@ +--- +# Application du plan qui EST le fournisseur d'identite. +resoudre_idp_app: "keycloak" +# Realm applicatif — le meme pour tous les consommateurs d'une instance. +resoudre_idp_realm: "{{ identite_realm | default('chezlepro') }}" diff --git a/roles/resoudre_idp/tasks/main.yml b/roles/resoudre_idp/tasks/main.yml new file mode 100644 index 0000000..04f2555 --- /dev/null +++ b/roles/resoudre_idp/tasks/main.yml @@ -0,0 +1,42 @@ +--- +# Role utilitaire (pas un groupe deployable) : resout l'URL PUBLIQUE du fournisseur +# d'identite depuis l'exposition que le plan declare. +# +# Sorties (facts) : +# resoudre_idp_hote — auth. (le FQDN publie, pas un nom fabrique) +# resoudre_idp_base — https:///realms/ +# resoudre_idp_discovery — .../.well-known/openid-configuration +# +# POURQUOI CE ROLE. Quatre roles fabriquaient `https://keycloak.` par +# convention de nommage — un nom que RIEN ne publie : le plan expose Keycloak sous +# `auth.`, et c'est ce nom que le DNS resout, que nginx sert et que +# Keycloak annonce dans son issuer. Un client OIDC qui interroge l'autre nom +# echoue, ou recoit un issuer qui ne correspond pas au sien. +# +# Corrige une premiere fois dans `serveur_oauth2_proxy` le 2026-08-07, puis +# retrouve a l'identique dans forgejo, grafana et nextcloud. Une quatrieme copie +# aurait diverge comme les trois premieres : d'ou ce role. +- name: Charger les registres applications et domaines (expositions) + ansible.builtin.include_vars: + file: "{{ item }}" + loop: + - "{{ setops_plan_dir }}/applications.yml" + - "{{ setops_plan_dir }}/domaines.yml" + +- name: Exiger que l'IdP declare une exposition + ansible.builtin.assert: + that: + - ((applications | default({})).get(resoudre_idp_app, {}).get('expose', []) | length) > 0 + fail_msg: >- + L'application `{{ resoudre_idp_app }}` n'expose aucun FQDN dans + instance/plan/applications.yml. Un client OIDC ne peut pas joindre un + fournisseur qui n'est publie nulle part. + +- name: Deriver l'URL publique du fournisseur d'identite + ansible.builtin.set_fact: + resoudre_idp_hote: >- + {{ (applications[resoudre_idp_app].expose | first) }} + resoudre_idp_base: >- + https://{{ (applications[resoudre_idp_app].expose | first) }}/realms/{{ resoudre_idp_realm }} + resoudre_idp_discovery: >- + https://{{ (applications[resoudre_idp_app].expose | first) }}/realms/{{ resoudre_idp_realm }}/.well-known/openid-configuration diff --git a/roles/serveur_forgejo/defaults/main.yml b/roles/serveur_forgejo/defaults/main.yml index fa59a66..15a23af 100644 --- a/roles/serveur_forgejo/defaults/main.yml +++ b/roles/serveur_forgejo/defaults/main.yml @@ -60,7 +60,12 @@ serveur_forgejo_oidc_nom: "{{ organisation | default('organisation') | lower | r serveur_forgejo_oidc_client_id: "forgejo" serveur_forgejo_oidc_client_secret: "{{ vault_forgejo_oidc | default('') }}" # voûte (vault_forgejo_oidc) serveur_forgejo_oidc_realm: "{{ identite_realm | default('chezlepro') }}" -serveur_forgejo_oidc_discovery: "https://keycloak.{{ domaine_interne }}/realms/{{ serveur_forgejo_oidc_realm }}/.well-known/openid-configuration" +# DERIVE de l'exposition declaree au plan (role `resoudre_idp`). La valeur +# ci-dessous n'est qu'un REPLI : `keycloak.` n'est publie nulle part. +serveur_forgejo_oidc_repli: >- + https://keycloak.{{ domaine_interne }}/realms/{{ serveur_forgejo_oidc_realm }}/.well-known/openid-configuration +serveur_forgejo_oidc_discovery: >- + {{ resoudre_idp_discovery | default(serveur_forgejo_oidc_repli) }} # TLS vers PostgreSQL (zero-confiance). 'disable' = compat ; 'verify-full' = chiffre + verifie # le cert serveur contre le root_ca step-ca (via PGSSLROOTCERT dans l'unite systemd). diff --git a/roles/serveur_forgejo/tasks/main.yml b/roles/serveur_forgejo/tasks/main.yml index 5baff8d..4c9be3b 100644 --- a/roles/serveur_forgejo/tasks/main.yml +++ b/roles/serveur_forgejo/tasks/main.yml @@ -1,4 +1,12 @@ --- + +# L'URL PUBLIQUE de l'IdP se derive de l'exposition declaree au plan, elle ne se +# fabrique pas : `keycloak.` n'est publie nulle part (voir resoudre_idp). +- name: Resoudre le fournisseur d'identite (role partage) + ansible.builtin.include_role: + name: resoudre_idp + vars: + resoudre_idp_realm: "{{ serveur_forgejo_oidc_realm }}" - name: Refuser de fermer la connexion locale sur un Forgejo anterieur a la v10 # `ENABLE_INTERNAL_SIGNIN` a ete ajoute en v10 (suivi amont, issue 7476). Sur une # version anterieure il est ignore EN SILENCE : le role croirait avoir ferme la porte diff --git a/roles/serveur_grafana/defaults/main.yml b/roles/serveur_grafana/defaults/main.yml index e4245b4..4695fd7 100644 --- a/roles/serveur_grafana/defaults/main.yml +++ b/roles/serveur_grafana/defaults/main.yml @@ -36,7 +36,11 @@ serveur_grafana_oidc_nom: "{{ organisation | default('Organisation') }}" serveur_grafana_oidc_client_id: "grafana" serveur_grafana_oidc_client_secret: "{{ vault_grafana_oidc | default('') }}" # voûte (vault_grafana_oidc) serveur_grafana_oidc_realm: "{{ identite_realm | default('chezlepro') }}" -serveur_grafana_oidc_base: "https://keycloak.{{ domaine_interne }}/realms/{{ serveur_grafana_oidc_realm }}/protocol/openid-connect" +# DERIVE de l'exposition declaree au plan (role `resoudre_idp`) ; repli seulement. +serveur_grafana_oidc_repli: >- + https://keycloak.{{ domaine_interne }}/realms/{{ serveur_grafana_oidc_realm }} +serveur_grafana_oidc_base: >- + {{ (resoudre_idp_base | default(serveur_grafana_oidc_repli)) }}/protocol/openid-connect serveur_grafana_oidc_scopes: "openid email profile" # RBAC via SSO : mappe le claim 'roles' (rôles de realm Keycloak) vers le niveau Grafana. # Défaut : grafana-admin→Admin, grafana-editor→Editor, sinon Viewer. Vider pour désactiver. diff --git a/roles/serveur_grafana/tasks/main.yml b/roles/serveur_grafana/tasks/main.yml index 8bb1a5f..eabe9ad 100644 --- a/roles/serveur_grafana/tasks/main.yml +++ b/roles/serveur_grafana/tasks/main.yml @@ -1,4 +1,12 @@ --- + +# L'URL PUBLIQUE de l'IdP se derive de l'exposition declaree au plan, elle ne se +# fabrique pas : `keycloak.` n'est publie nulle part (voir resoudre_idp). +- name: Resoudre le fournisseur d'identite (role partage) + ansible.builtin.include_role: + name: resoudre_idp + vars: + resoudre_idp_realm: "{{ serveur_grafana_oidc_realm }}" - name: Exiger le mot de passe admin Grafana (Vault) ansible.builtin.assert: that: diff --git a/roles/serveur_nextcloud/defaults/main.yml b/roles/serveur_nextcloud/defaults/main.yml index 14158db..9d9f6e9 100644 --- a/roles/serveur_nextcloud/defaults/main.yml +++ b/roles/serveur_nextcloud/defaults/main.yml @@ -62,7 +62,11 @@ serveur_nextcloud_oidc_nom: "{{ organisation | default('Organisation') }}" serveur_nextcloud_oidc_client_id: "nextcloud" serveur_nextcloud_oidc_client_secret: "{{ vault_nextcloud_oidc | default('') }}" serveur_nextcloud_oidc_realm: "{{ identite_realm | default('chezlepro') }}" -serveur_nextcloud_oidc_discovery: "https://keycloak.{{ domaine_interne }}/realms/{{ serveur_nextcloud_oidc_realm }}/.well-known/openid-configuration" +# DERIVE de l'exposition declaree au plan (role `resoudre_idp`) ; repli seulement. +serveur_nextcloud_oidc_repli: >- + https://keycloak.{{ domaine_interne }}/realms/{{ serveur_nextcloud_oidc_realm }}/.well-known/openid-configuration +serveur_nextcloud_oidc_discovery: >- + {{ resoudre_idp_discovery | default(serveur_nextcloud_oidc_repli) }} # --- Collabora Online (WOPI) — app richdocuments --- serveur_nextcloud_office_actif: true diff --git a/roles/serveur_nextcloud/tasks/main.yml b/roles/serveur_nextcloud/tasks/main.yml index 4c08193..7797b38 100644 --- a/roles/serveur_nextcloud/tasks/main.yml +++ b/roles/serveur_nextcloud/tasks/main.yml @@ -1,4 +1,12 @@ --- + +# L'URL PUBLIQUE de l'IdP se derive de l'exposition declaree au plan, elle ne se +# fabrique pas : `keycloak.` n'est publie nulle part (voir resoudre_idp). +- name: Resoudre le fournisseur d'identite (role partage) + ansible.builtin.include_role: + name: resoudre_idp + vars: + resoudre_idp_realm: "{{ serveur_nextcloud_oidc_realm }}" - name: Exiger les secrets Nextcloud (Vault) ansible.builtin.assert: that: