【问题标题】:Nginx/Pyramid custom SSL portNginx/Pyramid 自定义 SSL 端口
【发布时间】:2018-04-08 22:47:36
【问题描述】:

作为前缀,我一直在使用以下堆栈并取得了巨大成功:

NGINX - 网络代理 SSL - 在 nginx 中配置 Pyramid Web 应用程序,由 gunicorn 提供服务

上面的组合效果很好,这是一个有效的配置。

    server {
        # listen on port 80
        listen       80;
        server_name portalapi.example.com;
        # Forward all traffic to SSL
        return         301 https://www.portalapi.example.com$request_uri;
    }

    server {
        # listen on port 80
        listen       80;
        server_name www.portalapi.example.com;
        # Forward all traffic to SSL
        return         301 https://www.portalapi.example.com$request_uri;
    }

    #ssl server 
    server {
    listen         443 ssl;
        ssl    on;
        ssl_certificate    /usr/local/etc/letsencrypt/live/portalapi.example.com/fullchain.pem;
        ssl_certificate_key    /usr/local/etc/letsencrypt/live/portalapi.example.com/privkey.pem;
        server_name    www.portalapi.example.com;

    client_max_body_size 10M;
    client_body_buffer_size 128k;

    location ~ /.well-known/acme-challenge/ {
            root /usr/local/www/nginx/portalapi;
            allow all;
    }

    location / {
            proxy_set_header Host $host;
            proxy_pass  http://10.1.1.16:8005;
            #proxy_intercept_errors on;
            allow   all;

    }

    error_page   404 500 502 503 504  /index.html;
    location = / {
        root   /home/luke/ecom2/dist;
    }

}

现在,这就是我为面向公众的应用程序提供服务的方式,它运行良好。对于我所有的内部应用程序,我曾经简单地将用户引导到一个内部域示例:http://subdomain.company.domain,这在很长一段时间内都运行良好。

现在在 KRACK 攻击之后,虽然我们有一些非常彻底的防火墙规则来防止很多攻击,但我想强制所有内部流量通过 SSL,我不想使用自签名证书,我想要使用让我们加密,这样我就可以自动更新证书,这使得管理更容易(并且更便宜)。

为了使用 Let 加密,我需要有一个面向公众的 DNS 和服务器来执行 ACME 质询(用于自动更新)。现在再次在 nginx 中设置这是一件非常容易的事情,并且以下配置非常适合提供静态内容:

如果来自 Internet 的用户访问 intranet.example.com,它会显示一条禁止消息。但是,如果本地用户尝试,他们会被转发到 intranet.example.com:8002,并且端口 8002 仅在本地可用,因此外部用户无法访问此站点上的网页

geo $local_user {
    192.168.155.0/24 0;
    172.16.10.0/28 1;
    172.16.155.0/24 1;
}

