使用和不使用 SSL 的 Apache 反向代理之间行为不同的原因

Posted

技术标签:

【中文标题】使用和不使用 SSL 的 Apache 反向代理之间行为不同的原因【英文标题】:Reasons for differing behavior between Apache reverse proxy with and without SSL 【发布时间】:2020-08-21 17:54:12 【问题描述】:

我一直在研究一个本地反向代理,它在两个本地 Apache 安装之间路由流量(每个都运行不同版本的 mod_wsgi,这就是分叉的原因)。无论请求是 HTTP 还是 HTTPS,我都希望这个反向代理能够正常工作。

但是,当使用 SSL 时,ProxyPassReverse 不会(正确)修改 Location 响应标头。

以下分别是 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 时,位置标头返回为:Location: http://snakeoil.us.com/6x/snk610/index

但是,当我访问 url https://snakeoil.us.com/6x/snk610/index 时,位置标头返回为:Location: https://snakeoil.us.com/snk610/index,这会导致 404,因为只有两个本地 Apache 实例(与 6x 路由关联的那个)被代理识别 snk610 别名(在这种情况下,它不是路由到的实例)。

底线是 HTTP VirtualHost 定义代理两个本地 Apache 实例之间的请求而不会失败。但是,HTTPS VirtualHost 定义没有,我不清楚是什么导致了这种差异。

【问题讨论】:

【参考方案1】:

设法找到解决方案。回想起来,应该更明显。

在代理到的 Apache 实例上,我将 access_log 格式更改为如下:

LogFormat "%h %l %u %t \"%r\" %>s %b   -->   ResponseLocation: '%Locationo'" 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&params= HTTP/1.1" 302 204   -->   ResponseLocation: 'http://localhost:10090/snk610/index'

从上面可以看出,响应位置标头看起来与预期一样,即 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&params= HTTP/1.1" 240 752392   -->   ResponseLocation: 'https://snakeoil.us.com/snk610/index'

从上面可以看出,响应位置标头中的服务器名称已替换为传入的主机名称。这就是导致 ProxyPassReverse 无法替换传出主机名(在反向代理服务器上)的原因。

我通过显式更新被代理到的服务器上的传出位置标头解决了这个问题:

    # 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

【讨论】:

以上是关于使用和不使用 SSL 的 Apache 反向代理之间行为不同的原因的主要内容,如果未能解决你的问题,请参考以下文章

Apache2 反向代理上的 Odoo 14 - 页面速度错误始终如一

Nginx:15---反向代理之(安全隔离:SSL流量加密SSL客户端身份验证基于原始IP地址阻止流量)

使用 Apache 反向代理的 Ratchet websocket 没有响应

为Apache启用mod_proxy做正向代理反向代理

apache2反向代理到docker容器403被禁止?

12.反向代理——Apache2