Nginx/Pyramid 自定义 SSL 端口
Nginx/Pyramid custom SSL port
作为前缀,我已经使用以下堆栈一段时间并取得了巨大成功:
NGINX - 网络代理
SSL - 在 nginx 中配置
金字塔网络应用程序,由 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;
}
}
现在,这就是我为面向 public 的应用程序提供服务的方式,效果很好。对于我所有的内部应用程序,我过去常常简单地将用户定向到一个内部域示例:http://subdomain.company.domain,这在很长一段时间内都运行良好。
现在在 KRACK 攻击之后,虽然我们有一些非常彻底的防火墙规则来防止很多攻击,但我想强制所有内部流量通过 SSL,并且我不想使用自签名证书,我想使用 lets encrypt 这样我就可以 auto-renew 使管理更容易(也更便宜)的证书。
为了使用 lets encrypt,我需要有一个 public 面向 DNS 和服务器来执行 ACME 质询(用于自动更新)。现在这又是在 nginx 中设置的一件非常容易的事情,下面的配置非常适合提供静态内容:
它的作用是,如果互联网用户访问 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;
}
}
现在,pyramid/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;
}
}
我 运行 遇到了各种各样的问题,首先在金字塔内部 我在我的 views/templates
中使用了以下代码
request.route_url # get route url for desired function
现在使用具有上述设置的 request.route_url 应该会导致 https://intranet.example.com:8002/login to route tohttps://intranet.example.com:8002/welcome but in reality, this setup would forward a user to: http://intranet.example.com/welcome 这又是不正确的。
如果我使用 route_url 和 NGINX 代理设置:
proxy_set_header Host $http_host;
我收到错误:NGINX 到 return 400 错误:
400: The plain HTTP request was sent to HTTPS port
并请求:https://intranet.example.com:8002/ gets reverted to: http://intranet.example.com/login(省略端口和 https)
然后我使用相同的 nginx 设置 (header $htto),但我想我会改为使用:
request.route_path
我的理论是这应该强制所有内容都保持相同的 url 前缀,并且只是转发来自 https://intranet.example.com:8002/login to https://intranet.example.com:8002/welcome 的用户,但实际上,此设置的执行方式与使用 [=78] 相同=].
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/ gets reverted to: 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;
}
结果相同。
我检查了一个类似的配置,你的最后一个例子似乎是正确的,
至少对于简单的 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()
templates/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 而不是 httpS 到等待 httpS[= 的服务器时,通常会弹出 - http 400 52=] 请求。如果 post 中没有拼写错误,并且当您导航到 https://intranet.example.com:8002 时它确实发生了,那么很高兴看到
curl
请求显示了这一点,并且 tcpdump
显示了正在发生的事情。实际上,您只需输入 http://intranet.example.com:8002
即可轻松重现它
另一个想法是,您正在从您的应用程序进行重定向,并且 link 在重定向发生时被破坏。我更好地描述用户如何从 https://intranet.example.com:8002/login
导航到 .../welcome
会有所帮助
还有一个想法是您的应用程序并不那么简单,您使用了一些中间件/定制,这使得默认逻辑的工作方式不同,您的 X-Forwarded-Proto
header 被忽略了 -在这种情况下,行为将与您描述的一样
这里的问题显然是后端生成的 Location
指令中缺少端口。
现在,为什么缺少端口?肯定是因为以下代码:
proxy_set_header Host $host;
请注意,$host
本身不包含 $server_port
,与 $http_host
不同,因此,如果您只需单独使用 $host
。
请注意,default
的 proxy_redirect
默认值期望 Location
与 proxy_pass
的值相对应,以便发挥其魔力(根据文档),因此,您的显式 header 设置可能会干扰此类逻辑。
因此,从 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
响应都具有正确的端口;唯一不起作用的方法是,如果您的上游 non-HTTP 使用缺少的端口进行重定向。
作为前缀,我已经使用以下堆栈一段时间并取得了巨大成功:
NGINX - 网络代理 SSL - 在 nginx 中配置 金字塔网络应用程序,由 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;
}
}
现在,这就是我为面向 public 的应用程序提供服务的方式,效果很好。对于我所有的内部应用程序,我过去常常简单地将用户定向到一个内部域示例:http://subdomain.company.domain,这在很长一段时间内都运行良好。
现在在 KRACK 攻击之后,虽然我们有一些非常彻底的防火墙规则来防止很多攻击,但我想强制所有内部流量通过 SSL,并且我不想使用自签名证书,我想使用 lets encrypt 这样我就可以 auto-renew 使管理更容易(也更便宜)的证书。
为了使用 lets encrypt,我需要有一个 public 面向 DNS 和服务器来执行 ACME 质询(用于自动更新)。现在这又是在 nginx 中设置的一件非常容易的事情,下面的配置非常适合提供静态内容:
它的作用是,如果互联网用户访问 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;
}
}
现在,pyramid/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;
}
}
我 运行 遇到了各种各样的问题,首先在金字塔内部 我在我的 views/templates
中使用了以下代码request.route_url # get route url for desired function
现在使用具有上述设置的 request.route_url 应该会导致 https://intranet.example.com:8002/login to route tohttps://intranet.example.com:8002/welcome but in reality, this setup would forward a user to: http://intranet.example.com/welcome 这又是不正确的。
如果我使用 route_url 和 NGINX 代理设置:
proxy_set_header Host $http_host;
我收到错误:NGINX 到 return 400 错误:
400: The plain HTTP request was sent to HTTPS port
并请求:https://intranet.example.com:8002/ gets reverted to: http://intranet.example.com/login(省略端口和 https)
然后我使用相同的 nginx 设置 (header $htto),但我想我会改为使用:
request.route_path
我的理论是这应该强制所有内容都保持相同的 url 前缀,并且只是转发来自 https://intranet.example.com:8002/login to https://intranet.example.com:8002/welcome 的用户,但实际上,此设置的执行方式与使用 [=78] 相同=].
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/ gets reverted to: 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;
}
结果相同。
我检查了一个类似的配置,你的最后一个例子似乎是正确的, 至少对于简单的 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()
templates/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 而不是 httpS 到等待 httpS[= 的服务器时,通常会弹出
- http 400 52=] 请求。如果 post 中没有拼写错误,并且当您导航到 https://intranet.example.com:8002 时它确实发生了,那么很高兴看到
curl
请求显示了这一点,并且tcpdump
显示了正在发生的事情。实际上,您只需输入http://intranet.example.com:8002
即可轻松重现它
另一个想法是,您正在从您的应用程序进行重定向,并且 link 在重定向发生时被破坏。我更好地描述用户如何从
https://intranet.example.com:8002/login
导航到.../welcome
会有所帮助还有一个想法是您的应用程序并不那么简单,您使用了一些中间件/定制,这使得默认逻辑的工作方式不同,您的
X-Forwarded-Proto
header 被忽略了 -在这种情况下,行为将与您描述的一样
这里的问题显然是后端生成的 Location
指令中缺少端口。
现在,为什么缺少端口?肯定是因为以下代码:
proxy_set_header Host $host;
请注意,
$host
本身不包含$server_port
,与$http_host
不同,因此,如果您只需单独使用$host
。请注意,
default
的proxy_redirect
默认值期望Location
与proxy_pass
的值相对应,以便发挥其魔力(根据文档),因此,您的显式 header 设置可能会干扰此类逻辑。
因此,从 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
响应都具有正确的端口;唯一不起作用的方法是,如果您的上游 non-HTTP 使用缺少的端口进行重定向。