Blog Ingénierie Pouvons-nous standardiser la variable d'environnement NO_PROXY ?
Date de la publication : January 27, 2021
Lecture : 15 min

Pouvons-nous standardiser la variable d'environnement NO_PROXY ?

Découvrez notre guide complet sur no_proxy, la configuration des paramètres du proxy, ainsi que les spécificités de no_proxy, http_proxy et https_proxy.

question-mark-pile.jpg

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

  1. Adoptez le format en minuscules.
  2. Utilisez les valeurs hostname:port séparées par des virgules.
  3. Les adresses IP sont acceptables, mais les noms d'hôtes ne sont pas résolus.
  4. Les suffixes correspondent toujours (example.com correspondra à test.example.com).
  5. Évitez d'utiliser le point initial (.) pour les domaines de premier niveau.
  6. 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 :

  1. Préférez le format en minuscules aux variantes en majuscules (http_proxy devrait être utilisé avant HTTP_PROXY).
  2. Utilisez des valeurs hostname:port séparées par des virgules (chaque valeur peut inclure des espaces facultatifs.).
  3. Ne faites pas de recherche DNS et évitez les expressions régulières (regex).
  4. Appliquez * pour faire correspondre tous les hôtes.
  5. Supprimez les points initiaux (.) et faites correspondre les suffixes de domaine.
  6. Prenez en charge la correspondance des blocs CIDR.
  7. É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.

Votre avis nous intéresse

Cet article de blog vous a plu ou vous avez des questions ou des commentaires ? Partagez vos réflexions en créant un nouveau sujet dans le forum de la communauté GitLab. Partager votre expérience

Lancez-vous dès maintenant

Découvrez comment la plateforme DevSecOps unifiée de GitLab peut aider votre équipe.

Commencer un essai gratuit

Découvrez le forfait qui convient le mieux à votre équipe

En savoir plus sur la tarification

Découvrez ce que GitLab peut offrir à votre équipe

Échanger avec un expert