Gunicorn: le schéma de détection =='https' est cassé s'il est lié au socket de domaine unix

Créé le 30 avr. 2018  ·  57Commentaires  ·  Source: benoitc/gunicorn

commit b07532be752668be5eb5dbd0a8303abf5c219c99 "Interdire les en-têtes de schéma sécurisé contradictoires" a cassé "request.scheme" et "wsgi.url_scheme" lorsque gunicorn est lié au socket de domaine unix. Lorsque gunicorn est lié au socket TCP, tout fonctionne correctement.

Lorsqu'il est utilisé avec nginx devant gunicorn 19.8, nginx transmet HTTP_X_FORWARDED_PROTO == https , mais dans gunicorn/http/message.py à la ligne remote_addr = self.unreader.sock.getpeername() en cas de domaine unix socket remote_addr est une chaîne vide, puis secure_scheme_headers est un dict vide et le schéma finit par être "http" bien que le serveur Web soit uniquement https et ait passé tous les en-têtes.

J'utilise gevent worker avec le dernier tout sur python 3.6 mais je ne pense pas que cela ait d'importance.

Commentaire le plus utile

salut

Comme promis, j'ai rapidement déployé sur un serveur de test une application Django très simple pour démontrer que le problème est toujours présent.

Vous pouvez l'afficher depuis ces urls : http://gunicorn-test.exige.info et https://gunicorn-test.exige.info
Les sources sont disponibles sur https://github.com/alorence/gunicorn-test

Veuillez noter que sur la plupart des navigateurs, lorsque vous avez visité le lien https:// pour la première fois, il n'est pas possible de revenir à http://. Atteindre la page avec curl fonctionne bien.

Comme vous pouvez le voir, avec gunicorn 19.8.1, avec un socket unix utilisé pour fournir une connexion entre le proxy inverse nginx et l'instance de gunicorn, le wsgi.url_scheme n'est pas correctement défini.

Je suis d'accord pour donner plus d'informations si vous me dites ce dont vous avez besoin ;)

Tous les 57 commentaires

@zt-initech merci pour le rapport. Je vais regarder tout de suite.

@zt-initech #1767

Je ne sais pas qui d'autre pourrait être là pour l'examiner, mais c'est un patch assez simple et je l'ai testé localement. Si vous pouvez le vérifier, je fusionnerai et publierai une version.

@tilgovi votre patch a résolu le problème pour moi. Mais j'aurais fait ça à la place :

            if self.unreader.sock.family == socket.AF_UNIX:
                secure_scheme_headers = cfg.secure_scheme_headers
            else:
                remote_addr = self.unreader.sock.getpeername()
                if isinstance(remote_addr, tuple):
                    remote_host = remote_addr[0]
                    if remote_host in cfg.forwarded_allow_ips:
                        secure_scheme_headers = cfg.secure_scheme_headers

et je ne suis pas un expert en réseau, donc je ne sais pas si compter sur getpeername() pour renvoyer une chaîne est correct.

19.8.1 est publié

@zt-initech Ah, oui. Au moins, ce que j'ai fait est conforme à ce que nous avons eu dans le passé, mais je pense que vous avez raison, cela aurait été mieux.

Cela aurait été plus clair avec AF_UNIX , mais on peut se fier au type de chaîne :

L'adresse d'une socket AF_UNIX liée à un nœud de système de fichiers est représentée sous forme de chaîne

Source : https://docs.python.org/3/library/socket.html

J'ai nginx devant gunicorn, et 19.8.1 entraîne toujours l'erreur "En-têtes de schéma contradictoires" même si j'ai défini proxy_set_header X-Forwarded-Proto $scheme; dans la conf nginx.

@danielskun existe-t-il un autre proxy inverse (comme un équilibreur de charge) devant nginx définissant un en-tête différent ?

Si vous pouvez faire un petit repo qui reproduit le problème, je peux jeter un œil.

Salut,

Comme @danielskun, je suis tombé dans ce cas hier. Après un déploiement standard de mon application Django, j'ai remarqué une boucle de redirection 301 infinie lors de la connexion à sa version https.

Cela a été causé par l'option de sécurité SECURE_SSL_REDIRECT , et la cause était la mise à jour de gunicorn de 19.7.1 à 19.8.1

