rotation : vault_forgejo_oidc, et la reconciliation des secrets OIDC

Le secret avait fui dans une sortie de diagnostic. Le faire tourner a d'abord
demande de rendre la rotation possible : ni Keycloak ni Forgejo ne reconciliaient
un secret OIDC existant. Le commentaire de clients-oidc.yml l'avouait
(« create-si-absent »), et `update-oauth` ne passait pas --secret.

Regenerer la voute aurait laisse les deux cotes sur l'ancienne valeur — ou un
seul des deux, et le SSO aurait casse sans que rien ne l'annonce.

Keycloak compare desormais le secret EN PLACE a celui voulu avant d'ecrire.
Verifie par empreinte aux trois endroits : voute, Keycloak, Forgejo — identiques.
Keycloak rejoue a changed=0.

Signale sans etre corrige : `Deployer app.ini` change a chaque passage. Forgejo
reecrit lui-meme ce fichier (il y persiste ses secrets generes) et le gabarit
l'ecrase. Prealable au correctif : decider quelles cles appartiennent au gabarit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-08-07 22:20:11 -04:00
parent b51933566b
commit 35602546eb
3 changed files with 54 additions and 5 deletions

View file

@ -2,6 +2,35 @@
## 2026-08-06 — le chemin nord-sud devient dérivable
### `vault_forgejo_oidc` — le secret exposé ne vaut plus rien
Il avait fui dans une sortie de diagnostic. Le faire tourner a d'abord demandé de rendre la
rotation possible : **ni Keycloak ni Forgejo ne réconciliaient un secret OIDC existant.**
Le commentaire de `clients-oidc.yml` l'avouait — *« la réconciliation fine n'est pas faite :
create-si-absent »* — et `update-oauth`, côté Forgejo, ne passait pas `--secret`. Régénérer la
voûte aurait donc laissé les deux côtés sur l'ancienne valeur, ou pire, un seul des deux : le
SSO aurait cassé sans que rien ne l'annonce.
Les deux réconcilient désormais. Keycloak compare le secret **en place** (`get client-secret`)
à celui voulu avant d'écrire — pas de `changed` inutile.
**Vérifié par empreinte, aux trois endroits :**
```
voûte 2484770b53a4fd76f9b57bf1
Keycloak 2484770b53a4fd76f9b57bf1
Forgejo 2484770b53a4fd76f9b57bf1
```
Keycloak rejoue à `changed=0`.
**Une non-idempotence préexistante, signalée sans être corrigée** : `Deployer app.ini` change à
chaque passage sur Forgejo. Le rôle réécrit un fichier que Forgejo modifie lui-même — il y
persiste ses secrets générés. Ce n'est pas lié à la rotation, et le corriger demande de décider
quelles clés appartiennent au gabarit et lesquelles au service.
### `vault_keycloak_admin` : le secret qui est la clé de son propre changement
Rotation faite, chaîne d'identité intacte (groupe, membre, rôle), rejeu à `changed=0`.

View file

@ -180,8 +180,11 @@
# `update-oauth` est idempotent cote Forgejo : il reecrit les memes valeurs
# sans effet de bord. On ne peut pas comparer avant/apres (la CLI n'expose
# pas le detail d'une source), donc on ne signale PAS `changed`.
# `--secret` fait partie de la reconciliation : sans lui, faire tourner
# `vault_forgejo_oidc` laissait Forgejo sur l'ancienne valeur.
$FJ admin auth update-oauth --id "$ID" \
--auto-discover-url "{{ serveur_forgejo_oidc_discovery }}" \
--secret "$OIDC_SECRET" \
$OPTS_GROUPE >/dev/null
echo SETOPS_OK
fi

View file

@ -1,7 +1,11 @@
---
# Enregistre les clients OIDC applicatifs dans le realm via kcadm — idempotent.
# Crée le client s'il est absent (confidentiel, authorization code). La réconciliation
# fine (mettre à jour un client existant) n'est pas faite : create-si-absent.
# Crée le client s'il est absent, et RÉCONCILIE son secret et ses URI s'il existe.
#
# « Create-si-absent » rendait la rotation d'un secret OIDC impossible : la voûte
# changeait, l'application recevait la nouvelle valeur, et Keycloak gardait
# l'ancienne — le SSO cassait sans que rien ne l'annonce. Constaté le 2026-08-07,
# au moment de faire tourner `vault_forgejo_oidc`, exposé dans une transcription.
- name: Enregistrer les clients OIDC (kcadm, idempotent)
ansible.builtin.shell:
executable: /bin/bash
@ -10,9 +14,22 @@
KC={{ serveur_keycloak_home }}/bin/kcadm.sh
"$KC" config credentials --server http://localhost:8080 --realm master \
--user {{ serveur_keycloak_admin_user }} --password "$KC_ADMIN_PW" >/dev/null
if "$KC" get clients -r {{ serveur_keycloak_realm }} -q clientId={{ item.clientId }} 2>/dev/null \
| grep -q '"{{ item.clientId }}"'; then
echo SETOPS_OK
CID=$("$KC" get clients -r {{ serveur_keycloak_realm }} -q clientId={{ item.clientId }} \
--fields id --format csv --noquotes 2>/dev/null | head -1 || true)
if [ -n "$CID" ]; then
# Le secret EN PLACE, compare a celui voulu. `get client-secret` le rend en
# clair : c'est la seule facon de savoir s'il faut ecrire.
ACTUEL=$("$KC" get clients/"$CID"/client-secret -r {{ serveur_keycloak_realm }} 2>/dev/null \
| sed -n 's/.*"value" *: *"\([^"]*\)".*/\1/p' | head -1 || true)
if [ "$ACTUEL" != "$OIDC_SECRET" ]; then
"$KC" update clients/"$CID" -r {{ serveur_keycloak_realm }} \
-s secret="$OIDC_SECRET" \
-s 'redirectUris={{ item.redirect_uris | to_json }}' \
-s 'webOrigins={{ item.web_origins | default([]) | to_json }}' >/dev/null
echo SETOPS_CHANGED
else
echo SETOPS_OK
fi
else
"$KC" create clients -r {{ serveur_keycloak_realm }} \
-s clientId={{ item.clientId }} -s enabled=true -s protocol=openid-connect \