Gunicorn: detecção de esquema == 'https' é quebrado se vinculado ao soquete de domínio unix

Criado em 30 abr. 2018  ·  57Comentários  ·  Fonte: benoitc/gunicorn

commit b07532be752668be5eb5dbd0a8303abf5c219c99 "Proibir cabeçalhos de esquema seguro contraditórios" quebrou "request.scheme" e "wsgi.url_scheme" quando gunicorn está vinculado ao soquete de domínio unix. Quando o gunicorn é vinculado ao soquete tcp, tudo funciona bem.

Quando usado com nginx na frente do gunicorn 19.8, nginx está passando HTTP_X_FORWARDED_PROTO == https , mas em gunicorn / http / message.py na linha remote_addr = self.unreader.sock.getpeername() no caso de domínio unix socket remote_addr é uma string vazia e então secure_scheme_headers é um dict vazio e o esquema acaba sendo "http" embora o servidor web seja apenas https e tenha passado todos os cabeçalhos.

Estou usando o gevent worker com tudo mais recente no python 3.6, mas não acho que isso importe.

Comentários muito úteis

Oi

Como prometido, implantei rapidamente em um servidor de teste um aplicativo django muito simples para demonstrar que o problema ainda está presente.

Você pode exibi-lo a partir destes urls: http://gunicorn-test.exige.info e https://gunicorn-test.exige.info
As fontes estão disponíveis em https://github.com/alorence/gunicorn-test

Observe que na maioria dos navegadores, quando você visitou o link https: // pela primeira vez, não é possível voltar para http: //. Alcançar a página com curl funciona bem.

Como você pode ver, com o gunicorn 19.8.1, com um soquete unix usado para fornecer uma conexão entre o proxy reverso nginx e a instância do gunicorn, o wsgi.url_scheme não está configurado corretamente.

Posso fornecer mais informações se você me disser do que precisa;)

Todos 57 comentários

@ zt-initech, obrigado pelo relatório. Vou dar uma olhada imediatamente.

@ zt-initech # 1767

Não tenho certeza de quem mais pode estar por perto para revisá-lo, mas é um patch bastante simples e testei-o localmente. Se você puder verificar, irei mesclar e fazer uma versão.

@tilgovi, seu patch corrigiu o problema para mim. Mas eu teria feito isso em vez disso:

            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

e eu não sou um especialista em redes, então não sei se confiar em getpeername () para retornar uma string está ok.

19.8.1 é lançado

@ zt-initech Ah, sim. Pelo menos o que fiz é consistente com o que tivemos no passado, mas acho que você está certo, teria sido melhor.

Teria sido mais claro com AF_UNIX , mas podemos confiar no tipo de string:

O endereço de um soquete AF_UNIX ligado a um nó do sistema de arquivos é representado como uma string

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

Eu tenho o nginx na frente do gunicorn, e 19.8.1 ainda resulta em erro "Cabeçalhos de esquema contraditórios" mesmo se eu tiver definido o esquema proxy_set_header X-Forwarded-Proto $; em nginx conf.

@danielskun existe outro proxy reverso (como um balanceador de carga) na frente do nginx definindo um cabeçalho diferente?

Se você puder fazer um pequeno repo que reproduza o problema eu posso dar uma olhada.

Oi,

Como @danielskun , caí nesse caso ontem. Após uma implantação padrão do meu aplicativo django, percebi um loop de redirecionamento 301 infinito ao conectar à sua versão https.

Isso foi causado pela opção de segurança SECURE_SSL_REDIRECT e a causa foi a atualização do gunicorn de 19.7.1 para 19.8.1

Com 19.7.1, com SECURE_SSL_REDIRECT=True , o Django detectou corretamente que o site é visitado com esquema https. O ambiente wsgi.url_scheme foi configurado corretamente. Agora, com gunicorn 19.8.1, wsgi.url_scheme contém http .

Meu aplicativo wsgi é executado por trás do proxy reverso nginx (nenhum outro aplicativo frontal, nenhum balanceador de carga) e está conectado por meio de um soquete Unix (no disco).

Não sei como depurar isso, já que desenvolvo no windows, geralmente não consigo simular a conexão https na minha máquina dev. Mas acho que posso configurar um mini aplicativo wsgi / django em meu servidor para demonstrar o problema facilmente. Com um código-fonte aberto, você pode olhar por si mesmo. Claro, estou pronto para ajudá-lo a depurar isso, só não sei como proceder ainda;)

