From 2bbe12299f846e45cb5a8e9eae5aaf948546242c Mon Sep 17 00:00:00 2001 From: Daniel Allaire Date: Tue, 29 Sep 2026 00:30:43 -0400 Subject: [PATCH] sonde passerelle : le trajet d un visiteur jusqu a la page de connexion Apres /ping, /oauth2/start doit renvoyer vers l emetteur configure pour ce client, et l IdP, joint depuis la passerelle, doit servir sa page de connexion. Le secret du client n est pas teste (journal du realm). Port derive de l ecoute ; archive oauth2-proxy plus retransferee a chaque deploiement. Fin de la revue des sondes. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 39 +++++++++++ roles/serveur_oauth2_proxy/defaults/main.yml | 10 +++ .../serveur_oauth2_proxy/meta/supervision.yml | 4 +- roles/serveur_oauth2_proxy/tasks/main.yml | 7 +- .../templates/sonde-passerelle.sh.j2 | 69 +++++++++++++++---- wiki/Rôle-serveur_oauth2_proxy.md | 2 +- 6 files changed, 114 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25ecb2a..6ce7afe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,44 @@ # CHANGELOG — Set-OPS +## 2026-09-29 (29) — `passerelle` fait le trajet d'un visiteur jusqu'à la page de connexion ; fin de la revue des sondes + +Cinquième et dernière des sondes « répond » de la revue (25). + +**Avant.** `check_http` sur `/ping` : vert tant que le processus oauth2-proxy vit, même +quand plus personne ne peut entrer — client supprimé ou renommé dans Keycloak, URL de +retour retirée du client, IdP injoignable depuis la machine de la passerelle. + +**Maintenant.** Après `/ping`, la sonde fait le trajet d'un visiteur : `/oauth2/start` doit +renvoyer vers l'émetteur configuré (lu dans la configuration déployée), pour CE client ; puis +l'IdP, joint depuis cette machine avec le même magasin de confiance que la passerelle, doit +servir sa page de connexion (200). C'est aussi le chemin de l'échange du code au retour. +Un refus de Keycloak est nommé (`Invalid parameter: redirect_uri`, `Client not found`…) ; +un IdP injoignable rend le code d'erreur de curl. + +**Ce qu'elle ne teste pas : le secret du client.** Le vérifier demande un échange de jeton +raté, que Keycloak écrit dans le journal de sécurité du realm — toutes les 15 minutes, par +passerelle, ce serait noyer le journal où l'on cherche les vraies tentatives. Le cas sain +n'y écrit rien. + +**Éprouvé.** Au vert sur les quatre passerelles (vigie et console, chez les deux +locataires), 35 ms ; le site n'en a pas. Mises en défaut chez Chezlepro : URL de retour non +autorisée (`serveur_oauth2_proxy_sonde_retour`) → 400 nommé ; configuration illisible ; port +fermé ; magasin de confiance inutilisable → « n'atteint pas l'IdP (curl 77) ». Toutes +critiques. **À savoir en lisant le journal du realm `chezlepro`** : ces épreuves y ont laissé +cinq `LOGIN_ERROR` (`redirect_uri` vers `exemple.invalid`), les 28 et 29 septembre. + +**Au passage.** Le port de la sonde était écrit en dur (4180) : il dérive de l'écoute. Un +échec de `/ping` sortait sur deux lignes : une seule. Et l'archive oauth2-proxy repartait du +contrôleur à chaque déploiement, pour la même raison que Keycloak en (26) — le repère était +dans `/tmp` ; c'est désormais le binaire installé. Plus aucun rôle ne prend `/tmp` pour +repère. Limite qui demeure, dans les deux rôles : un changement de VERSION ne réinstalle pas +(l'extraction s'arrête sur `creates`) ; à traiter le jour d'une montée de version. + +**Bilan de la revue.** Les cinq sondes qui disaient « répond » disent désormais « sert » : +`filtrage` (GTUBE analysé), `edge` (chaque exposition servie), `identite` (Keycloak +authentifié auprès de l'annuaire), `vigie` (base, annuaire ou comptes, Redis), `passerelle` +(trajet jusqu'à la connexion). + ## 2026-09-28 (28) — `vigie` vérifie ce que la vigie lit, pas seulement qu'elle répond Quatrième des cinq sondes « répond » de la revue (25). diff --git a/roles/serveur_oauth2_proxy/defaults/main.yml b/roles/serveur_oauth2_proxy/defaults/main.yml index 20f0582..978825c 100644 --- a/roles/serveur_oauth2_proxy/defaults/main.yml +++ b/roles/serveur_oauth2_proxy/defaults/main.yml @@ -69,3 +69,13 @@ serveur_oauth2_proxy_cache_local: "{{ setops_cache_artefacts | default(lookup('e # CRITIQUE sans rien casser, ce qui rend la seconde preuve REJOUABLE. Une sonde se prouve # deux fois — verte sur le sain, rouge sur le casse. serveur_oauth2_proxy_sonde_attendu: "OK" + +# LE PORT QUE LA SONDE INTERROGE, DERIVE DE L'ECOUTE (2026-09-28). Il etait ecrit en dur +# (4180) dans le gabarit : une passerelle deplacee aurait fait crier une sonde saine. +serveur_oauth2_proxy_sonde_port: "{{ serveur_oauth2_proxy_ecoute.split(':') | last }}" + +# L'URL DE RETOUR QUE LA SONDE PRESENTE A KEYCLOAK. Vide = celle que la passerelle envoie +# vraiment (le cas normal). Mise en defaut PAR PARAMETRE de la seconde etape : une URL que +# le client n'autorise pas — Keycloak refuse (400), comme il refuserait tout le monde. +# Chaque mise en defaut laisse UNE erreur dans le journal du realm ; le cas sain, aucune. +serveur_oauth2_proxy_sonde_retour: "" diff --git a/roles/serveur_oauth2_proxy/meta/supervision.yml b/roles/serveur_oauth2_proxy/meta/supervision.yml index 0b20c29..04259aa 100644 --- a/roles/serveur_oauth2_proxy/meta/supervision.yml +++ b/roles/serveur_oauth2_proxy/meta/supervision.yml @@ -15,6 +15,6 @@ sondes: - nom: passerelle ttl: 5400 - raison: 'La passerelle d''authentification repond-elle ? Elle porte l''acces aux applications sans - SSO natif — la supervision la premiere. Quand elle meurt, les services derriere restent + raison: 'La passerelle d''authentification repond-elle, et mene-t-elle vraiment a la page de connexion + de l''IdP pour son client ? Elle porte l''acces aux applications sans SSO natif — la vigie, la console. Quand elle meurt, les services derriere restent debout et deviennent injoignables : on cherche la panne du mauvais cote.' diff --git a/roles/serveur_oauth2_proxy/tasks/main.yml b/roles/serveur_oauth2_proxy/tasks/main.yml index 65af08f..175e384 100644 --- a/roles/serveur_oauth2_proxy/tasks/main.yml +++ b/roles/serveur_oauth2_proxy/tasks/main.yml @@ -58,9 +58,14 @@ # Sans garde, `get_url` recontacte le serveur distant a CHAQUE deploiement — et un # serveur tiers lent suffit alors a faire tomber un deploiement de flotte. Constate le # 2026-08-09 sur le binaire Forgejo, meme motif ici. +# +# LE REPERE EST LE BINAIRE INSTALLE, PAS L'ARCHIVE (2026-09-28). On regardait l'archive +# dans /tmp : /tmp se vide, et elle repartait du controleur a chaque deploiement (`changed` +# sans raison). Le binaire est exactement ce que l'extraction attend (`creates`) : present, +# il n'y a rien a apporter. Meme correction que `serveur_keycloak`. - name: L artefact de cette version est-il deja pose ? ansible.builtin.stat: - path: "/tmp/oauth2-proxy-{{ serveur_oauth2_proxy_version }}.tar.gz" + path: "{{ serveur_oauth2_proxy_binaire }}" register: serveur_oauth2_proxy_archive_present # Le controleur telecharge, puis pousse par SSH — la cible ne tire jamais d'Internet. diff --git a/roles/serveur_oauth2_proxy/templates/sonde-passerelle.sh.j2 b/roles/serveur_oauth2_proxy/templates/sonde-passerelle.sh.j2 index a021521..a6713f1 100644 --- a/roles/serveur_oauth2_proxy/templates/sonde-passerelle.sh.j2 +++ b/roles/serveur_oauth2_proxy/templates/sonde-passerelle.sh.j2 @@ -1,20 +1,63 @@ #!/bin/bash # GENERE par Set-OPS (role serveur_oauth2_proxy). Ne pas editer a la main. # -# SONDE « passerelle » — La passerelle d'authentification repond-elle ? -# -# UNE ENVELOPPE, PAS UNE SONDE ECRITE A LA MAIN. `check_http` vient de -# `monitoring-plugins` : il parle deja l'API des greffons Nagios, gere les delais, les -# redirections et vingt ans de cas limites. On lui passe l'adresse et ce qu'on attend, et -# on rend SON code de sortie tel quel. Ecrire du shell ici serait se donner du code a -# maintenir et se priver de tout ca — voir docs/supervision-conception.md. +# SONDE « passerelle » — la passerelle repond, et MENE VRAIMENT a la connexion. # # CE QUI TOMBE AVEC ELLE N'EST PAS ELLE. Cette passerelle porte l'acces aux applications -# qui n'ont pas de SSO natif — la supervision la premiere. Quand elle meurt, les services +# qui n'ont pas de SSO natif — la vigie, la console. Quand elle meurt, les services # derriere restent debout et deviennent injoignables : on cherche la panne du mauvais cote. # -# Mise en defaut PAR PARAMETRE : `serveur_oauth2_proxy_sonde_attendu` (chaine introuvable). -exec /usr/lib/nagios/plugins/check_http \ - -H 127.0.0.1 -p 4180 -u '/ping' \ - -s '{{ serveur_oauth2_proxy_sonde_attendu }}' \ - -t 8 +# Mise en defaut PAR PARAMETRE : `serveur_oauth2_proxy_sonde_attendu` (premiere etape), +# `serveur_oauth2_proxy_sonde_retour` (URL de retour que le client n'autorise pas). +set -uo pipefail + +PORT={{ serveur_oauth2_proxy_sonde_port }} +CFG={{ serveur_oauth2_proxy_config }} +RETOUR='{{ serveur_oauth2_proxy_sonde_retour }}' + +# PREMIERE ETAPE — LE PROCESSUS REPOND. `check_http` parle deja l'API des greffons Nagios. +ping=$(/usr/lib/nagios/plugins/check_http -H 127.0.0.1 -p "${PORT}" -u '/ping' \ + -s '{{ serveur_oauth2_proxy_sonde_attendu }}' -t 8 2>&1) +rc=$? +(( rc == 0 )) || { echo "La passerelle ne repond pas : $(printf '%s' "${ping}" | tr '\n' ' ' | cut -c1-160)"; exit "${rc}"; } + +# SECONDE ETAPE — ELLE MENE A LA CONNEXION (2026-09-28). `/ping` repond tant que le +# processus vit, meme quand plus personne ne peut entrer : client supprime ou renomme +# dans Keycloak, URL de retour retiree du client, IdP injoignable DEPUIS CETTE MACHINE +# (resolution, route, pare-feu, autorite de certification). On fait donc le trajet d'un +# visiteur : `/oauth2/start` doit renvoyer vers l'IdP configure, pour CE client ; et +# l'IdP, joint d'ici avec le meme magasin de confiance que la passerelle, doit servir sa +# page de connexion. C'est le meme chemin que prend l'echange du code apres connexion. +# +# CE QU'ON NE TESTE PAS, ET POURQUOI : le SECRET du client. Le verifier demande un echange +# de jeton rate — Keycloak l'ecrit dans le journal de securite du realm. Toutes les +# 15 minutes, par passerelle, ce serait noyer le journal qu'on lit pour trouver les vraies +# tentatives. Le cas sain ici n'ecrit RIEN dans ce journal. +valeur() { grep -m1 -oP "^$1\s*=\s*\[?\"\K[^\"]+" "${CFG}" 2>/dev/null; } +EMETTEUR=$(valeur oidc_issuer_url); CLIENT=$(valeur client_id); CA=$(valeur provider_ca_files) +[[ -n "${EMETTEUR}" && -n "${CLIENT}" ]] \ + || { echo "La passerelle repond, mais sa configuration est illisible (${CFG})."; exit 2; } + +loc=$(curl -s -o /dev/null -D - --max-time 8 -H 'X-Forwarded-Proto: https' \ + "http://127.0.0.1:${PORT}/oauth2/start?rd=%2F" 2>/dev/null | tr -d '\r' \ + | awk 'tolower($1) == "location:" {print $2; exit}') +if [[ "${loc}" != "${EMETTEUR}/protocol/openid-connect/auth?"* || "${loc}" != *"client_id=${CLIENT}&"* ]]; then + echo "La passerelle repond, mais NE RENVOIE PAS vers ${EMETTEUR} pour le client « ${CLIENT} » (renvoie : ${loc:-rien})." + exit 2 +fi +[[ -n "${RETOUR}" ]] && loc=$(printf '%s' "${loc}" | sed -E "s#redirect_uri=[^&]*#redirect_uri=${RETOUR}#") + +page=$(mktemp); trap 'rm -f "${page}"' EXIT +res=$(curl -s -o "${page}" -w '%{http_code} %{exitcode} %{errormsg}' --max-time 10 \ + ${CA:+--cacert "${CA}"} "${loc}" 2>/dev/null) +code=${res%% *} +if [[ "${code}" == "000" ]]; then + echo "La passerelle renvoie vers ${EMETTEUR}, mais cette machine N'ATTEINT PAS l'IdP (curl ${res#* }) — la connexion echouera au retour." + exit 2 +fi +if [[ "${code}" != "200" ]]; then + motif=$(grep -oiE 'Invalid parameter: [a-z_]+|Client not found|Realm does not exist|Page not found' "${page}" | head -1) + echo "L'IdP REFUSE le trajet de connexion du client « ${CLIENT} » : HTTP ${code}${motif:+ — ${motif}}. Personne ne peut entrer." + exit 2 +fi +echo "Passerelle : repond, et mene a la page de connexion de ${EMETTEUR} pour le client « ${CLIENT} »." diff --git a/wiki/Rôle-serveur_oauth2_proxy.md b/wiki/Rôle-serveur_oauth2_proxy.md index e9b57bd..536eda1 100644 --- a/wiki/Rôle-serveur_oauth2_proxy.md +++ b/wiki/Rôle-serveur_oauth2_proxy.md @@ -22,7 +22,7 @@ graph LR | sonde | TTL | ce qu'elle voit | |---|---|---| -| `passerelle` | 5400 s | La passerelle d'authentification repond-elle ? Elle porte l'acces aux applications sans SSO natif — la supervision la premiere. Quand elle meurt, les services derriere restent debout et deviennent injoignables : on cherche la panne du mauvais cote. | +| `passerelle` | 5400 s | La passerelle d'authentification repond-elle, et mene-t-elle vraiment a la page de connexion de l'IdP pour son client ? Elle porte l'acces aux applications sans SSO natif — la vigie, la console. Quand elle meurt, les services derriere restent debout et deviennent injoignables : on cherche la panne du mauvais cote. | ## Ce qu'il expose en séries