Gunicorn: 如果绑定到 unix 域套接字,则检测方案=='https' 被破坏

创建于 2018-04-30  ·  57评论  ·  资料来源: benoitc/gunicorn

commit b07532be752668be5eb5dbd0a8303abf5c219c99 当 gunicorn 绑定到 unix 域套接字时,“禁止矛盾的安全方案标头”破坏了“request.scheme”和“wsgi.url_scheme”。 当 gunicorn 绑定到 tcp 套接字时,一切正常。

当在 gunicorn 19.8 之前与 nginx 一起使用时,nginx 传递HTTP_X_FORWARDED_PROTO == https ,但是在 gunicorn/http/message.py 中的remote_addr = self.unreader.sock.getpeername()中的 unix 域套接字remote_addr是空字符串,然后secure_scheme_headers是空字典,并且方案最终是“http”,尽管 Web 服务器仅支持 https 并传递了所有标头。

我在 python 3.6 上使用 gevent worker 和最新的一切,但我认为这并不重要。

最有用的评论

你好

正如承诺的那样,我迅速在测试服务器上部署了一个非常简单的 django 应用程序,以证明问题仍然存在。

您可以从以下网址显示它: http : https://gunicorn-test.exige.info
来源可在https://github.com/alorence/gunicorn-test

请注意,在大多数浏览器上,当您第一次访问 https:// 链接时,无法返回http://。 使用 curl 访问页面确实效果很好。

如您所见,在gunicorn 19.8.1 中,使用unix 套接字提供nginx 反向代理和gunicorn 实例之间的连接, wsgi.url_scheme未正确设置。

如果您告诉我您需要什么,我可以提供更多信息;)

所有57条评论

@zt-initech 感谢您的报告。 我马上去看看。

@zt-initech #1767

我不确定还有谁会在附近审查它,但这是一个非常简单的补丁,我在本地对其进行了测试。 如果你能验证它,我会合并并发布。

@tilgovi你的补丁为我解决了这个问题。 但我会这样做:

            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

而且我不是网络专家,所以我不知道依靠 getpeername() 返回字符串是否可以。

19.8.1 发布

@zt-initech 啊,是的。 至少我所做的与我们过去所做的一致,但我认为你是对的,那会更好。

使用AF_UNIX会更清楚,但我们可以依赖字符串类型:

绑定到文件系统节点的 AF_UNIX 套接字的地址表示为字符串

来源: https :

我在 gunicorn 前面有 nginx,即使我设置了 proxy_set_header X-Forwarded-Proto $scheme,19.8.1 仍然会导致“Contradictory scheme headers”错误; 在 nginx 配置中。

@danielskun在 nginx 前面是否有另一个反向代理(如负载均衡器)设置不同的标头?

如果您可以制作一个重现问题的小型存储库,我可以看看。

你好,

就像@danielskun一样,我昨天就陷入了这种情况。 在对我的 django 应用程序进行标准部署后,我注意到在连接到其 https 版本时出现了一个无限的 301 重定向循环。

这是SECURE_SSL_REDIRECT安全选项导致的,原因是gunicorn从19.7.1更新到19.8.1

在 19.7.1 中,使用SECURE_SSL_REDIRECT=True ,Django 正确检测到使用 https 方案访问该网站。 环境wsgi.url_scheme设置正确。 现在,在 gunicorn 19.8.1 中, wsgi.url_scheme包含http

我的 wsgi 应用程序在 nginx 反向代理后面运行(没有其他前端应用程序,没有负载平衡器)并通过 unix 套接字(在磁盘上)连接。

我不知道如何调试这个,因为我在 Windows 上开发,我通常无法在我的开发机器上模拟 https 连接。 但我想我可以在我的服务器上设置一个迷你 wsgi/django 应用程序来轻松演示这个问题。 使用开源代码,您可以自己查看。 当然,我已经准备好帮助你调试这个,我只是不知道如何继续;)