Provavelmente irei publicar este mini aplicativo amanhã, me avise se precisar de mais informações. Tenha um bom dia

Oi

Como prometido, implantei rapidamente em um servidor de teste um aplicativo django muito simples para demonstrar que o problema ainda está presente.

Você pode exibi-lo a partir destes urls: http://gunicorn-test.exige.info e https://gunicorn-test.exige.info
As fontes estão disponíveis em https://github.com/alorence/gunicorn-test

Observe que na maioria dos navegadores, quando você visitou o link https: // pela primeira vez, não é possível voltar para http: //. Alcançar a página com curl funciona bem.

Como você pode ver, com o gunicorn 19.8.1, com um soquete unix usado para fornecer uma conexão entre o proxy reverso nginx e a instância do gunicorn, o wsgi.url_scheme não está configurado corretamente.

Posso fornecer mais informações se você me disser do que precisa;)

Obrigado @alorence , minha configuração é igual à sua. @tilgovi Não tenho mais nada na frente do nginx.

Encontrei o mesmo problema ao tentar atualizar de 19.17.1 para 19.9.0 em condições semelhantes a @alorence

Eu tenho o mesmo problema. Até 19.7.1 funcionava com soquetes unix. Com 19.8.0, 19.8.1 e 19.9.0, o esquema é definido como http, embora X-Forwarded-Proto esteja definido como https.

Quando escrevi o nº 1767 e lancei 19.8.1, definitivamente testei isso localmente. Se alguém puder fazer algo simples para reproduzir o problema, isso me pouparia algum tempo investigativo! Talvez um repo com um Dockerfile com nginx e gunicorn configurados? Caso contrário, analisarei isso quando puder e ficarei feliz em revisar todas as solicitações pull. Sinto muito pela interrupção.

esse problema está vindo apenas com django? qual versão?

@benoitc Provavelmente pode ser reproduzido com qualquer versão do Django. Eu pessoalmente vi isso com Django 1.11.14 e Django 2.0.7.

@tilgovi Eu já coloquei online uma demonstração muito simples do problema. O código está disponível em https://github.com/alorence/gunicorn-test . Não é uma configuração baseada em Docker, mas contém:

O projeto foi implantado online, você pode acessá-lo em http://gunicorn-test.exige.info e https://gunicorn-test.exige.info. Observe que você pode não conseguir acessar a versão http, uma vez que já abriu a versão https em navegadores recentes.

A coisa mais importante a notar é que wsgi.url_scheme ambiente é definido como http mesmo quando o site é visitado a partir de https url.

O arquivo de configuração 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 inicialização

#!/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 o que está em seu 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

eu tenho
proxy_set_header https X-Forwarded-Proto;

Atenciosamente
Gunnar

Randall Leeds [email protected] schrieb am Do., 19. Juli 2018,
16:31:

@alorence https://github.com/alorence o que está em seu proxy_params.conf?

-
Você está recebendo isto porque comentou.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/benoitc/gunicorn/issues/1766#issuecomment-406296854 ,
ou silenciar o tópico
https://github.com/notifications/unsubscribe-auth/ACu0K8NKp6Lm5Y5KdN9X6SqPmp92JVjMks5uIJgvgaJpZM4Ts9rh
.

encontrando o mesmo erro, mas o commit acima parece ajudar para mim, ou seja, definindo proxy_set_header X-Forwarded-Proto $scheme;

@alorence obrigado pelo repo. Consegui executá-lo localmente em meu próprio nginx com os mesmos parâmetros de proxy, mas em localhost. Para fazê-lo funcionar, tive que gerar um certificado autoassinado e adicionar localhost a ALLOWED_HOSTS , mas obtive o resultado correto. wsgi.url_scheme é definido e request.is_secure() retorna True .

Eu tenho esse problema e não sei como corrigi-lo. Quantas pessoas também têm esse problema? Eu tenho a mesma configuração que @alorence enviada (exceto django == 1.11.15 e python 2.4 se for importante)

Para ser claro: eu não reproduzi isso: - /. Se alguém puder compartilhar as etapas para reproduzir, por favor, compartilhe.

Eu apenas coloquei as coisas em produção e me deparei com esse problema sozinho. Tive que fazer o downgrade para 19.7.1.

Estas são as versões que eu estava usando:
gunicorn 19.9.0
python 2.7.6
django 1.11.15
nginx 1.0.15

Minha configuração nginx tem o seguinte:

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

Não tenho https em meu ambiente de teste, mas verei se consigo configurá-lo e rastrear o problema.

