使用 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。为此,我是否还需要ProxyPass
和 ProxyPassReverse
为 https
?【参考方案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 连接的主要内容,如果未能解决你的问题,请参考以下文章