Avec 19.7.1, avec SECURE_SSL_REDIRECT=True , Django a correctement détecté que le site Web est visité avec le schéma https. L'environnement wsgi.url_scheme été correctement défini. Désormais, avec gunicorn 19.8.1, wsgi.url_scheme contient http .

Mon application wsgi s'exécute derrière le proxy inverse nginx (pas d'autre application frontale, pas d'équilibreur de charge) et est connectée via un socket unix (sur disque).

Je ne sais pas comment déboguer cela, car je développe sous Windows, je ne peux généralement pas simuler la connexion https sur ma machine de développement. Mais je pense que je peux installer une mini application wsgi/django sur mon serveur pour démontrer facilement le problème. Avec un code open source, vous pouvez regarder par vous-même. Bien sûr, je suis prêt à vous aider à déboguer cela, je ne sais pas encore comment procéder ;)

Je publierai probablement cette mini application demain, faites le moi savoir si vous avez besoin de plus d'informations. Bonne journée

salut

Comme promis, j'ai rapidement déployé sur un serveur de test une application Django très simple pour démontrer que le problème est toujours présent.

Vous pouvez l'afficher depuis ces urls : http://gunicorn-test.exige.info et https://gunicorn-test.exige.info
Les sources sont disponibles sur https://github.com/alorence/gunicorn-test

Veuillez noter que sur la plupart des navigateurs, lorsque vous avez visité le lien https:// pour la première fois, il n'est pas possible de revenir à http://. Atteindre la page avec curl fonctionne bien.

Comme vous pouvez le voir, avec gunicorn 19.8.1, avec un socket unix utilisé pour fournir une connexion entre le proxy inverse nginx et l'instance de gunicorn, le wsgi.url_scheme n'est pas correctement défini.

Je suis d'accord pour donner plus d'informations si vous me dites ce dont vous avez besoin ;)

Merci @alorence , ma configuration est la même que la tienne. @tilgovi Je n'ai rien d'autre devant nginx.

J'ai rencontré le même problème en essayant de mettre à jour de 19.17.1 à 19.9.0 dans des conditions similaires à celles de @alorence

J'ai le même problème. Jusqu'à la 19.7.1, cela fonctionnait avec des sockets Unix. Avec 19.8.0, 19.8.1 et 19.9.0, le schéma est défini sur http, bien que X-Forwarded-Proto soit défini sur https.

Lorsque j'ai écrit #1767 et publié 19.8.1, j'ai définitivement testé cela localement. Si quelqu'un peut faire quelque chose de simple pour reproduire le problème, cela me ferait gagner du temps dans l'enquête ! Peut-être un repo avec un Dockerfile avec nginx et gunicorn configurés ? Sinon, je regarderai cela quand je le pourrai et je serai heureux d'examiner toutes les demandes de tirage. Je suis désolé pour le dérangement.

ce problème vient-il uniquement avec django ? quelle version ?

@benoitc Cela peut probablement être reproduit avec n'importe quelle version de Django. J'ai personnellement vu cela avec Django 1.11.14 et Django 2.0.7.

@tilgovi J'ai déjà mis en ligne une démonstration très simple du problème. Le code est disponible sur https://github.com/alorence/gunicorn-test , ce n'est pas une configuration basée sur Docker, mais il contient :

Le projet a été déployé en ligne, vous pouvez y accéder depuis http://gunicorn-test.exige.info et https://gunicorn-test.exige.info. Notez que vous ne pourrez peut-être pas accéder à la version http une fois que vous aurez déjà ouvert la version https dans les navigateurs récents.

La chose la plus importante à noter est que l' environnement wsgi.url_scheme est défini sur http même lorsque le site Web est visité à partir de l' URL

Le fichier de configuration nginx :

server {
    listen 80;
    listen [::]:80;
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name gunicorn-test.exige.info;

    access_log /var/log/nginx/gunicorn-test.access.log;
    error_log  /var/log/nginx/gunicorn-test.error.log;

    location / {
        include proxy_params;
        proxy_pass http://unix:/var/tmp/gunicorn_tests.sock;
    }

    # SSL Configuration
    ssl_certificate /etc/letsencrypt/live/exige.info/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/exige.info/privkey.pem;

    # Common SSL config
    include common_lets_encrypt;
}

Script de démarrage

#!/usr/bin/env bash