我明天可能会发布这个迷你应用程序,如果您需要更多信息,请告诉我。 祝你今天过得愉快

你好

正如承诺的那样,我迅速在测试服务器上部署了一个非常简单的 django 应用程序,以证明问题仍然存在。

您可以从以下网址显示它: http : https://gunicorn-test.exige.info
来源可在https://github.com/alorence/gunicorn-test

请注意,在大多数浏览器上,当您第一次访问 https:// 链接时,无法返回http://。 使用 curl 访问页面确实效果很好。

如您所见,在gunicorn 19.8.1 中,使用unix 套接字提供nginx 反向代理和gunicorn 实例之间的连接, wsgi.url_scheme未正确设置。

如果您告诉我您需要什么,我可以提供更多信息;)

谢谢@alorence ,我的配置和你的一样。 @tilgovi我在 nginx 面前什么都没有。

我遇到了同样的问题,试图在与@alorence类似的条件下从19.17.119.9.0

我有同样的问题。 在 19.7.1 之前,它使用 unix 套接字。 在 19.8.0、19.8.1 和 19.9.0 中,方案设置为 http,尽管 X-Forwarded-Proto 设置为 https。

当我编写 #1767 并发布 19.8.1 时,我肯定在本地进行了测试。 如果有人可以做一些简单的事情来重现该问题,那将为我节省一些调查时间! 也许是一个带有 Dockerfile 和 nginx 和 gunicorn 设置的存储库? 否则,我会在可能的情况下查看此内容,并且很高兴查看任何拉取请求。 我很抱歉打扰了。

这个问题只出现在 django 中吗? 哪个版本?

@benoitc这可能可以用任何版本的 Django 复制。 我个人在 Django 1.11.14 和 Django 2.0.7 中看到了这一点。

@tilgovi我已经在网上发布了一个非常简单的问题演示。 代码可从https://github.com/alorence/gunicorn-test 获得,它不是基于 Docker 的设置,但它包含:

项目已经上线,可以从http://gunicorn-test.exige.infohttps://gunicorn-test.exige.info获取。 请注意,一旦您在最近的浏览器中打开了 https 版本,您可能无法访问 http 版本。

需要注意的最重要的事情是wsgi.url_scheme环境设置为http即使从 https url

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;
}

启动脚本

#!/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你的 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

我有
proxy_set_header X-Forwarded-Proto https;

此致
贡纳尔

Randall Leeds通知@github.com schrieb am Do.,2018 年 7 月 19 日,
16:31:

@alorence https://github.com/alorence你的 proxy_params.conf 里有什么?


您收到此消息是因为您发表了评论。
直接回复本邮件,在GitHub上查看
https://github.com/benoitc/gunicorn/issues/1766#issuecomment-406296854
或静音线程
https://github.com/notifications/unsubscribe-auth/ACu0K8NKp6Lm5Y5KdN9X6SqPmp92JVjMks5uIJgvgaJpZM4Ts9rh
.

遇到同样的错误,但上面的提交似乎对我有帮助,即设置proxy_set_header X-Forwarded-Proto $scheme;

@alorence感谢您的回购。 我能够在我自己的 nginx 下使用相同的代理参数在本地运行它,但在本地主机上运行。 为了让它工作,我必须生成一个自签名证书并将 localhost 添加到ALLOWED_HOSTS ,但我得到了正确的结果。 wsgi.url_scheme已设置, request.is_secure()返回True

我有这个问题,我不知道如何解决它。 有多少人也有这个问题? 我的配置与@alorence发送的配置相同(django==1.11.15 和 python 2.4 除外,如果重要的话)

需要明确的是:我没有复制这个:-/。 如果有人可以分享重现的步骤,请这样做。

我只是将东西推向生产并自己遇到了这个问题。 我不得不降级回 19.7.1 。

这是我使用的版本:
枪炮 19.9.0
蟒蛇 2.7.6
Django 1.11.15
nginx 1.0.15

