使用 apache 建立安全 websocket 连接

Posted

技术标签:

【中文标题】使用 apache 建立安全 websocket 连接【英文标题】:tunneling secure websocket connections with apache 【发布时间】:2012-07-13 03:59:53 【问题描述】:

我有一个只能通过 HTTPS 访问的 Apache。我想从在同一台机器上运行的其他服务器应用程序提供 websockets,但由于客户端无法通过 443 以外的其他端口连接到我们的服务器,因此这些 websocket 连接需要通过 Apache 代理。

现在,我已经安装了 mod_proxy 并配置如下:

SSLProxyEngine on
ProxyPass /ws https://127.0.0.1:9001

但这不起作用。我现在可以在我的浏览器中连接到 https://server/ws,但是 apache 似乎吞下了部分 websockets 标头,因此真正的 websocket 连接不起作用。

如何通过 Apache 服务器完成我的 websocket 连接隧道?

【问题讨论】:

这个问题有进展吗? 似乎有新的 Apache 模块可能会使这更容易。但是,我使用 stunnel+HAproxy,stunnel 接受 SSL 连接并将未加密的流量传递到 HAproxy,然后决定是否存在“升级:WebSocket”标头是否应将其重定向到 websocket 服务器或通过纯 HTTP 到 Apache。使用 HAproxy 1.5 版(目前正在开发中),甚至可能不再需要使用 stunnel。 【参考方案1】:

如果您不希望 Apache 终止 SSL 连接(并转发未加密的 WebSocket 流量),但在最终目标 WebSocket 服务器上终止 SSL 并且只希望在 WebSocket 上使用 WSS流量进入 Apache,然后 mod_proxy_connect 可能只能通过原始流量进行连接。不确定。如果可行,我也会感兴趣。

如果以上不成立,这里有更多信息:

https://serverfault.com/questions/290121/configuring-apache2-to-proxy-websocket https://issues.apache.org/bugzilla/show_bug.cgi?id=47485 http://blog.alex.org.uk/2012/02/16/using-apache-websocket-to-proxy-tcp-connection/

无论如何,使用 Apache 将严重限制并发服务的 WebSocket 连接数量的可伸缩性,因为每个 WS 连接将消耗 Apache 上的 1 个进程/线程。

【讨论】:

感谢您的回复。但是,我认为 mod_proxy_connect 在这里没有帮助,因为它只处理来自浏览器的 CONNECT 请求。我需要的是透明的反向代理。顺便说一句,只要我不使用 SSL,一切都可以正常工作。 我必须重定向唯一的 WebSocket 请求并处理 apache 虚拟主机本身的 https 请求,我正在重定向这个 WebSockets。为此,我是否还需要 ProxyPassProxyPassReversehttps【参考方案2】:

我已经搞定了。

场景

-------------       ----------------       ----------
| Browser   |<----->| Apache httpd |<----->| Tomcat |
|           |  SSL  |    2.4.9     |  SSL  | 7.0.52 |
-------------       ----------------       ----------

通过 Apache httpd 浏览器 WebSocket,反向代理到 Tomcat 中的 Web 应用程序。所有 SSL 从前到后。

这里是每个部分的配置:

浏览器客户端

注意网址中的尾随“/”:wss://host/app/ws/。必须匹配正确的 wss ProxyPass 指令(在 Apache 配置部分中进一步显示)并防止 301 重定向到 https://host/app/ws。也就是说,它使用的是 https 方案,而不是后端的 wss 方案。

测试页
<!doctype html>
<body>

<script type="text/javascript">
    var connection = new WebSocket("wss://host/app/ws/");

    connection.onopen = function () 
        console.log("connected");
    ;

    connection.onclose = function () 
        console.log("onclose");
    ;

    connection.onerror = function (error) 
        console.log(error);
    ;
</script>

</body>
</html>

Apache httpd

我使用的是 Apache httpd 2.4.9,它提供了开箱即用的 mod_proxy_wstunnel。但是,在使用 wss:// 方案时,提供的 mod_proxy_wstunnel.so 不支持 SSL。它最终尝试以纯文本形式连接到后端(Tomcat),这导致 SSL 握手失败。请参阅错误here。因此,您必须按照错误报告中建议的更正自行修补 mod_proxy_wstunnel.c。这是一个简单的 3 行更改。

Suggested correction,
314a315
>     int is_ssl = 0;
320a322
>         is_ssl = 1;
344c346
<     backend->is_ssl = 0;
---
>     backend->is_ssl = is_ssl;

然后重建模块并用旧的替换新的 mod_proxy_wstunnel.so

构建 Apache httpd

这是我用来在我想要的模块中构建的 (2.4.9) 命令。您可能不需要全部。

