使用和不使用 SSL 的 Apache 反向代理之间行为不同的原因
Reasons for differing behavior between Apache reverse proxy with and without SSL
我一直致力于在两个本地 Apache 安装之间路由流量的本地反向代理(每个 运行 一个不同版本的 mod_wsgi,这就是分叉的原因)。无论请求是 HTTP 还是 HTTPS,我都希望这个反向代理能够正常工作。
但是,当使用 SSL 时,位置响应 header 不会被 ProxyPassReverse 修改(正确)。
以下分别是 HTTP 和 HTTPS 流量的 VirtualHost 定义:
<VirtualHost *:80>
# Proxy traffic for Version 6 with an alias of: 6x/
ProxyPass /6x/ http://localhost:10090/
ProxyPassReverse /6x/ http://localhost:10090/
# Proxy traffic for previous versions with aliases of: 5x/, 4x/, and /
ProxyPass /5x/ http://localhost:10080/
ProxyPassReverse /5x/ http://localhost:10080/
ProxyPass /4x/ http://localhost:10080/
ProxyPassReverse /4x/ http://localhost:10080/
ProxyPass / http://localhost:10080/
ProxyPassReverse / http://localhost:10080/
</VirtualHost>
<IfModule mod_ssl.c>
<VirtualHost *:443>
ServerName snakeoil.us.com
ProxyPreserveHost on
ProxyRequests off
SSLEngine on
SSLProxyEngine on
SSLProxyVerify none
SSLProxyCheckPeerCN off
SSLProxyCheckPeerName off
SSLProxyCheckPeerExpire off
SSLCertificateFile /etc/ssl/certs/snakeoil.crt
SSLCertificateKeyFile /etc/ssl/certs/snakeoil.key
SSLCertificateChainFile /etc/ssl/certs/bundle-client.crt
# Proxy traffic for Version 6 with an alias of: 6x/
ProxyPass /6x/ https://localhost:10453/
ProxyPassReverse /6x/ https://localhost:10453/
# Proxy traffic for previous versions with aliases of: 5x/, 4x/, and /
ProxyPass /5x/ https://localhost:10443/
ProxyPassReverse /5x/ https://localhost:10443/
ProxyPass /4x/ https://localhost:10443/
ProxyPassReverse /4x/ https://localhost:10443/
ProxyPass / https://localhost:10443/
ProxyPassReverse / https://localhost:10443/
</VirtualHost>
</IfModule>
当我访问 url http://snakeoil.us.com/6x/snk610/index
时,位置 header 返回为:Location: http://snakeoil.us.com/6x/snk610/index
.
但是,当我访问 url https://snakeoil.us.com/6x/snk610/index
时,位置 header 返回为:Location: https://snakeoil.us.com/snk610/index
,这导致 404,因为只有两个之一被代理的本地 Apache 实例(与 6x 路由关联的实例)识别 snk610
别名(在这种情况下它不是被路由到的实例)。
底线是 HTTP VirtualHost 定义代理两个本地 Apache 实例之间的请求而不会失败。但是,HTTPS VirtualHost 定义没有,我不清楚是什么导致了这种差异。
设法找到解决方案。回想起来,应该更明显。
在被代理的 Apache 实例上,我将 access_log 格式更改为以下内容:
LogFormat "%h %l %u %t \"%r\" %>s %b --> ResponseLocation: '%{Location}o'" common
这会导致传出响应位置被记录。
这是 Apache HTTP 实例(被代理到)的输出:
[snake6x@test1 httpd6x]$ grep "ResponseLocation: 'http" logs/access_log
::1 - - [06/May/2020:15:43:25 -0400] "GET /snk610 HTTP/1.1" 301 233 --> ResponseLocation: 'http://localhost:10090/snk610/index'
::1 - - [06/May/2020:15:43:30 -0400] "GET /snk610/index HTTP/1.1" 302 247 --> ResponseLocation: 'http://localhost:10090/snk610/login?params=&message=&redirect_to=index'
::1 - - [06/May/2020:15:43:32 -0400] "POST /snk610/auth?redirect_to=index¶ms= HTTP/1.1" 302 204 --> ResponseLocation: 'http://localhost:10090/snk610/index'
从上面可以看出,响应位置header看起来符合预期,即ProxyPassReverse应该能够成功替换它。
相反,这是 Apache HTTPS 实例(被代理到)的输出:
[snake6x@test1 httpd]$ grep "ResponseLocation: 'http" logs/ssl_request_log
[06/May/2020:19:53:38 +0000] ::1 "GET /snk610 HTTP/1.1" 240 2645788 --> ResponseLocation: 'https://snakeoil.us.com/snk610/index'
[06/May/2020:19:56:21 +0000] ::1 "GET /snk610/index HTTP/1.1" 254 2682899 --> ResponseLocation: 'https://snakeoil.us.com/snk610/login?params=&message=&redirect_to=index'
[06/May/2020:19:56:23 +0000] ::1 "POST /snk610/auth?redirect_to=index¶ms= HTTP/1.1" 240 752392 --> ResponseLocation: 'https://snakeoil.us.com/snk610/index'
从上面可以看出,在响应位置 header 中,服务器名称已替换为传入的主机名。这就是导致 ProxyPassReverse 无法替换传出主机名(在反向代理服务器上)的原因。
我通过明确更新被代理服务器上的传出位置 header 解决了这个问题:
# Since this server has a proxy immediately in front of it, we need the outgoing
# location to match the incoming location. However, the ServerName tag will
# cause the incoming location to be changed to include the ServerName, which will
# cause the upstream ProxyPassReverse to fail to update the outgoing location
# properly.
#
# This Header modification replaces the outgoing ServerName with the incoming
# name.
#
# FIXME: There is surely a better way to do this with a variable that contains
# the incoming host
Header edit Location ^https://snakeoil.us.com:443 https://localhost:10453
Header edit Location ^https://snakeoil.us.com https://localhost:10453
我一直致力于在两个本地 Apache 安装之间路由流量的本地反向代理(每个 运行 一个不同版本的 mod_wsgi,这就是分叉的原因)。无论请求是 HTTP 还是 HTTPS,我都希望这个反向代理能够正常工作。
但是,当使用 SSL 时,位置响应 header 不会被 ProxyPassReverse 修改(正确)。
以下分别是 HTTP 和 HTTPS 流量的 VirtualHost 定义:
<VirtualHost *:80>
# Proxy traffic for Version 6 with an alias of: 6x/
ProxyPass /6x/ http://localhost:10090/
ProxyPassReverse /6x/ http://localhost:10090/
# Proxy traffic for previous versions with aliases of: 5x/, 4x/, and /
ProxyPass /5x/ http://localhost:10080/
ProxyPassReverse /5x/ http://localhost:10080/
ProxyPass /4x/ http://localhost:10080/
ProxyPassReverse /4x/ http://localhost:10080/
ProxyPass / http://localhost:10080/
ProxyPassReverse / http://localhost:10080/
</VirtualHost>
<IfModule mod_ssl.c>
<VirtualHost *:443>
ServerName snakeoil.us.com
ProxyPreserveHost on
ProxyRequests off
SSLEngine on
SSLProxyEngine on
SSLProxyVerify none
SSLProxyCheckPeerCN off
SSLProxyCheckPeerName off
SSLProxyCheckPeerExpire off
SSLCertificateFile /etc/ssl/certs/snakeoil.crt
SSLCertificateKeyFile /etc/ssl/certs/snakeoil.key
SSLCertificateChainFile /etc/ssl/certs/bundle-client.crt
# Proxy traffic for Version 6 with an alias of: 6x/
ProxyPass /6x/ https://localhost:10453/
ProxyPassReverse /6x/ https://localhost:10453/
# Proxy traffic for previous versions with aliases of: 5x/, 4x/, and /
ProxyPass /5x/ https://localhost:10443/
ProxyPassReverse /5x/ https://localhost:10443/
ProxyPass /4x/ https://localhost:10443/
ProxyPassReverse /4x/ https://localhost:10443/
ProxyPass / https://localhost:10443/
ProxyPassReverse / https://localhost:10443/
</VirtualHost>
</IfModule>
当我访问 url http://snakeoil.us.com/6x/snk610/index
时,位置 header 返回为:Location: http://snakeoil.us.com/6x/snk610/index
.
但是,当我访问 url https://snakeoil.us.com/6x/snk610/index
时,位置 header 返回为:Location: https://snakeoil.us.com/snk610/index
,这导致 404,因为只有两个之一被代理的本地 Apache 实例(与 6x 路由关联的实例)识别 snk610
别名(在这种情况下它不是被路由到的实例)。
底线是 HTTP VirtualHost 定义代理两个本地 Apache 实例之间的请求而不会失败。但是,HTTPS VirtualHost 定义没有,我不清楚是什么导致了这种差异。
设法找到解决方案。回想起来,应该更明显。
在被代理的 Apache 实例上,我将 access_log 格式更改为以下内容:
LogFormat "%h %l %u %t \"%r\" %>s %b --> ResponseLocation: '%{Location}o'" common
这会导致传出响应位置被记录。
这是 Apache HTTP 实例(被代理到)的输出:
[snake6x@test1 httpd6x]$ grep "ResponseLocation: 'http" logs/access_log
::1 - - [06/May/2020:15:43:25 -0400] "GET /snk610 HTTP/1.1" 301 233 --> ResponseLocation: 'http://localhost:10090/snk610/index'
::1 - - [06/May/2020:15:43:30 -0400] "GET /snk610/index HTTP/1.1" 302 247 --> ResponseLocation: 'http://localhost:10090/snk610/login?params=&message=&redirect_to=index'
::1 - - [06/May/2020:15:43:32 -0400] "POST /snk610/auth?redirect_to=index¶ms= HTTP/1.1" 302 204 --> ResponseLocation: 'http://localhost:10090/snk610/index'
从上面可以看出,响应位置header看起来符合预期,即ProxyPassReverse应该能够成功替换它。
相反,这是 Apache HTTPS 实例(被代理到)的输出:
[snake6x@test1 httpd]$ grep "ResponseLocation: 'http" logs/ssl_request_log
[06/May/2020:19:53:38 +0000] ::1 "GET /snk610 HTTP/1.1" 240 2645788 --> ResponseLocation: 'https://snakeoil.us.com/snk610/index'
[06/May/2020:19:56:21 +0000] ::1 "GET /snk610/index HTTP/1.1" 254 2682899 --> ResponseLocation: 'https://snakeoil.us.com/snk610/login?params=&message=&redirect_to=index'
[06/May/2020:19:56:23 +0000] ::1 "POST /snk610/auth?redirect_to=index¶ms= HTTP/1.1" 240 752392 --> ResponseLocation: 'https://snakeoil.us.com/snk610/index'
从上面可以看出,在响应位置 header 中,服务器名称已替换为传入的主机名。这就是导致 ProxyPassReverse 无法替换传出主机名(在反向代理服务器上)的原因。
我通过明确更新被代理服务器上的传出位置 header 解决了这个问题:
# Since this server has a proxy immediately in front of it, we need the outgoing
# location to match the incoming location. However, the ServerName tag will
# cause the incoming location to be changed to include the ServerName, which will
# cause the upstream ProxyPassReverse to fail to update the outgoing location
# properly.
#
# This Header modification replaces the outgoing ServerName with the incoming
# name.
#
# FIXME: There is surely a better way to do this with a variable that contains
# the incoming host
Header edit Location ^https://snakeoil.us.com:443 https://localhost:10453
Header edit Location ^https://snakeoil.us.com https://localhost:10453