server {
    listen       80;
    server_name  intranet.example.com;

    client_max_body_size 4M;
    client_body_buffer_size 128k;

    # Space for lets encrypt to perform challenges
    location ~ /\.well-known/ {
            root /usr/local/www/nginx/intranet;
    }

    if ($local_user) {
        # If user is local, redirect them to SSL proxy only available locally
        return         301 https://intranet.example.com:8002$request_uri;
    }

# Default block all non local users see
location / {
        root   /home/luke/forbidden_html;
        index  index.html;
}


# This server block is only available to local users inside geo $local_user
# this block listens on an internal port only, so it is never availble to 
# external networks
server {
        listen       8002 default ssl; # listen on a port only accessible locally
        server_name  intranet.example.com;
        ssl_certificate    /usr/local/etc/letsencrypt/live/intranet.example.com/fullchain.pem;
        ssl_certificate_key    /usr/local/etc/letsencrypt/live/intranet.example.com/privkey.pem;

        client_max_body_size 4M;
        client_body_buffer_size 128k;

        location / {
        allow   192.168.155.0/24; 
        allow   172.16.10.0/28;   # also add in allow/deny rules in this block (extra security)
        allow   172.16.155.0/24;  

        root   /home/luke/ecom2/dist;
        index  index.html;


    deny   all;
        }


}

现在,金字塔/nginx 的结合问题来了,如果我使用与上述相同的配置,但在 8002 上为我的服务器设置以下设置:

server {
    listen       8002 default ssl; # listen on a port only accessible locally
    server_name  intranet.example.com;
    ssl_certificate    /usr/local/etc/letsencrypt/live/intranet.example.com/fullchain.pem;
    ssl_certificate_key    /usr/local/etc/letsencrypt/live/intranet.example.com/privkey.pem;

    client_max_body_size 4M;
    client_body_buffer_size 128k;

    location / {
    allow   192.168.155.0/24; 
    allow   172.16.10.0/28;   # also add in allow/deny rules in this block (extra security)
    allow   172.16.155.0/24;  
    # Forward all requests to python application server
    proxy_set_header Host $host;
    proxy_pass   http://10.1.1.16:6543;
    proxy_intercept_errors on;
    deny   all;
    }

}

我遇到了各种各样的问题,首先在金字塔内部,我在视图/模板中使用了以下代码

request.route_url # get route url for desired function

现在使用带有上述设置的 request.route_url 应该会导致 https://intranet.example.com:8002/login 路由到 https://intranet.example.com:8002/welcome 但实际上,此设置会将用户转发到:http://intranet.example.com/welcome 同样这是不正确。

如果我将 route_url 与 NGINX 代理设置一起使用:

proxy_set_header Host $http_host;

我收到错误:NGINX 返回 400 错误:

400: The plain HTTP request was sent to HTTPS port

对:https://intranet.example.com:8002/ 的请求被还原为:http://intranet.example.com/login(省略端口和 https)

然后我使用了相同的 nginx 设置(标头 $htto),但我想我会改为使用:

request.route_path

我的理论是这应该强制所有内容保持在相同的 url 前缀上,并且只是将用户从 https://intranet.example.com:8002/login 转发到 https://intranet.example.com:8002/welcome,但实际上,此设置的执行方式与使用 route_url 的方式相同。

proxy_set_header Host $http_host;

然后在导航到 https://intranet.example.com:8002 时出现错误

400: The plain HTTP request was sent to HTTPS port

并且对:https://intranet.example.com:8002/ 的请求被还原为:http://intranet.example.com/login(省略端口和 https)

谁能协助正确设置,以便我在https://intranet.example.com:8002上提供我的申请

编辑:

也试过了:

    location / {
        allow   192.168.155.0/24;
        allow   172.16.10.0/28;   # also add in allow/deny rules in this block (extra security)
        allow   172.16.155.0/24;
        # Forward all requests to python application server
        proxy_set_header Host $host:$server_port;
        proxy_pass   http://10.1.1.16:8002;
        proxy_intercept_errors on;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # root   /home/luke/ecom2/dist;
        # index  index.html;


        deny   all;
        }

结果相同。

【问题讨论】:

  • IRC 推荐的解决方案是使用proxy_set_header Host $host:$server_port;
  • 是的,这也不起作用。删除 proxy_set_header 解决了这个问题。

标签: nginx pyramid gunicorn lets-encrypt


【解决方案1】:

我检查了一个类似的配置,你的最后一个例子似乎是正确的, 至少对于一个简单的 gunicorn/pyramid 应用程序组合。

您的谜题中似乎缺少某些东西)

这是我的代码(我是 Pyramid 的新手,所以可能会做得更好)

helloworld.py

from pyramid.config import Configurator
from pyramid.renderers import render_to_response


def main(request):
    return render_to_response('templates:test.pt', {}, request=request)


with Configurator() as config:
    config.add_route('main', '/')
    config.add_view(main, route_name='main')
    config.include('pyramid_chameleon')
    app = config.make_wsgi_app()

模板/test.pt

<html>
<body>
    Route url: ${request.route_url('main')}
</body>
</html>

我的 nginx 配置

server {
    listen 80;
    server_name             pyramid.lan;

    location / {
        return              301 https://$server_name:8002$request_uri;
    }
}