Pelo menos consegui encontrar esse problema rapidamente quando fiz uma pesquisa por "cabeçalhos de esquema contraditórios" e fui capaz de fazer o downgrade rapidamente para corrigir o problema. Acho que esse problema provavelmente deveria ser reaberto, pois parece afetar várias pessoas.

Eu concordo que o bug pode ser reaberto, ou talvez um novo possa ser criado, uma vez que @ zt-initech confirmou que seu problema original foi corrigido por PR # 1767

@tisdall qual é o ambiente wsgi retornado? Além disso, como você inicia o gunicorn? Você pode compartilhar os argumentos de configuração e linha de comando que está usando?

@alorence não tenho certeza de seguir, mas se um bug ainda existir, abra um tíquete para ele :)

@alorence já compartilhou sua configuração nas mensagens acima e eu tenho as mesmas linhas em minha configuração.

Eu inicio o gunicorn por meio do supervisor, argumentos de linha de comando:

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

@predatell, portanto, seu problema é pelo menos diferente, pois você não está vinculado a um soquete UNIX.

Todos os outros podem fornecer seu comando de inicialização do gunicorn e qualquer outra configuração que você considere útil? Se houver um problema aqui, gostaria muito de corrigi-lo.

Quaisquer detalhes sobre balanceadores de carga e proxies também podem ajudar. Por exemplo, se você estiver implantando no Heroku, ou no AWS, ou em algum outro serviço. Ou mesmo se você tiver um orquestrador com roteamento como o k8s. Qualquer coisa que possa afetar os cabeçalhos que o gunicorn recebe. Além disso, não posso dizer se todos estão usando o nginx ou não.

Por favor, tantos detalhes quanto você puder fornecer. Se possível, um repositório de exemplo inteiro com instruções de implantação seria incrível.

Eu tenho o problema vivo em meu ambiente de teste agora, então posso tentar diferentes correções e ver como isso funciona.

Comando gunicorn (alterado para privacidade): gunicorn -c /data/web/gunicorn_config.py -b unix:/tmp/wsgi_application.sock wsgi:application

O gunicorn_config.py:

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

Ele está sendo executado por trás do nginx como um proxy com a seguinte configuração:

        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 - Não tenho certeza do que você quer dizer com "o ambiente wsgi retornou" ou como eu informo que ... Você pode esclarecer onde posso encontrar essa informação?

Eu ativei a depuração e notei o seguinte: secure_scheme_headers: {'X-FORWARDED-PROTOCOL': 'ssl', 'X-FORWARDED-PROTO': 'https', 'X-FORWARDED-SSL': 'on'}

No entanto, a configuração do nginx está definindo X-Forwarded-Protocol para https vez de ssl . Isso poderia ser a causa?

Ok, esse é definitivamente o problema ... Mudei meu gunicorn_config.py e adicionei o seguinte para corrigi-lo temporariamente:

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

Também comentado aqui: https://github.com/benoitc/gunicorn/issues/1857#issuecomment -414435088

A análise de @tisdall está correta e explica o problema de @predatell também.

Estou curioso para saber se algum de vocês está familiarizado com o software que define X-Forwarded-Protocol por padrão e quais valores ele espera. Presumo que nosso secure_scheme_headers padrão está configurado para esperar ssl porque algum software em estado selvagem faz isso.

Observe que não é necessário definir mais de um desses cabeçalhos, portanto, você pode remover X-Forwarded-Protocol e deixar X-Forwarded-Proto .

O erro "Cabeçalhos de esquema contraditório" destina-se a detectar casos em que seu proxy reverso define apenas alguns dos secure_scheme_headers mas um cliente malicioso tenta falsificar o status seguro definindo outro na solicitação. A menos que você espere outro proxy reverso, fora do seu controle, para especificar outro cabeçalho, não há necessidade de definir mais de um deles no nginx.

Não tenho proxy_set_header X-Forwarded-Protocol na configuração do nginx, portanto, no meu caso, não acho que essa seja a causa raiz do problema. Mas seguindo o conselho de @benoitc , criarei uma nova edição o mais rápido possível com um resumo do problema e investigações, para higienizar a discussão.

Obrigado a todos por sua ajuda

@tilgovi e @tisdall obrigado pela ajuda. Tive problemas com isso no meu nginx:

proxy_set_header X-Forwarded-Protocol $scheme;

@alorence - mudar secure_scheme_headers para {'X-FORWARDED-PROTO': 'https'} resolve o seu problema?

