La connexion VPN se bloque après un Access-Challenge RADIUS
Access-Challenge n’est ni un succès ni un rejet définitif. Voyez comment le NAS, la passerelle et le client doivent transporter la réponse suivante dans le même échange.
Votre mot de passe est correct, la connexion VPN échoue quand même, et le serveur RADIUS a peut-être fait exactement ce qu’on lui avait demandé. La passerelle a probablement reçu un Access-Challenge, n’a rien affiché d’utile, puis a expiré ou a traité cette réponse comme un rejet. Côté client, cela ressemble à des identifiants erronés ou à un tunnel cassé. La cause se trouve généralement au milieu d’une conversation que personne n’a capturée.
Une connexion, deux échanges RADIUS
Quand le backend veut un second facteur, il ne répond pas à la première requête par une acceptation ou un rejet. Il répond avec le code 11 et attend la réponse suivante de l’utilisateur, que la passerelle doit renvoyer dans une requête de suivi. Cette requête de suivi appartient à la même conversation d’authentification, pas à une nouvelle tentative de connexion.
Dans une configuration qui fonctionne, l’utilisateur voit une seconde invite : d’abord le mot de passe, puis un code à usage unique, ou une invite combinée unique si l’opérateur en a documenté une. Dans une configuration cassée, la seconde invite n’apparaît jamais et la connexion échoue simplement.
C’est le symptôme à traquer, et il est facile à confirmer depuis le seul côté client : si le client VPN demande exactement un secret puis rejette un mot de passe que vous savez valide, l’échange s’est probablement arrêté au défi.
Ce que le défi transporte réellement
La spécification RADIUS décrit le code 11 à la section 4.4 et autorise deux attributs qui comptent ici. Reply-Message contient un texte destiné à l’utilisateur, et State contient une valeur opaque que le serveur veut voir renvoyée. Le NAS n’est pas censé interpréter State, seulement le retourner inchangé.
La requête de suivi utilise un nouvel Identifier et un nouveau Request Authenticator, et elle doit porter la même valeur State plus la réponse de l’utilisateur. Le serveur la relie au défi en attente grâce à ce State, ce qui explique qu’une passerelle qui régénère ou abandonne l’attribut ne puisse pas terminer la cérémonie. Le response authenticator de chaque réponse doit aussi être validé, sinon le NAS devrait rejeter le paquet.
La section 4.4 ajoute le détail qui explique la plupart des échecs silencieux : un NAS qui ne peut pas prendre en charge les défis traite Access-Challenge comme un rejet. Si le service VPN de votre passerelle est antérieur à cette prise en charge ou l’omet, la demande du backend pour un second facteur devient un « échec d’authentification » immédiat, sans aucune invite.
Où la requête en chaîne se perd
Le premier suspect est le chemin de l’invite, pas l’identifiant. Un portail qui affiche un champ OTP pour les connexions web ne le fait pas automatiquement pour le service VPN, et certaines passerelles ne transmettent un défi qu’aux clients qui annoncent cette capacité. Testez le même compte aux deux points d’entrée ; si le portail fonctionne et pas le VPN, vous regardez le transfert, pas le jeton.
Le format d’entrée est le second suspect. Concaténer mot de passe et code sans le séparateur documenté du fournisseur, ou laisser un identifiant enregistré rejouer un ancien code, échoue d’une manière identique à un mauvais mot de passe. Utilisez l’invite séparée ou le format combiné documenté par l’opérateur, et n’improvisez jamais de délimiteur.
Des défauts propres à une version existent, mais une liste de versions corrigées d’il y a des années n’est pas un diagnostic pour le déploiement d’aujourd’hui. Lisez les notes de version du logiciel que vous exécutez réellement, et conservez l’étape d’échange observée comme preuve.
Retracez une seule tentative de bout en bout
Reproduisez-la une fois, avec le débogage côté serveur activé, et gardez toute autre session hors de la fenêtre de capture :
$ sudo radsniff -i eth0 -x -f 'udp port 1812'
$ sudo freeradius -X
$ radtest alice 'password' 127.0.0.1 0 testing123
Dans la sortie, cherchez la forme de la conversation plutôt qu’une seule ligne. Une première Access-Request avec Identifier 3, un Access-Challenge portant State, puis une seconde Access-Request avec Identifier 4 qui répète ce State et inclut la réponse de l’utilisateur. Si la seconde requête manque, la passerelle n’a jamais fait de suivi. Si elle arrive sans State, le serveur ne peut pas la relier au défi en attente et la rejettera généralement.
Masquez les identifiants, les jetons et les données d’identité inutiles lorsque vous partagez la trace, et corrélez les événements d’une seule tentative au lieu de mélanger les reprises de plusieurs onglets de navigateur. Les horodatages sont vos amis ici ; un défi qui arrive après que le client a abandonné ressemble à un rejet pour l’utilisateur et à une réponse lente dans le journal.
L’état final souhaité est une conversation terminée et autorisée, suivie de l’accès prévu au service. Une réponse qui arrive du serveur RADIUS prouve bien moins que cela.