server {
    listen                      8002;
    server_name                 pyramid.lan;

    ssl                         on;
    ssl_certificate             /usr/local/etc/nginx/cert/server.crt;
    ssl_certificate_key         /usr/local/etc/nginx/cert/server.key;

    location / {
        proxy_set_header        Host $host:$server_port;
        proxy_pass              http://127.0.0.1:5678;
        proxy_set_header        X-Forwarded-Proto $scheme;
        proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

这就是我运行 gunicorn 的方式:

gunicorn -w 1 -b 127.0.0.1:5678 helloworld:app

是的,它有效:

$ curl --insecure https://pyramid.lan:8002/
<html>
<body>
    Route url: https://pyramid.lan:8002/
</body>
</html>

$ curl -D - http://pyramid.lan
HTTP/1.1 301 Moved Permanently
Server: nginx/1.12.2
Date: Thu, 02 Nov 2017 20:41:50 GMT
Content-Type: text/html
Content-Length: 185
Connection: keep-alive
Location: https://pyramid.lan:8002/

让我们弄清楚你的情况可能出了什么问题

  • http 400 通常在您通过 httP 而不是 httpS 到等待 httpS 请求的服务器时弹出。如果帖子中没有错字,并且当您导航到 https://intranet.example.com:8002 时确实发生了错字,那么很高兴看到 curl 请求显示此内容和 tcpdump 显示正在发生的事情。实际上,您只需键入 http://intranet.example.com:8002 即可轻松复制它
  • 另一个想法是,您正在从您的应用程序进行重定向,并且在发生重定向时链接会断开。我更好地描述用户如何从https://intranet.example.com:8002/login 导航到.../welcome 会有所帮助

  • 还有一个想法是,您的应用程序并不那么简单,您使用了一些中间件/自定义,使默认逻辑工作方式不同,并且您的 X-Forwarded-Proto 标头被忽略 - 在这种情况下,行为将与您一样描述

【讨论】:

  • 谢谢,我可以提供根 URL 没问题,例如pyramid.lan:8002 当我的金字塔应用程序返回 HTTP_FOUND (route_path) 时,它会省略端口和方案。感谢您的帮助,我会进一步调查
  • 让我们在您的环境中尝试我的示例应用程序(即在不同的隐藏位置启动它) - 如果它有效,那么问题肯定出在您的应用程序中,我们可以深入挖掘可能出现的问题它。如果不是 - 问题出在 nginx 配置中。有可能吗?
  • @feast,看看这是否有助于github.com/Pylons/pyramid/issues/1435。如果没有,请告诉我
  • @TarunLalwani,如果使用 gunicorn,似乎不需要 PrefixMiddleware
  • @crooksey,您现在使用proxy_set_header Host $host:$server_port; 进行测试了吗?
【解决方案2】:

显然,这里的问题是您的后端生成的 Location 指令中缺少端口。

现在,为什么端口不见了?最肯定的是,因为下面的代码:

proxy_set_header Host $host;
    1234563 /p> 1234563这样的逻辑。

因此,从 nginx 的角度来看,我看到了多种可能的独立解决方案:

  • 删除proxy_set_header Host,让proxy_redirect发挥作用;
  • 适当设置proxy_set_header Host,以包含端口号,例如,使用您认为合适的$host:$server_port$http_host(如果这不起作用,那么可能缺陷实际上在于您的上游应用程序本身,但是不要害怕——请阅读下文);
  • 提供自定义proxy_redirect 设置,例如proxy_redirect https://pyramid.lan/ /(相当于proxy_redirect https://pyramid.lan/ https://pyramid.lan:8002/),这将确保所有Location 响应都具有正确的端口;这不起作用的唯一方法是,如果您的上游使用缺少的端口进行非 HTTP 重定向。

【讨论】:

  • 好的,这就像删除 proxy_set_header 一样简单,非常感谢。我知道我很接近了!
猜你喜欢
  • 2015-11-23
  • 2015-07-22
  • 2022-08-19
  • 2020-09-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-21
相关资源
最近更新 更多