我的 nginx 配置如下:

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

我的测试环境中没有 https,但我会看看我是否可以设置它并追踪问题。

至少我在搜索“Contradictory Scheme headers”时能够快速找到这个问题,并且能够快速降级以解决问题。 我认为这个问题可能应该重新打开,因为它似乎影响了几个人。

我同意该错误可能会重新打开,或者可能会创建一个新错误,因为 @zt-initech 确认他的原始问题已由 PR #1767 修复

@tisdall返回的 wsgi 环境是什么? 另外你如何发射 gunicorn ? 你能分享你正在使用的配置和命令行参数吗?

@alorence不确定是否会遵循,但如果错误仍然存​​在,请为它打开一张票:)

@alorence已经在上面的消息中分享了他的配置,我的配置中有相同的行。

我通过主管,命令行参数启动 gunicorn:

枪炮应用程序。 wsgi:application --bind 127.0.0.1:8000 -w12 --max-requests 10000 --timeout 180

@predatell所以您的问题至少有所不同,因为您没有绑定到 UNIX 套接字。

其他人能否提供他们的 gunicorn 启动命令以及您认为可能有帮助的任何其他配置? 如果这里有问题,我非常想修复它。

有关负载平衡器和代理的任何详细信息也可能有所帮助。 例如,如果您在 Heroku、AWS 或其他一些服务上进行部署。 或者即使你有一个像 k8s 这样的路由编排器。 任何可能影响 gunicorn 接收的标头的东西。 另外,我不知道是否每个人都在使用 nginx。

请提供尽可能多的详细信息。 如果可能,带有部署说明的完整示例存储库会很棒。

我现在在我的测试环境中很好地解决了这个问题,所以我可以尝试不同的修复程序,看看它是如何工作的。

gunicorn 命令(为了隐私而更改): gunicorn -c /data/web/gunicorn_config.py -b unix:/tmp/wsgi_application.sock wsgi:application

gunicorn_config.py:

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

它作为具有以下配置的代理在 nginx 后面运行:

        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 - 我不知道你所说的“返回的 wsgi 环境”是什么意思,或者我如何报告......你能澄清我在哪里可以找到这些信息吗?

我打开调试并注意到这一点: secure_scheme_headers: {'X-FORWARDED-PROTOCOL': 'ssl', 'X-FORWARDED-PROTO': 'https', 'X-FORWARDED-SSL': 'on'}

但是,nginx 配置将X-Forwarded-Protocolhttps而不是ssl 。 这可能是原因吗?

好的,这绝对是问题......我更改了我的 gunicorn_config.py 并添加了以下内容来临时修复它:

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

也在这里评论: https :

@tisdall的分析是正确的,并且也解释了@predatell的问题。

我很好奇你们中的任何一个是否熟悉默认设置X-Forwarded-Protocol的软件以及它期望的值。 我假设我们的默认secure_scheme_headers设置为期望ssl因为某些软件会这样做。

请注意,没有必要设置多个头文件,因此您可以删除X-Forwarded-Protocol并保留X-Forwarded-Proto

“Contradictory scheme headers”错误旨在检测您的反向代理仅设置部分secure_scheme_headers但恶意客户端试图通过在请求上设置另一个来欺骗安全状态的情况。 除非您期望另一个不受您控制的反向代理来指定另一个标头,否则无需在 nginx 中设置多个标头。

我的 nginx 配置中没有proxy_set_header X-Forwarded-Protocol ,所以就我而言,我认为这不是问题的根本原因。 但是按照@benoitc 的建议,我将尽快创建一个新问题,并附上问题和调查的摘要,以清理讨论。

感谢你的帮助

@tilgovi@tisdall感谢您的帮助。 我在我的 nginx 中遇到了这个问题:

proxy_set_header X-Forwarded-Protocol $scheme;

@alorence - 将secure_scheme_headers更改{'X-FORWARDED-PROTO': 'https'}解决您的问题?

