Si vous avez déjà utilisé un serveur proxy Web, vous êtes probablement familier avec les variables d’environnement http_proxy
ou HTTP_PROXY
. Cependant, vous l'êtes peut-être moins avec la variable no_proxy
qui permet d’exclure le trafic destiné à certains hôtes d'utiliser le proxy. Bien que le protocole HTTP soit standardisé, aucune norme ne précise comment utiliser ces variables. Par conséquent, les clients Web prennent en charge ces variables de manière différente.
Découvrez dans cet article tout ce que vous devez savoir sur la variable d’environnement no_proxy, ainsi qu’un cas pratique d’un de nos clients GitLab.
Comprendre http_proxy, https_proxy et no_proxy
De nos jours, la plupart des clients Web permettent de se connecter à des serveurs proxy via les variables d'environnement suivantes :
http_proxy
/HTTP_PROXY
https_proxy
/HTTPS_PROXY
no_proxy
/NO_PROXY
Ces variables indiquent au client l'URL à utiliser pour accéder aux serveurs proxy et quelles exceptions appliquer.
Par exemple, si vous avez un serveur proxy attaché à http://alice.example.com:8080
, vous pourriez l’utiliser via :
export http_proxy=http://alice.example.com:8080
Mais quel serveur proxy sera utilisé si Maxime définit aussi la version en majuscules : HTTP_PROXY
?
export HTTP_PROXY=http://maxime.example.com:8080
Aussi surprenant que cela puisse paraître, cela dépend. Dans certains cas, le proxy d'Alice est utilisé, dans d'autres cas, c'est celui de Maxime. Nous y reviendrons plus loin dans cet article.
Que se passe-t-il si vous souhaitez définir des exceptions ? Par exemple, admettons que vous souhaitez utiliser un serveur proxy pour tout, sauf pour internal.example.com
et internal2.example.com
. C'est ici que la variable no_proxy
entre en jeu. Vous paramétrez alors no_proxy
comme suit :
export no_proxy=internal.example.com,internal2.example.com
Et si vous souhaitez exclure certaines adresses IP ? Faut-il utiliser des astérisques ou des caractères génériques ? Ou bien utiliser des blocs CIDR (comme 192.168.1.1/32
) ? Même réponse : cela dépend.
L'évolution des variables proxy et no_proxy
En 1994, la plupart des clients Web utilisaient la bibliothèque logicielle libwww
du CERN, qui prenait en charge les variables d'environnement http_proxy
et no_proxy
. libwww
utilisait uniquement la forme en minuscules de http_proxy
et la syntaxe de no_proxy
était simple :
no_proxy is a comma- or space-separated list of machine
or domain names, with optional :port part. If no :port
part is present, it applies to all ports on that domain.
Example:
no_proxy="cern.ch,some.domain:8001"
De nouveaux clients sont apparus, ajoutant leurs propres implémentations HTTP sans les relier à libwww
. En janvier 1996, Hrvoje Niksic publie geturl
, le prédécesseur de ce qu'on connaît aujourd'hui sous le nom de wget
. Un mois plus tard, geturl
prend en charge http_proxy
dans la version v1.1. En mai 1996, geturl
v1.3 ajoute la prise en charge de no_proxy
. Tout comme libwww
, geturl
n'accepte que les minuscules.
En janvier 1998, Daniel Stenberg publie curl
v5.1, qui prend en charge les variables http_proxy
et no_proxy
. De plus, curl
accepte désormais les majuscules, HTTP_PROXY
et NO_PROXY
.
Nota Bene : en mars 2009, curl
v7.19.4 abandonne la prise en compte de la forme en majuscules de HTTP_PROXY
en raison de problèmes de sécurité. Cependant, même si curl
n'accepte plus HTTP_PROXY
, HTTPS_PROXY
fonctionne toujours.
Gestion des variables du serveur proxy
Suite aux recherches de notre collègue Nourdin el Bacha, nous comprenons que la gestion des variables du serveur proxy varie en fonction du langage ou de l'outil utilisé.
http_proxy et https_proxy
Dans ce tableau, chaque ligne représente une variable, et chaque colonne correspond à l'outil (comme curl
) ou au langage (comme Ruby
) auquel elle s'applique :
curl | wget | Ruby | Python | Go | |
---|---|---|---|---|---|
http_proxy |
Oui | Oui | Oui | Oui | Oui |
HTTP_PROXY |
Non | Non | Oui (Mise en garde) | Oui (si REQUEST_METHOD n'est pas dans env) |
Oui |
https_proxy |
Oui | Oui | Oui | Oui | Oui |
HTTPS_PROXY |
Oui | Non | Oui | Oui | Oui |
Casse de caractères | Minuscules | Minuscules uniquement | Minuscules | Minuscules | Majuscules |
Sources | source | source | source | source | source |
Notez que http_proxy
et https_proxy
sont toujours pris en charge, alors que HTTP_PROXY
ne l'est pas. Python (via urllib
) ne facilite pas les choses : HTTP_PROXY
peut être utilisé tant que REQUEST_METHOD
n'est pas défini dans l'environnement.
Les variables d'environnement sont normalement définies en majuscules, mais puisque http_proxy
est apparu en premier, il est devenu de fait la norme. En cas de doute, optez pour la forme en minuscules, car elle est universellement prise en charge.
Contrairement à la plupart des implémentations, Go essaie d'abord la forme en majuscules avant revenir à la version en minuscules. Nous verrons plus tard pourquoi cela a causé du tort à notre client GitLab.
no_proxy
Certains utilisateurs ont signalé le manque d'indications sur no_proxy
. Puisque no_proxy
définit une liste d'exceptions, des questions sur son fonctionnement se posent.
Par exemple, vous configurez votre no_proxy
comme suit :
export no_proxy=example.com
Est-ce que cela signifie que le domaine doit être une correspondance exacte ou que subdomain.example.com
corresponde aussi à cette configuration ?
Le tableau suivant présente les différentes configurations. Toutes les implémentations correspondent aux suffixes, comme le montre la ligne « correspondance des suffixes
».
curl | wget | Ruby | Python | Go | |
---|---|---|---|---|---|
no_proxy |
Oui | Oui | Oui | Oui | Oui |
NO_PROXY |
Oui | Non | Oui | Oui | Oui |
Casse de caractères | Minuscules | Minuscules uniquement | Minuscules | Minuscules | Majuscules |
Correspondance des suffixes ? | Oui | Oui | Oui | Oui | Oui |
Points initiaux . ? |
Oui | Non | Oui | Oui | Non |
* fait correspondre tous les hôtes |
Oui | Non | Non | Oui | Oui |
Prise en charge des expressions régulières ? | Non | Non | Non | Non | Non |
Prise en charge des blocs CIDR ? | Non | Non | Oui | Non | Oui |
Détection des adresses IP de bouclage ? | Non | Non | Non | Non | Oui |
Sources | source | source | source | source | source |
Cependant, si no_proxy
est précédé d'un .
, cela implique des changements.
Par exemple, curl
et wget
se comportent différemment. curl
supprime le .
dans la configuration pour se coller au suffixe du domaine. Il contourne ainsi le proxy :
$ env https_proxy=http://non.existent/ no_proxy=.gitlab.com curl https://gitlab.com
<html><body>You are being <a href="https://about.gitlab.com/">redirected</a>.</body></html>
Néanmoins, wget
ne supprime pas le .
et utilise la correspondance exacte avec le nom d'hôte. Par conséquent, wget
essaie d'utiliser un proxy si un domaine de premier niveau est utilisé :
$ env https_proxy=http://non.existent/ no_proxy=.gitlab.com wget https://gitlab.com
Resolving non.existent (non.existent)... failed: Name or service not known.
wget: unable to resolve host address 'non.existent'
Toutes les implémentations ne prennent pas en charge les expressions régulières (regex). Utiliser des regex peut compliquer la configuration puisqu'elles peuvent prendre différentes variantes (comme PCRE ou POSIX). L'utilisation d'expressions régulières entraîne également de potentielles failles de sécurité et de performance.
Configurer no_proxy
avec un astérisque (*
) peut désactiver l'utilisation de proxy pour toutes les adresses, bien que ce principe ne soit pas appliqué tout le temps.
Au moment de décider de l'utilisation d'un proxy, aucune implémentation n'effectue de recherche DNS pour résoudre un nom d'hôte en adresse IP. Il est préférable d'éviter de spécifier les adresses IP dans no_proxy
à moins que le client ne les utilise explicitement.
Il en va de même pour les blocs CIDR, tels que 18.240.0.1/24
. Ces blocs ne fonctionnent que si la requête est faite directement à une adresse IP. Seuls les environnements de développement Go et Ruby permettent l'utilisation de blocs CIDR. Go désactive même automatiquement l'utilisation d'un proxy si une adresse IP de bouclage est détectée, ce qui n'est pas le cas dans d'autres implémentations.
Pourquoi les paramètres du proxy sont-ils importants ?
Si votre application est codée en plusieurs langages et doit fonctionner avec un pare-feu avec un serveur proxy, il est important de faire attention à ces différences.
Par exemple, GitLab est composé d'éléments codés en Ruby et d'autres en Go. Un de nos clients a configuré son proxy de la façon suivante :
HTTP_PROXY: http://proxy.company.com
HTTPS_PROXY: http://proxy.company.com
NO_PROXY: .correct-company.com
Il a ensuite signalé un problème avec GitLab :
- Un
git push
à partir de la ligne de commande a fonctionné. - Les modifications Git effectuées à partir de l'interface Web ont échoué.
Nos ingénieurs ont découvert qu'un problème de configuration de Kubernetes entraînait le maintien de valeurs obsolètes. L'environnement ressemblait à :
HTTP_PROXY: http://proxy.company.com
HTTPS_PROXY: http://proxy.company.com
NO_PROXY: .correct-company.com
no_proxy: .wrong-company.com
Les irrégularités entre no_proxy
et NO_PROXY
ont entraîné des alertes. Supprimer l'entrée incorrecte ou uniformiser les variables auraient pu résoudre le problème. Observons ce qu'il s'est passé.
Rappelez-vous que :
- Ruby priorise
no_proxy
. - Go priorise
NO_PROXY
.
Par conséquent, les services codées en Go, comme GitLab Workhorse, ont un bon paramétrage du proxy. Un git push
depuis la ligne de commande a fonctionné car les services Go gèrent cette activité :
Parse error on line 2: ...agram autonumber participant C a ----------------------^ Expecting 'SOLID_OPEN_ARROW', 'DOTTED_OPEN_ARROW', 'SOLID_ARROW', 'DOTTED_ARROW', 'SOLID_CROSS', 'DOTTED_CROSS', got 'NL'
L'appel gRPC de l'étape 2 n'a jamais tenté d'utiliser le proxy car no_proxy
a été configuré correctement pour se connecter directement à Gitaly.
Cependant, lorsqu'un utilisateur effectue une modification dans l'interface utilisateur, Gitaly transmet la requête au service gitaly-ruby
, qui est écrit en Ruby. gitaly-ruby
effectue des modifications dans le dépôt et renvoie un rapport via un appel gRPC à son processus parent. Malheureusement, comme le montre l'étape 4 ci-dessous, l'étape de reporting n'a pas eu lieu :
Parse error on line 2: ...agram autonumber participant C a ----------------------^ Expecting 'SOLID_OPEN_ARROW', 'DOTTED_OPEN_ARROW', 'SOLID_ARROW', 'DOTTED_ARROW', 'SOLID_CROSS', 'DOTTED_CROSS', got 'NL'
Puisque gRPC utilise HTTP/2 comme mode de transport, gitaly-ruby
tente de se connecter au proxy (il était connecté à une configuration erronée de no_proxy
). Le proxy a immédiatement rejeté la requête HTTP, ce qui a entraîné l'erreur du push sur l'interface Web.
Après la suppression des minuscules no_proxy
de l'environnement, les pushs depuis l'interface utilisateur ont fonctionné comme prévu. gitaly-ruby
s'est connecté au processus parent Gitaly. L'étape 4 a donc fonctionné comme suit :
Parse error on line 2: ...agram autonumber participant C a ----------------------^ Expecting 'SOLID_OPEN_ARROW', 'DOTTED_OPEN_ARROW', 'SOLID_ARROW', 'DOTTED_ARROW', 'SOLID_CROSS', 'DOTTED_CROSS', got 'NL'
Pourquoi faut-il privilégier un proxy HTTPS ?
Notez que le client a défini HTTPS_PROXY
sur un proxy HTTP non crypté : http://
est préféré à https://
. Bien que ce ne soit pas idéal en termes de sécurité, certaines équipes de développement utilisent cette méthode pour éviter les problèmes de certificats TLS (et donc, des problèmes de connexion pour les utilisateurs finaux).
Si un proxy HTTPS avait été spécifié, nous n’aurions pas rencontré ce problème. Lorsqu'un proxy HTTPS est utilisé, gRPC ignore ce paramètre car les proxy HTTPS ne sont pas pris en charge.
Le plus petit dénominateur commun
Personne ne devrait définir des valeurs irrégulières avec des paramètres de proxy en minuscules et en majuscules. Cependant, si vous devez gérer une stack dans plusieurs langages, vous pourrez configurer les paramètres de proxy HTTP selon le plus petit dénominateur commun.
http_proxy
et https_proxy
HTTP_PROXY
n'est pas toujours pris en charge ou recommandé. Préférez toujours le format en minuscules et si vous devez absolument utiliser la version en majuscules, vérifiez qu'elles partagent la même valeur.
no_proxy
- Adoptez le format en minuscules.
- Utilisez les valeurs
hostname:port
séparées par des virgules. - Les adresses IP sont acceptables, mais les noms d'hôtes ne sont pas résolus.
- Les suffixes correspondent toujours (
example.com
correspondra àtest.example.com
). - Évitez d'utiliser le point initial (
.
) pour les domaines de premier niveau. - Veillez à ne pas utiliser de correspondances de blocs CIDR. Seuls Go et Ruby les prennent en charge.
Standardiser no_proxy
Connaître le plus petit dénominateur commun aide à éviter des problèmes si ces définitions sont copiées pour différents clients Web. Mais est-ce que no_proxy
et les autres configurations de proxy requièrent une version standard documentée plutôt qu'une convention ad hoc ?
Voici quelques points qui peuvent vous aider :
- Préférez le format en minuscules aux variantes en majuscules (
http_proxy
devrait être utilisé avantHTTP_PROXY
). - Utilisez des valeurs
hostname:port
séparées par des virgules (chaque valeur peut inclure des espaces facultatifs.). - Ne faites pas de recherche DNS et évitez les expressions régulières (regex).
- Appliquez
*
pour faire correspondre tous les hôtes. - Supprimez les points initiaux (
.
) et faites correspondre les suffixes de domaine. - Prenez en charge la correspondance des blocs CIDR.
- Évitez les suppositions sur des adresses IP spéciales (comme les adresses de bouclage dans
no_proxy
).
Conclusion
Plus de 30 ans se sont écoulés depuis la sortie du premier serveur proxy Web. Depuis, les principes fondamentaux pour configurer un client Web grâce à des variables n'ont pas vraiment changé. Néanmoins, certaines subtilités ont vu le jour.
À travers un cas pratique, vous avez découvert qu'une définition erronée des variables no_proxy
et NO_PROXY
a entraîné des heures de travail pour résoudre le problème, en raison de différences d'acceptation de configuration entre Ruby et Go.
En mettant en évidence ces différences, nous espérons vous éviter de futurs problèmes dans votre stack de production. Et qui sait, nous verrons peut-être voir le jour une standardisation au niveau des chargés de maintenance des clients Web.