./configure --prefix=/usr/local/apache --with-included-apr --enable-alias=shared
--enable-authz_host=shared --enable-authz_user=shared 
--enable-deflate=shared --enable-negotiation=shared 
--enable-proxy=shared --enable-ssl=shared --enable-reqtimeout=shared
--enable-status=shared --enable-auth_basic=shared
--enable-dir=shared --enable-authn_file=shared
--enable-autoindex=shared --enable-env=shared --enable-php5=shared
--enable-authz_default=shared --enable-cgi=shared
--enable-setenvif=shared --enable-authz_groupfile=shared
--enable-mime=shared --enable-proxy_http=shared
--enable-proxy_wstunnel=shared

注意最后一个开关:--enable-proxy_wstunnel=shared 起初,我错误地使用了 --enable-proxy-wstunnel=shared,它似乎构建得很好,但最终无法正常工作我使用了生成的 .so 文件。看到不同?您要确保在 "proxy_wstunnel" 中使用下划线而不是破折号。

Apache httpd 配置

httpd.conf
...
LoadModule proxy_module modules/mod_proxy.so
...
LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so
...
LoadModule ssl_module modules/mod_ssl.so
...
Include conf/extra/httpd-ssl.conf
...
LogLevel debug
ProxyRequests off

# Note, this is the preferred ProxyPass configuration, and *should* be equivalent
# to the same inline version below, but it does NOT WORK!
#<Location /app/ws/>
#        ProxyPass wss://localhost:8443/app/ws
#        ProxyPassReverse wss://localhost:8443/app/ws
#</Location>
#<Location /app/>
#        ProxyPass https://localhost:8443/app/
#        ProxyPassReverse https://localhost:8443/app/
#</Location>

# NOTE: Pay strict attention to the slashes "/" or lack thereof!
# WebSocket url endpoint
ProxyPass /app/ws/ wss://localhost:8443/app/ws
ProxyPassReverse /app/ws/ wss://localhost:8443/app/ws

# Everything else
ProxyPass /app/ https://localhost:8443/app/
ProxyPassReverse /app/ https://localhost:8443/app/

如果您在上面的配置中没有看到我的注释,这里又是:请严格注意斜线“/”或缺少斜线!

另外,如果您在 apache 日志中看到调试日志语句显示 wss 连接已建立然后关闭,那么您可能像我一样启用了 mod_reqtimeout,因此请确保它没有加载:

#LoadModule reqtimeout_module modules/mod_reqtimeout.so

雄猫

假设您的 HTTP 连接器设置正确,则在 tomcat 中无需进行太多配置。尽管为了帮助调试,我发现创建一个如下所示的$CATALINA_HOME/bin/setenv.sh 很有用:

设置环境文件
CATALINA_OPTS=$CATALINA_OPTS" -Djavax.net.debug=all -Djavax.net.debug=ssl:handshake:verbose"

这让我可以查看我修改的 mod_proxy_wstunnel.so 是否适用于 wss://。当它不工作时,我的 catalina.out 日志文件会显示:

javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection?
http-nio-8443-exec-1, SEND TLSv1 ALERT:  fatal, description = internal_error
http-nio-8443-exec-1, WRITE: TLSv1 Alert, length = 2
http-nio-8443-exec-1, called closeOutbound()
http-nio-8443-exec-1, closeOutboundInternal()

最后的想法

虽然我使用的是 Apache httpd 2.4.9,I've seen where backports of mod_proxy_wstunnel can be applied to versions 2.2.x。希望我上面的笔记可以应用于那些旧版本。

【讨论】:

值得注意的是 mod_proxy_wstunnel 还可以将非 SSL websockets 封装在 SSL 代理后面。 @PhilLello,那么我假设如果您通过非 ssl websocket 连接到 WS,则不需要修改后的 mod_proxy_wstunnel,但可以使用“开箱即用”的。如果 Apache 在 localhost 上运行,是否有任何令人信服的理由让 Apache 连接到 wss 而不是 ws?似乎是一个很好的解决方案。 @Tom,您认为这是一个很好的安全解决方案吗?我不是 WebSocket 专家,但我认为,将安全连接重定向到非安全并不是一件好事(如果我错了,请纠正我)谢谢! 我实际上是在尝试在 SSL 代理后面包装一个非 ssl websocket,但似乎无法让它工作..任何指针? 注意:提到的 apache 错误自 2.4.10 起已修复。

以上是关于使用 apache 建立安全 websocket 连接的主要内容,如果未能解决你的问题,请参考以下文章

websockets apache 服务器兼容性

ruby 中的安全 Websocket 客户端

WebSockets (Apache ProxyPass)

多用户的春天WebSocket安全问题,怎么解决

WebSocket无法建立到服务器的连接?

使用多域 SSL SAN 证书无法在 Chrome 中建立 wss websocket 连接