Infelizmente não. Veja https://gunicorn-test.exige.info/

O arquivo de configuração atual é

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

e a linha de comando atualizada para executar o gunicorn é: gunicorn --bind unix:/var/tmp/gunicorn_tests.sock -c ./gunicorn_config.py gunicorn_https.wsgi:application

@alorence - O que há em gunicorn_https/wsgi.py ?

@alorence - A https://gunicorn-test.exige.info/ com sucesso ...

Este aplicativo ainda está disponível no github . Aqui está o conteúdo 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()

Não entendo sua próxima observação. Também consigo abrir https://gunicorn-test.exige.info/ , mas o conteúdo é inválido:

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

Devemos ter request.is_secure = True e request.environ['wsgi.url_scheme']: https

Para registro, este é um pequeno aplicativo que escrevi rapidamente e coloquei online em um dos meus servidores de teste para demonstrar o problema. Mais informações em https://github.com/benoitc/gunicorn/issues/1766#issuecomment -406161275

Oh, desculpe ... Eu pensei em um ponto que o vi apresentando o erro "Cabeçalhos de esquema contraditórios" em vez de conteúdo. Também esqueci que você mencionou que o código estava no github.

Sim, eu verifiquei e o meu está configurando corretamente request.is_secure para True e request.environ['wsgi.url_scheme'] para 'https' .

E se você adicionar explicitamente proxy_set_header X-Forwarded-Proto $scheme; à configuração do nginx? Eu acho que o nginx pode não usar automaticamente o que está em proxy_params e você deve realmente incluir include proxy_params; naquele bloco location / .

uh .. você tem a instrução include aí .. Acho que preciso descansar. ^ _ ^

Enviei essas modificações para o arquivo de configuração nginx e reiniciei o aplicativo. O resultado permanece o mesmo. 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;
}

frustrante! Alguma pista em request.META ?

Tenho que atualizar as fontes do projeto para imprimir o conteúdo de request.META neste ambiente. Infelizmente, não poderei fazer isso antes de 2 dias. Desculpe por isso. Assim que possível, irei atualizar o projeto, reiniciá-lo no servidor de teste, e talvez abrir um novo problema para que possamos continuar a discussão em um tíquete aberto ... Muito obrigado por sua ajuda

OK. Marque-me o novo problema e eu darei uma olhada quando você o alterar ...

@alorence eu tenho o mesmo problema, como você resolve? Agora eu faço downgrade do gunicorn de 19.9.0 para 19.7.1 .

A correção foi enviada para o branch principal, mas não foi lançada por enquanto. A versão 19.9 ainda tem o problema. Você pode seguir # 1861 para obter informações atualizadas.
De minha parte, escolhi mudar de soquetes Unix para soquetes HTTP para a maioria dos meus sites, então não sou mais afetado pelo problema.

Eu também gastei algum tempo rastreando a raiz desse problema depois de ter aumentado gunicorn de 19.8.0 para 19.9.0 em um de meus envs. Como esta é uma regressão bastante séria, estou surpreso por ainda não ter havido um lançamento 19.9.1.

@villebro de que assunto você está falando? Se você está falando sobre # 1861 ou # 1882, provavelmente deveria comentar aí. (Acho que é a única coisa que não está disponível na versão mais recente)

@villebro minhas desculpas. Todos nós temos tempo e atenção limitados para dar a Gunicorn. Estamos trabalhando muito para lançar a versão 20.

@tisdall você está correto, eu estava me referindo a # 1861 (eu acho). Vou comentar no assunto correto. @tilgovi, por favor, não entenda mal, eu entendo perfeitamente que os desenvolvedores têm tempo limitado e não pretendem parecer ingratos pelo trabalho que está sendo feito. No meu caso, a regressão torna 19.9.0 inutilizável, e eu esperaria que esse fosse o caso para muitos usuários do gunicorn. Pessoalmente, eu preferiria ver uma versão 19.9.1 com todas as principais regressões conhecidas corrigidas (= backport do mestre) em vez de uma nova versão principal 20.0.0.

@villebro não se preocupe. Eu estava, mais do que tudo, apenas me certificando de que você ouvisse alguma resposta para que soubesse que estava sendo ouvido.

Eu deveria ter mencionado que estamos comprometidos em lançar mais uma versão do 19.x para que possamos obter algumas correções antes de descartar o suporte ao Python 2.

Dê uma olhada no # 2022 e deixe-nos saber se algo esquecido.

Esta página foi útil?
0 / 5 - 0 avaliações