source venv/bin/activate
python manage.py migrate
gunicorn --bind unix:/var/tmp/gunicorn_tests.sock gunicorn_https.wsgi:application

@alorence qu'y a-t-il dans votre proxy_params.conf ?

@tilgovi

~ ᐅ cat /etc/nginx/proxy_params
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
~ ᐅ sudo nginx -v
nginx version: nginx/1.10.3

j'ai
proxy_set_header X-Forwarded-Proto https ;

Meilleures salutations
Gunnar

Randall Leeds [email protected] schrieb am Do., 19. juillet 2018,
16h31 :

@alorence https://github.com/alorence qu'y a-t-il dans votre proxy_params.conf ?

-
Vous recevez ceci parce que vous avez commenté.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/benoitc/gunicorn/issues/1766#issuecomment-406296854 ,
ou couper le fil
https://github.com/notifications/unsubscribe-auth/ACu0K8NKp6Lm5Y5KdN9X6SqPmp92JVjMks5uIJgvgaJpZM4Ts9rh
.

rencontrer la même erreur, mais le commit ci-dessus semble m'aider, c'est-à-dire définir proxy_set_header X-Forwarded-Proto $scheme;

@alorence merci pour le repo. J'ai pu l'exécuter localement sous mon propre nginx avec les mêmes paramètres de proxy mais sur localhost. Pour que cela fonctionne, j'ai dû générer un certificat auto-signé et ajouter localhost à ALLOWED_HOSTS , mais j'obtiens le bon résultat. wsgi.url_scheme est défini et request.is_secure() renvoie True .

J'ai ce problème et je ne sais pas comment le résoudre. Combien de personnes ont également ce problème ? J'ai la même configuration que @alorence envoyée (sauf django==1.11.15 et python 2.4 si c'est important)

Pour être clair : je n'ai pas reproduit ceci :-/. Si quelqu'un peut partager les étapes à reproduire, veuillez le faire.

J'ai juste poussé des choses à la production et j'ai rencontré ce problème moi-même. J'ai dû rétrograder à 19.7.1 .

Voici les versions que j'utilisais :
gunicorne 19.9.0
python 2.7.6
Django 1.11.15
nginx 1.0.15

Ma configuration nginx contient les éléments suivants :

        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Protocol $scheme;

Je n'ai pas https dans mon environnement de test, mais je vais voir si je peux le configurer et localiser le problème.

Au moins, j'ai pu trouver rapidement ce problème lorsque j'ai fait une recherche sur "En-têtes de schéma contradictoires" et j'ai pu rapidement rétrograder pour résoudre le problème. Je pense que cette question devrait probablement être rouverte car elle semble toucher plusieurs personnes.

J'accepte que le bogue puisse être rouvert, ou peut-être qu'un nouveau pourrait être créé, puisque @zt-initech a confirmé que son problème d'origine avait été résolu par PR # 1767

@tisdall quel est l'environnement wsgi renvoyé ? Et comment lances-tu gunicorn ? Pouvez-vous partager les arguments de configuration et de ligne de commande que vous utilisez ?

@alorence pas sûr de suivre, mais si un bug existe toujours, veuillez ouvrir un ticket pour cela :)

@alorence a déjà partagé sa config dans les messages ci-dessus et j'ai les mêmes lignes dans ma config.

Je lance gunicorn via le superviseur, arguments de ligne de commande :

application gunicorn. wsgi:application --bind 127.0.0.1:8000 -w12 --max-requests 10000 --timeout 180

@predatell donc votre problème est au moins différent en ce que vous n'êtes pas lié à un socket UNIX.

Est-ce que tout le monde peut fournir sa commande de lancement de gunicorn et toute autre configuration qui, selon vous, pourrait être utile ? S'il y a un problème ici, j'aimerais beaucoup le résoudre.

Tous les détails sur les équilibreurs de charge et les proxys peuvent également être utiles. Par exemple, si vous effectuez un déploiement sur Heroku, ou sur AWS, ou sur un autre service. Ou même si vous avez un orchestrateur avec routage comme k8s. Tout ce qui pourrait affecter les en-têtes que gunicorn reçoit. De plus, je ne peux pas dire si tout le monde utilise nginx ou non.

S'il vous plaît, autant de détails que vous pouvez fournir. Si possible, un exemple complet de référentiel avec des instructions de déploiement serait incroyable.

