Quelqu’un pointe un hôte HAProxy public vers une application interne et demande pourquoi le navigateur ne peut pas suivre. Le proxy joint l’app par son VPN, la redirection renvoie un en-tête Location, et le navigateur essaie alors une adresse qui n’existe qu’à l’intérieur du tunnel. De l’extérieur, la page reste simplement bloquée. La décision porte sur la partie qui ouvre la seconde connexion, et c’est ce qui sépare une redirection d’un backend.

Deux chemins qui se ressemblent dans la configuration

Une redirection termine l’échange par une réponse. Le client lit Location et lance une nouvelle requête depuis l’endroit où il se trouve, c’est-à-dire le réseau que vous n’avez jamais contrôlé. Le proxy n’intervient plus. Tout ce qui suit dépend du DNS, de la route et de la confiance du client, donc le résultat varie selon l’endroit où l’utilisateur a ouvert le navigateur :

GET /app        client -> proxy
302 Location    proxy  -> client   (http://10.40.0.20:8080/)
GET /app        client -> 10.40.0.20:8080   client network, no route

Un backend du proxy garde la seconde étape côté serveur. Le proxy l’ouvre par le chemin qu’il possède déjà, et le client continue de parler à un seul nom public. Si le tunnel tombe, le client reçoit une erreur au lieu d’une URL qu’il ne peut pas suivre. Cette différence est aussi ce qui rend la panne diagnosticable, car l’erreur apparaît là où sont les journaux :

GET /app        client -> proxy
GET /app        proxy  -> 10.40.0.20:8080   over the VPN
response        proxy  -> client

La documentation des redirections de HAProxy trace exactement cette ligne : répondre au client par une redirection est une action différente du transfert de la requête vers un backend. Les deux directives se trouvent à quelques lignes d’écart dans un fichier de configuration et ne se comportent pas du tout de la même façon. L’une termine la requête, l’autre en commence une seconde que le proxy possède.

Ce dont le navigateur a besoin et que le VPN du serveur ne lui donne pas

La requête du navigateur lui-même a besoin de quatre choses qu’il n’a peut-être pas : une réponse DNS pour le nom privé, une route vers cette adresse, l’autorisation d’atteindre le service qui écoute, et la confiance dans le certificat que ce service présente. L’hôte du proxy possède les quatre grâce à son tunnel. Aucune ne se transmet à un client qui n’a jamais été sur ce tunnel, car le tunnel est une propriété de l’hôte, pas de l’URL.

Cet écart explique le symptôme du presque-fonctionnement. La première requête réussit, la barre d’adresse change, puis la connexion se bloque ou échoue alors que le proxy est sain tout du long. Rien n’est cassé dans la configuration du proxy, et l’ajuster n’aide pas un client qui n’a aucun chemin vers 10.40.0.20. Soit le client obtient une route, soit le proxy doit établir la connexion à sa place.

Quand seul le proxy peut atteindre l’app

Supprimez l’action de redirection et envoyez ces requêtes à un backend qui pointe vers l’adresse privée. Le frontend garde son nom public tandis que le backend décide où va réellement la requête. Rien ne change dans le chemin du client, parce que le proxy cesse de décrire une destination et commence à en être une. Pour une application qui écoute volontairement sur le port interne 8080, le bloc serveur est court :

backend private_app
    mode http
    option httpchk GET /health
    server app1 10.40.0.20:8080 check

Lisez ce bloc comme une affirmation sur la table de routage du proxy : 10.40.0.20 doit être joignable depuis l’hôte du proxy lui-même, ce qui signifie ici par l’interface VPN plutôt que par la route par défaut. Le mot-clé check ne fait que rapporter ce que fait cette route, et le chemin de santé doit exister sur l’application, sinon chaque sonde marque le serveur comme hors service. Une vérification réussie ne prouve rien sur le client et ne remplace pas la politique qui décide qui peut utiliser le backend.

Le fragment n’est pas un frontend complet. Conservez l’écouteur, l’authentification, la sélection des hôtes et chemins autorisés, et la règle qui décide qui peut atteindre l’application. Alignez aussi le protocole du backend, car un backend HTTPS exige une vérification de certificat et une configuration d’identité de serveur, et sauter cette vérification transforme une connexion privée en connexion non vérifiée.

Confirmez le chemin de retour dans les deux sens avant de déclarer la victoire. L’application voit maintenant une connexion venant du proxy, donc ses journaux et toute métadonnée d’adresse client décrivent le proxy, sauf si vous en décidez autrement. Décidez comment l’identité et les adresses transférées sont gérées, et testez la décision au lieu de la supposer.

Les redirections de l’app pointent encore vers l’intérieur

Transférer la première requête n’est que la moitié de l’intégration. L’application peut émettre des redirections absolues, des URL de ressources codées en dur ou des cookies liés au nom qu’elle connaît en interne, et chacune renvoie le navigateur vers une adresse qu’il ne peut pas atteindre. Un en-tête Location pointant vers le nom interne ressemble à l’identique au problème d’origine, ce qui le fait diagnostiquer à tort comme une panne du proxy.

Réglez l’URL externe documentée de l’application et ses options de proxy, puis faites correspondre l’en-tête Host à l’hôte virtuel et aux attentes du certificat. Un cookie lié au nom interne ne reviendra pas par le nom public, et la boucle de connexion qui suit se lit comme un bug d’authentification. Changez d’abord l’URL externe, retestez depuis un profil de navigateur propre, et attendez-vous à ce qu’une configuration à moitié migrée produise une connexion qui réussit et une session qui meurt aussitôt.

Ne faites confiance aux métadonnées transférées que depuis le proxy désigné. Un en-tête que n’importe quel client peut définir ne doit jamais devenir une valeur d’autorisation, sinon le proxy devient un moyen pour un appelant de revendiquer l’identité d’un autre. Cette règle est indépendante de la question de la redirection et survit à toute refonte ultérieure.

Décidez quel design vous avez réellement

Si chaque utilisateur dispose déjà d’un accès VPN autorisé, une redirection vers une URL privée peut être un choix délibéré. Confirmez explicitement cette exigence au lieu de la supposer, car les deux designs servent des populations totalement différentes. L’un ne fonctionne pas du tout depuis un réseau d’hôtel, et l’autre expose l’application à quiconque peut charger l’URL publique. Cette distinction appartient à la note de conception.

Lancez la vérification depuis une machine du réseau des utilisateurs et suivez chaque saut du navigateur, pas seulement la première réponse. Surveillez la barre d’adresse et chaque requête suivante, car une seule URL absolue dans une balise de ressource suffit à casser un flux que le premier saut avait passé. La connexion doit fonctionner par le nom public, et les ressources qui doivent être refusées doivent le rester.

Publier un proxy inversé est une décision d’accès distincte, et non une extension transparente du VPN du proxy à quiconque peut charger son URL publique. Les deux se ressemblent sur un schéma, et ils diffèrent par qui est autorisé à entrer. Trancher cette question après le déploiement, c’est ainsi qu’une application interne devient publique par accident.

Notez laquelle des deux vous avez construite et pourquoi. La personne suivante qui touchera la configuration supposera l’autre. Une seule redirection ajoutée par commodité suffit à recréer la panne d’origine, et le symptôme ressemblera exactement à celui que vous venez de corriger.

Articles similaires