tunneling secure websocket connections with apache

佐手、 提交于 2019-11-27 07:07:34

I've got it working.

Scenario

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

Browser WebSocket through Apache httpd, reverse proxying to the web app in Tomcat. All SSL front-to-back.

Here's the configuration for each piece:

Browser Client

Note the trailing "/" in the url: wss://host/app/ws/. It was necessary to match the correct wss ProxyPass directive (shown further down in the Apache config section) and preventing a 301 redirect to https://host/app/ws. That is, it was redirecting using the https scheme and not the wss scheme for the back-end.

Test Page
<!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

I am using Apache httpd 2.4.9, which out of the box provides mod_proxy_wstunnel. However, the mod_proxy_wstunnel.so provided does not support SSL when using wss:// scheme. It ends up trying to connect to the back-end (Tomcat) in plaintext, which fails the SSL handshake. See bug here. So, you have to patch mod_proxy_wstunnel.c yourself by following the suggested correction in the bug report. It's an easy 3 line change.

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

Then rebuild the module and replace in your new mod_proxy_wstunnel.so with the old one.

Building Apache httpd

Here's the (2.4.9) command I used to build in the modules I wanted. You might not need them all.

./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

Note the very last switch: --enable-proxy_wstunnel=shared At first, I was incorrectly using --enable-proxy-wstunnel=shared, which seemed to build fine, but ultimately wouldn't work when I used the resultant .so file. See the difference? You want to make sure to use an underscore in "proxy_wstunnel" rather than a dash.

Apache httpd config

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/

If you didn't see my note in the above config, here it is again: Pay strict attention to the slashes "/" or lack thereof!

Also, if you are seeing debug log statements in your apache log that says a wss connection was made then closed, it is possible that you have mod_reqtimeout enabled as I did, so make sure it not loaded:

#LoadModule reqtimeout_module modules/mod_reqtimeout.so

Tomcat

Assuming your HTTP connector is setup correct, there's not much to configure in tomcat. Though to aid in debugging, I found it useful to create a $CATALINA_HOME/bin/setenv.sh that looked like this:

setenv.sh
CATALINA_OPTS=$CATALINA_OPTS" -Djavax.net.debug=all -Djavax.net.debug=ssl:handshake:verbose"

This allowed me to see if the mod_proxy_wstunnel.so that I modified was working or not for wss://. When it wasn't working, my catalina.out log file would show:

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()

Final Thoughts

Though I am using Apache httpd 2.4.9, I've seen where backports of mod_proxy_wstunnel can be applied to versions 2.2.x. Hopefully my notes above can be applied to those older versions.

oberstet

If you don't want Apache to terminate the SSL connection (and forward unencrypted WebSocket traffic), but have the SSL terminated on the final target WebSocket server and exlusively want to use WSS on the WebSocket traffic coming into Apache, then mod_proxy_connect may be able to just connect through the raw traffic. Not sure. I'd be also interested if that works.

If above does not hold, here is more information:

In any case, using Apache will severly limit the scalability regarding number of concurrently served WebSocket connections, since every WS connection will consume 1 process/thread on Apache.

I am trying to install this https://github.com/kawasima/mod_proxy_websocket. Hope it helps.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!