Le problème est bien présent dans mon environnement de test maintenant, je peux donc essayer différentes corrections et voir comment cela fonctionne.

commande gunicorn (modifiée pour plus de confidentialité) : gunicorn -c /data/web/gunicorn_config.py -b unix:/tmp/wsgi_application.sock wsgi:application

Le gunicorn_config.py :

workers = 4
limit_request_line = 8190
max_requests = 8000  # number of requests before restart worker
max_requests_jitter = 500

Il fonctionne derrière nginx en tant que proxy avec la configuration suivante :

        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header X-NginX-Proxy true;
        proxy_pass http://unix:/tmp/wsgi_application.sock/;
        proxy_redirect off;
        proxy_headers_hash_bucket_size 96;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Protocol $scheme;

@benoitc - Je ne sais pas ce que vous entendez par "l'environnement wsgi est revenu" ou comment je signale cela... Pouvez-vous préciser où je pourrais trouver cette information ?

J'ai activé le débogage et j'ai remarqué ceci : secure_scheme_headers: {'X-FORWARDED-PROTOCOL': 'ssl', 'X-FORWARDED-PROTO': 'https', 'X-FORWARDED-SSL': 'on'}

Cependant, la configuration de nginx définit X-Forwarded-Protocol sur https au lieu de ssl . Est-ce que ça pourrait être la cause?

D'accord, c'est définitivement le problème... J'ai modifié mon gunicorn_config.py et ajouté ce qui suit pour le corriger temporairement :

secure_scheme_headers = {'X-FORWARDED-PROTOCOL': 'https', 'X-FORWARDED-PROTO': 'https', 'X-FORWARDED-SSL': 'on'}

Également commenté ici : https://github.com/benoitc/gunicorn/issues/1857#issuecomment -414435088

L'analyse de @tisdall est correcte et explique également le problème pour @predatell .

Je suis curieux de savoir si l'un de vous connaît un logiciel qui définit X-Forwarded-Protocol par défaut et quelles valeurs il attend. Je suppose que nos secure_scheme_headers par défaut sont configurés pour s'attendre à ssl parce que certains logiciels dans la nature le font.

Notez qu'il n'est pas nécessaire de définir plus d'un de ces en-têtes, vous pouvez donc à la fois supprimer X-Forwarded-Protocol et laisser X-Forwarded-Proto .

L'erreur « En-têtes de schéma contradictoires » est destinée à détecter les cas où votre proxy inverse ne définit qu'une partie des secure_scheme_headers mais un client malveillant tente d'usurper le statut sécurisé en en définissant un autre sur la demande. À moins que vous ne vous attendiez à ce qu'un autre proxy inverse, hors de votre contrôle, spécifie un autre en-tête, il n'est pas nécessaire d'en définir plusieurs dans nginx.

Je n'ai pas proxy_set_header X-Forwarded-Protocol dans ma configuration nginx, donc dans mon cas, je ne pense pas que ce soit la cause première du problème. Mais suivant les conseils de @benoitc , je

Merci à tous pour votre aide

@tilgovi et @tisdall merci pour votre aide. J'ai eu un problème avec ceci dans mon nginx:

proxy_set_header X-Forwarded-Protocol $scheme;

@alorence - Est-ce que changer secure_scheme_headers en {'X-FORWARDED-PROTO': 'https'} résout votre problème ?

Malheureusement non. Voir https://gunicorn-test.exige.info/

Le fichier de configuration actuel est

secure_scheme_headers = {
#    'X-FORWARDED-PROTOCOL': 'ssl',
    'X-FORWARDED-PROTO': 'https',
#    'X-FORWARDED-SSL': 'on'
}

et la ligne de commande mise à jour pour exécuter gunicorn est : gunicorn --bind unix:/var/tmp/gunicorn_tests.sock -c ./gunicorn_config.py gunicorn_https.wsgi:application

@alorence - Qu'y a-t-il dans gunicorn_https/wsgi.py ?

@alorence - BTW, je suis capable de charger https://gunicorn-test.exige.info/ avec succès...

Cette application est toujours disponible sur github . Voici le contenu de wsgi.py :

import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gunicorn_https.settings")

application = get_wsgi_application()

