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 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-09-29 00:30:43 -04:00
parent 5404c947d8
commit 2bbe12299f
6 changed files with 114 additions and 17 deletions

View file

@ -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).

View file

@ -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: ""

View file

@ -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.'

View file

@ -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.

View file

@ -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} »."

View file

@ -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