抱歉不行。 见https://gunicorn-test.exige.info/

当前的配置文件是

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

运行 gunicorn 的更新命令行是: gunicorn --bind unix:/var/tmp/gunicorn_tests.sock -c ./gunicorn_config.py gunicorn_https.wsgi:application

@alorence - gunicorn_https/wsgi.py什么?

@alorence - 顺便说一句,我可以成功加载https: //gunicorn-test.exige.info /...

这个应用程序在 github 上仍然可用。 这是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()

我不明白你的下一句话。 我也可以打开https://gunicorn-test.exige.info/ ,但是内容无效:

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

我们应该有request.is_secure = Truerequest.environ['wsgi.url_scheme']: https

作为记录,这是一个小应用程序,我快速编写并在我的测试服务器 1 上联机以演示该问题。 https://github.com/benoitc/gunicorn/issues/1766#issuecomment -406161275中的更多信息

哦,对不起.. 我想一度我看到它给出了“矛盾的方案标题”错误而不是内容。 我也忘了你提到代码在 github 上。

是的,我检查了一下,我的正确设置了request.is_secureTruerequest.environ['wsgi.url_scheme']'https'

如果您显式地将proxy_set_header X-Forwarded-Proto $scheme;到 nginx 配置中会怎样? 我认为 nginx 可能不会自动使用proxy_params ,您必须在location /块中实际包含include proxy_params;

呃.. 你有include声明.. 我想我需要休息一下。 ^_^

我将这些修改推送到 nginx 配置文件,并重新启动了应用程序。 结果保持不变。 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;
}

令人沮丧! request.META任何线索?

我必须更新项目的源代码才能在此环境中打印request.META内容。 不幸的是,我将无法在 2 天前执行此操作。 对于那个很抱歉。 尽快,我会更新项目,在测试服务器上重新启动它,并且可能会打开一个新问题,以便我们可以在开放票中继续讨论......非常感谢您的帮助

好的。 在新问题上标记我,当你改变它时我会看看......

@alorence我也有同样的问题,请问怎么解决的? 现在我将 gunicorn 从19.9.0降级为19.7.1

该修复程序已推送到主分支,但目前尚未发布。 版本 19.9 仍然存在问题。 您可以关注#1861 获取更新信息。
就我而言,我选择将大多数网站从 Unix 套接字切换到 HTTP 套接字,因此我不再受此问题的影响。

在我的一个环境中将gunicorn从 19.8.0 提高到 19.9.0 之后,我还花了很多时间来追踪这个问题的根源。 由于这是一个相当严重的回归,我很惊讶还没有 19.9.1 版本?

@villebro你说的是哪个问题? 如果您在谈论 #1861 或 #1882,您可能应该在那里发表评论。 (我认为这是最新版本中唯一没有的东西)

@villebro我很抱歉。 我们给 Gunicorn 的时间和注意力都是有限的。 我们正在努力推出第 20 版。

@tisdall你是对的,我指的是 #1861(我认为)。 我会在正确的问题中发表评论。 @tilgovi请不要误解,我完全理解开发人员的时间有限,并不意味着对正在完成的工作感到忘恩负义。 在我的情况下,回归使 19.9.0 无法使用,我希望许多 gunicorn 用户都是这种情况。 就我个人而言,我更愿意看到 19.9.1 版本与新的主要版本 20.0.0 相比,所有已知的主要回归都已修复(=从主版本反向移植)。

@villebro完全不用担心。 最重要的是,我只是确保你听到了一些回应,这样你就知道自己被听到了。

我应该提到我们承诺再发布一个 19.x 版本,以便在我们放弃 Python 2 支持之前获得一些修复。

看看#2022,如果我们错过了什么,请告诉我们。

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

diwu1989 picture diwu1989  ·  36评论

tijs picture tijs  ·  36评论

obi1kenobi picture obi1kenobi  ·  78评论

jezdez picture jezdez  ·  90评论

benoitc picture benoitc  ·  43评论