Je ne comprends pas votre prochaine remarque. Je peux aussi ouvrir https://gunicorn-test.exige.info/ , mais le contenu est invalide :

request.is_secure: False
request.environ['wsgi.url_scheme']: http

Nous devrions avoir request.is_secure = True et request.environ['wsgi.url_scheme']: https

Pour mémoire, il s'agit d'une petite application que j'ai rapidement écrite et mise en ligne sur 1 de mes serveurs de tests pour démontrer le problème. Plus d'informations sur https://github.com/benoitc/gunicorn/issues/1766#issuecomment -406161275

Oh, désolé.. J'ai pensé à un moment donné que je l'avais vu donner l'erreur "En-têtes de schéma contradictoires" au lieu de contenu. J'ai aussi oublié que vous mentionniez que le code était sur github.

Oui, j'ai vérifié et le mien définit correctement request.is_secure à True et request.environ['wsgi.url_scheme'] à 'https' .

Et si vous ajoutiez explicitement proxy_set_header X-Forwarded-Proto $scheme; à la configuration nginx ? Je pense que nginx peut ne pas utiliser automatiquement le contenu de proxy_params et vous devez en fait inclure un include proxy_params; dans ce bloc location / .

euh.. vous avez la déclaration include là.. Je pense que j'ai besoin de repos. ^_^

J'ai poussé ces modifications dans le fichier de configuration nginx et redémarré l'application. Le résultat reste le même. https://gunicorn-test.exige.info/

server {
    listen 80;
    listen [::]:80;
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name gunicorn-test.exige.info;

    access_log /var/log/nginx/gunicorn-test.access.log;
    error_log  /var/log/nginx/gunicorn-test.error.log;

    location / {
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_pass http://unix:/var/tmp/gunicorn_tests.sock;
    }

    # SSL Configuration
    ssl_certificate /etc/letsencrypt/live/exige.info/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/exige.info/privkey.pem;

    # Common SSL config
    include common_lets_encrypt;
}

frustrant! Des indices dans request.META ?

Je dois mettre à jour les sources du projet pour imprimer le contenu de request.META dans cet environnement. Malheureusement, je ne pourrai pas le faire avant 2 jours. Désolé pour ça. Dès que possible, je vais mettre à jour le projet, le redémarrer sur le serveur de test, et peut-être ouvrir un nouveau problème afin que nous puissions continuer la discussion dans un ticket ouvert... Merci beaucoup pour votre aide

D'accord. Taguez-moi sur le nouveau numéro et j'y jetterai un œil quand vous l'aurez modifié...

@alorence j'ai le même problème, comment le résolvez-vous ? Maintenant, je rétrograde le gunicorn de 19.9.0 à 19.7.1 .

Le correctif a été poussé vers la branche principale, mais n'a pas été publié pour l'instant. La version 19.9 a toujours le problème. Vous pouvez suivre #1861 pour des informations mises à jour.
De mon côté, j'ai choisi de passer des sockets Unix aux sockets HTTP pour la plupart de mes sites web, je ne suis donc plus concerné par le problème.

J'ai également passé pas mal de temps à rechercher la racine de ce problème après avoir fait passer gunicorn de 19.8.0 à 19.9.0 dans l'un de mes environnements. Comme il s'agit d'une régression assez sérieuse, je suis surpris qu'il n'y ait pas encore de version 19.9.1 ?

@villebro de quel problème parles-tu ? Si vous parlez de #1861 ou #1882, vous devriez probablement commenter là-bas. (Je pense que c'est la seule chose qui n'est pas disponible dans la dernière version)

@villebro mes excuses. Nous avons tous un temps et une attention limités à consacrer à Gunicorn. Nous travaillons dur pour sortir la version 20.

@tisdall vous avez raison, je faisais référence à #1861 (je pense). Je commenterai dans le bon numéro. @tilgovi s'il vous plaît ne vous

@villebro pas de soucis du tout. J'étais, plus que tout, juste en train de m'assurer que vous entendiez une réponse afin que vous sachiez que vous étiez entendu.

J'aurais dû mentionner que nous nous engageons à publier une version supplémentaire de 19.x afin que nous puissions obtenir des correctifs avant d'abandonner la prise en charge de Python 2.

Jetez un œil au #2022 et dites-nous si nous avons manqué quelque chose.

Cette page vous a été utile?
0 / 5 - 0 notes