WebSocket through SSL with Apache reverse proxy - apache

On the client side, I am trying to establish the wss connection:
var ws = new WebSocket("wss://wsserver.com/test")
and it returns an error:
WebSocket connection to 'wss://wsserver.com/test' failed: Error during WebSocket handshake: Unexpected response code: 400
The full headers are:
Request Headers
GET wss://wsserver.com/test HTTP/1.1
Host: wsserver.com
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Origin: https://website.net
Sec-WebSocket-Version: 13
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36
Accept-Encoding: gzip, deflate, sdch, br
Accept-Language: en-US,en;q=0.8
Sec-WebSocket-Key: Tj9AJ5TKglNf5LoHsQTpvQ==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Response Headers
Access-Control-Allow-Credentials:true
Access-Control-Allow-Origin:https://website.net
Connection:close
Content-Length:18
Content-Type:text/plain; charset=utf-8
Date:Fri, 21 Apr 2017 21:03:45 GMT
Server:Apache/2.4.18 (Ubuntu)
Vary:Origin
X-Content-Type-Options:nosniff
The server side is running on go at port 8888 behind an Apache reverse proxy. This is the Apache configuration:
<VirtualHost *:443>
ServerName website.com
ProxyPreserveHost On
ProxyRequests Off
ProxyPass "/" "wss://localhost:8888/"
mod_proxy and mod_proxy_wstunnel are installed.
Is there something missing here? It seems like the request goes through but no connection is established.

I ended up solving this problem by using this configuration for the virtual host, which filters requests using the HTTP headers:
<VirtualHost *:443>
ServerName website.com
RewriteEngine On
# When Upgrade:websocket header is present, redirect to ws
# Using NC flag (case-insensitive) as some browsers will pass Websocket
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule ^/ws/(.*) wss://localhost:8888/ws/$1 [P,L]
# All other requests go to http
ProxyPass "/" "http://localhost:8888/"
I'm leaving this as a reference in case it helps others

In order to place a secure reverse proxy server in front of an insecure websocket server, you could do this:
<VirtualHost *:443>
SSLEngine on
SSLProxyEngine on
SSLProtocol -all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1 +TLSv1.2
SSLCipherSuite HIGH:aNULL:eNULL:EXPORT:DES:RC4:!MD5:!PSK:!SRP:!CAMELLIA
SSLCertificateFile /path/to/cert
SSLCertificateKeyFile /path/to/key
SSLCertificateChainFile /path/to/chain
ServerName website.com
RewriteEngine On
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule /(.*) ws://localhost:8888/$1 [P,L]
</VirtualHost>
This will take a request inbound for wss://website.com:443, and reverse proxy it to ws://localhost:8888.
If the websocket server is also secure, you can simply change
ws://localhost:8888 to
wss://website.com:8888

This is my setup of virtualhost that worked for me, I have .netcore app on docker with SignalR as a websocket service.
On 5000 my .netcore app is running, and on /chatHub my signalR listens.
Will be helpful for future comers with same problem.
<IfModule mod_ssl.c>
<VirtualHost *:443>
RewriteEngine On
ProxyPreserveHost On
ProxyRequests Off
# allow for upgrading to websockets
RewriteEngine On
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule /(.*) ws://localhost:5000/$1 [P,L]
RewriteCond %{HTTP:Upgrade} !=websocket [NC]
RewriteRule /(.*) http://localhost:5000/$1 [P,L]
ProxyPass "/" "http://localhost:5000/"
ProxyPassReverse "/" "http://localhost:5000/"
ProxyPass "/chatHub" "ws://localhost:5000/chatHub"
ProxyPassReverse "/chatHub" "ws://localhost:5000/chatHub"
ServerName site.com
SSLCertificateFile /etc/letsencrypt/live/site.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/site.com/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>
</IfModule>
Source: http://shyammakwana.me/server/websockets-with-apache-reverse-proxy-with-ssl.html

# pimgeek's Comment:
I think instead of
RewriteRule ^/nodered/comms wss://localhost:1880/nodered/comms [P,L]
you could have utilized $1 as follow:
RewriteRule ^/nodered/comms$ wss://localhost:1880/$1 [P,L]
Also, this should work aswell:
RewriteRule ^/nodered/comms$ wss://localhost:1880$1 [P,L]
Notice the not needed / after the port, since $1 includes already a / at the beginning

In my case, I needed to activate "SSLProxyEngine on" to make the whole thing works...
I ended up with this 2 lines solution on Debian / Apache 2.4 (used port is 4321)
SSLProxyEngine on
ProxyPass /wss wss://127.0.0.1:4321/

Related

HTTPD ReverseProxy ProxyPass directive ending in wrong Location header

HTTPD is configure as following:
#redirectder edit Location "(^http[s]?://)([^/]+)" "" port 80 to secure
<VirtualHost *:80>
ServerName mitestui02.sn.test.net
#ServerAlias server server2.domain.com server2
ServerAdmin support.p240#test.com
ErrorLog /var/log/test/iiq/appserver/apache-error.log
CustomLog /var/log/test/iiq/appserver/apache-access.log common
Redirect /identityiq/ https://mitestui02.sn.test.net/identityiq/
Redirect / https://mitestui02.sn.test.net/identityiq/
</VirtualHost>
#redirect to port 8080 on localhost
<VirtualHost *:443>
ServerName mitestui02.sn.test.net
# ServerAlias mitestui02 mitestui02.sn.test.net
ServerAdmin support.p240#test.com
SSLProxyEngine On
SSLEngine On
#allow only tls
SSLProtocol -all +TLSv1.2
SSLHonorCipherOrder on
SSLCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384...
SSLCertificateFile /etc/opt/test/iiq/appserver/secure/ssl/web-iiq.crt
SSLCertificateKeyFile /etc/opt/test/iiq/appserver/secure/ssl/apache-iiq.key
Redirect /identityiq/ https://mitestui02.sn.test.net/
Redirect / https://mitestui02.sn.test.net/identityiq/
ProxyRequests Off
ProxyPreserveHost On
ProxyPass /identityiq/ http://localhost:8080/identityiq/
RewriteEngine On
RewriteCond %{REQUEST_METHOD} ^OPTIONS
RewriteRule .* - [F]
<If "%{THE_REQUEST} =~ m#.jsf/?[?\s]#">
Header add X-UI-Source "mitestui02"
Header add X-UA-Compatible "IE=edge"
Header add Referrer-Policy "strict-origin-when-cross-origin"
Header add Feature-Policy "microphone 'none'; geolocation 'none'; usb 'none'; payment 'none'; document-domain 'none'; camera 'none'; display-capture 'none'; ambient-light-sensor 'none'"
Header add Permissions-Policy "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"
Header add Strict-Transport-Security "max-age=63072000; includeSubDomains"
Header add Content-Security-Policy "default-src 'self' 'unsafe-inline' 'unsafe-eval'"
Header add X-Content-Type-Options "nosniff"
Header always edit Set-Cookie (.*) "$1; Secure; SameSite=Strict"
Header onsuccess edit Set-Cookie (.*) "$1; Secure; SameSite=Strict"
</If>
</VirtualHost>
When I connect to the front-end URL, https://mitest.sn.test.net/ I get redirected with a response code 302 and Location header pointing to https://mitestui02.sn.test.net/identityiq/ instead of https://mitest.sn.test.net/identityiq/ .
This doesn't happen when connecting to https://mitest.sn.test.net/identity/ directly.
I have tried with different ProxyPass and ProxyPassReverse directives and also rewriting the Location header, nothing seems to help.
Thanks
So the issue seemed to be related to the Redirect directives.
We removed them and added the following for 443:
RewriteEngine On
RewriteCond %{HTTP:X-Forwarded-Proto} ^http$
RewriteRule ^.*$ https://%{SERVER_NAME}%{REQUEST_URI} [L,R=301,NE]
# Redirect / to /identiyiq
RedirectMatch ^/$ /identityiq
We removed them and added the following for 80:
Redirect permanent / https://mitestui02.sn.test.net/
Now it is working as expected.

How to set apache HTTP to HTTPS redirection on internal proxy?

On my website I have two applications written in Vue. The first is SPA on port 80, the second is Nuxt SSR on port 8080, which is redirected from localhost:8000.
I managed to set HTTPS on both external ports but only on port 80 HTTP traffic is redirected to HTTPS. On port 8080 HTTPS works, but when i try to visit site with http:// it gives me Apache error Bad Requetst:
"Your browser sent a request that this server could not understand.
Reason: You're speaking plain HTTP to an SSL-enabled server port.
Instead use the HTTPS scheme to access this URL, please."
I want all the traffic on port 8080 to be redirected to HTTPS.
My site.conf:
<VirtualHost *:443>
ServerName site.com
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
Protocols h2 http/1.1
SSLEngine On
SSLCertificateFile cert_path...
SSLCertificateKeyFile key_path...
SSLCertificateChainFile cert_path...
...
</VirtualHost>
<VirtualHost *:80>
...
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^/?(.*) https://%{SERVER_NAME}/$1 [R,L]
...
</VirtualHost>
<VirtualHost *:8080>
...
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
Protocols h2 http/1.1
SSLEngine On
SSLCertificateFile cert_path...
SSLCertificateKeyFile key_path...
SSLCertificateChainFile cert_path...
ProxyPreserveHost On
ProxyPass / http://127.0.0.1:8000/
ProxyPassReverse / http://127.0.0.1:8000/
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^/?(.*) https://%{SERVER_NAME}:8080/$1 [R,L]
</VirtualHost>

"Unexpected token H in JSON at position 0" SignalR Core and Kestrel on Apache Web Server

I'm currently running an apache2 webserver on Ubuntu. This web server is hosting a Vue.js web app that connects to a .Net Core app (also on the same linux server) with SignalR to create a websocket.
The .Net Core app is ran using Kestrel. Below is the apache config to reroute the domain.com/api to the .Net Core app running on port 5000.
<VirtualHost *:80>
ServerName example.com
Redirect permanent / https://www.example.com/
</VirtualHost>
<VirtualHost _default_:443>
DocumentRoot /var/www/example.com/dist/spa
ServerName www.example.com
ServerAlias example.com
ProxyPreserveHost On
ProxyPass "/api" http://localhost:5000/
ProxyPassReverse "/api" http://localhost:5000/
RewriteEngine on
RewriteCond %{HTTP:UPGRADE} ^WebSocket$ [NC]
RewriteCond %{HTTP:CONNECTION} Upgrade$ [NC]
RewriteRule /(.*) ws://localhost:5000/$1 [P]
SSLEngine on
SSLProtocol all -SSLv2
SSLCipherSuite ALL:!ADH:!EXPORT:!SSLv2:!RC4+RSA:+HIGH:+MEDIUM:!LOW:!RC4
SSLCertificateFile /etc/apache2/ssl/example_com.crt
SSLCertificateKeyFile /etc/apache2/ssl/server.key
SSLCertificateChainFile /etc/apache2/ssl/example_com.ca-bundle
ErrorLog ${APACHE_LOG_DIR}example-error.log
CustomLog ${APACHE_LOG_DIR}example.log common
</VirtualHost>
When the Vue.js app attempts to create a websocket connection to the example.com/api/mainHub the following error occurs:
Failed to complete negotiation with the server: SyntaxError: Unexpected token H in JSON at position 0
It seems to be sending the request to the correct URL, https://example.com/api/mainHub but fails to establish the connection.
One thing I noticed on the Linux server, is the POST does not include the /api in the path.
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 POST http://example.com//mainHub/negotiate text/plain;charset=UTF-8 0
Is this an issue with how my apache2 config is routing the requests to the /api directory?
Edit
I've since changed my apache config to remove the extra / at the end of the localhost:5000 - This allowed the outside connection to reach the correct API path.
<VirtualHost *:80>
ServerName example.com
Redirect permanent / https://www.example.com/
</VirtualHost>
<VirtualHost _default_:443>
DocumentRoot /var/www/example.com/dist/spa
ServerName www.example.com
ServerAlias example.com
RewriteEngine on
RewriteCond %{HTTP:UPGRADE} ^WebSocket$ [NC]
RewriteRule /(.*) wss://localhost:5000$1 [P]
RewriteCond %{HTTP:CONNECTION} Upgrade$ [NC]
RewriteRule /(.*) https://localhost:5000$1 [P]
ProxyPreserveHost On
ProxyPass "/api" http://localhost:5000
ProxyPassReverse "/api" http://localhost:5000
SSLEngine on
SSLProtocol all -SSLv2
SSLCipherSuite ALL:!ADH:!EXPORT:!SSLv2:!RC4+RSA:+HIGH:+MEDIUM:!LOW:!RC4
SSLCertificateFile /etc/apache2/ssl/example_com.crt
SSLCertificateKeyFile /etc/apache2/ssl/server.key
SSLCertificateChainFile /etc/apache2/ssl/example_com.ca-bundle
ErrorLog ${APACHE_LOG_DIR}example-error.log
CustomLog ${APACHE_LOG_DIR}example.log common
</VirtualHost>
The issue now seems to be with the secure websocket upgrade. I tried changing the ws upgrade to wss, but something is still wrong. When the client attempts to make the wss connection, the following error occurs:
WebSocket connection to 'wss://example.com/api/mainHub?id=k1-_KxspgAQgIAhKroQBpQ' failed: Error during WebSocket handshake: Unexpected response code: 502
I believe something is wrong with how my virtual host is upgrading the websocket connection with HTTPS.
Error: Failed to start the transport 'WebSockets': null
If your .Net Core app is listening at /api/ on port 5000, then add that to ProxyPass directives:
ProxyPass "/api" http://localhost:5000/api
ProxyPassReverse "/api" http://localhost:5000/api
Edit: Try
RewriteEngine on
RewriteCond %{HTTP:UPGRADE} ^WebSocket$ [NC]
RewriteCond %{HTTP:CONNECTION} Upgrade$ [NC]
RewriteRule /(.*) ws://localhost:5000/$1 [P]
ProxyPreserveHost On
ProxyPass "/api" http://localhost:5000
ProxyPassReverse "/api" http://localhost:5000

MQTT over websocket over Apache SSL

I have an mqtt broker providing unencrypted websocket. I would like to proxy it through an Apache which should encrypt the websocket to the outside.
It is an Apache 2.4 on a Windows machine.
My config is:
<VirtualHost *:80>
ServerName test.someurl.com
RewriteEngine On
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule /(.*) ws://localhost:9876/$1 [P,L]
</VirtualHost>
<VirtualHost *:443>
ServerName test.someurl.com
SSLEngine on
SSLCertificateFile "C:/Program Files (x86)/Apache24/conf/ssl/some_certificate.crt"
SSLCertificateKeyFile "C:/Program Files (x86)/Apache24/conf/ssl/some_key.key"
RewriteEngine On
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule /(.*) ws://localhost:9876/$1 [P,L]
# Websocket proxy
# wss redirects to working ws protocol
# ProxyPass /wss ws://127.0.0.1:9876 retry=0 keepalive=On
# ProxyPassReverse /wss ws://127.0.0.1:9876 retry=0
</VirtualHost>
I am able to connect through ws / port 80. It works fine. However, I am not able to connect using the wss.
I tried both using a rewriting and also a proxy_pass directives. I tried 100 different solution. However, this one looked most promising as port 80 is working for ws but not for the encrypted part. Any idea? Or I am just blinded by the options O:)
This is an old question, but as I've just got this working:
I have Mosquitto listening on port 8000 (which is firewalled to block any connections other than from localhost)
listener 8000
socket_domain ipv4
allow_anonymous true
protocol websockets
Then setup apache as so:
<VirtualHost *:80>
ProxyPass /ws/ ws://localhost:8000/
ProxyPassReverse /ws/ ws://localhost:8000/
</VirtualHost>
<VirtualHost *:443>
SSLCertificateFile ...
SSLCertificateChainFile ...
SSLCertificateKeyFile ...
ProxyPass /ws/ ws://localhost:8000/
ProxyPassReverse /ws/ ws://localhost:8000/
</VirtualHost>
Finally, the web-application is set to connect like so:
mqtt.connect((window.location.protocol == "https:" ? "wss:" : "ws:") + "//example.org/ws/");
Note that the proxy protocol can be "ws" or "wss" - both seem to work interchangeably. This is the connection between apache and mosquitto, there's no need to encrypt (they're on the same host). The use of the "/ws/" suffix on the path means I can do without mod_rewrite, and simply use mod_proxy.
This approach is the only way I could require authentication when accessed over HTTPS (which is public) but not over HTTP (which is behind the firewall).

Is it possible to use "%{HTTP_HOST}" in ProxyPass?

I'm trying to implement a transparent proxy using apache2 and mod_proxy that for now - doesn't do anything. just forwards the traffic to the correct "host".
I don't want it to be host-dependant - but dynamic so it'll work for all hosts.
I tried to do this:
RewriteEngine on
RewriteLogLevel 5
RewriteLog "/var/log/apache2/rewrite.log"
RewriteRule ^(.*)$ $1
ProxyPass / http://$1
I also tried several other approaches (none worked).
Is there any way I can access the "host" from the header and use it in the ProxyPass directive?
In nginx I would use $host, $remote_addr, etc.. any way to replace that on apache?
What I need is to be able to access %{HTTP_HOST}, %{REQUEST_URI} and %{SERVER_PORT} inside the ProxyPass command.
To use Apache ProxyPass directives with dynamic hostnames you will need to also use ModRewrite.
Objective
All requests to the virtualhost will ProxyPass and ProxyPassReverse (also known as an "Apache Gateway") to the %{HTTP_HOST}
The only reason this would make sense to do is if you have localhost entries on the apache server for specfic host names
Examples
Localhost File
10.0.0.2 foo.bar.com
10.0.0.3 bar.bar.com
How it works
The client makes a request to foo.bar.com (dnslookup is a public IP... YOUR APACHE SERVER)
Your apache server has a localhost entry of 10.0.0.2 for foo.bar.com (some other server on your network)
The request goes through ModRewrite and /path1 is appended, then handed off to ProxyPass and ProxyPassReverse
ProxyPass and ProxyPassReverse hand the call off to foo.bar.com at ip 10.0.0.2
Client requests foo.bar.com ---reverse proxies to----> foo.bar.com/path1 (on some OTHER internal server)
Apache Configuration
<VirtualHost *:443>
Servername *
# Must not contain /path1 in path (will add /path1)
RewriteEngine on
RewriteCond %{REQUEST_URI} !^/path1/.*
RewriteRule ^/(.*) https://%{HTTP_HOST}/path1$1 [NC,R=302,L]
# Must contain /path1 in path (will send request to the proxy)
RewriteEngine On
RewriteOptions Inherit
RewriteCond %{REQUEST_URI} ^/path1/.*
RewriteRule ^(.*)$ https://%{HTTP_HOST}$1 [NC,P]
SSLEngine on
SSLProxyEngine On
ProxyRequests Off
ProxyPass / https://$1/
ProxyPassReverse / https://$1/
ProxyPreserveHost On
###################
# SSL Constraints #
###################
SSLProtocol -ALL +SSLv3 +TLSv1
# Choose cipher suites
SSLHonorCipherOrder On
SSLCipherSuite ALL:!ADH:RC4+RSA:+HIGH:+MEDIUM:!LOW:!SSLv2:!EXPORT
# SameOrigin The page can only be displayed in a frame on the same origin as the page itself
Header set X-Frame-Options SAMEORIGIN
SSLCertificateFile /etc/apache2/example.crt
SSLCertificateKeyFile /etc/apache2/example.key
SSLCertificateChainFile /etc/apache2/gd_bundle.crt
SetOutputFilter INFLATE;proxy-html;DEFLATE
</VirtualHost>
Just answering my own question:
I was missing 2 things:
the configuration should be:
RewriteEngine On
RewriteRule ^(.*)$ http://%{HTTP_HOST}$1 [P]
Not to forget to enable inherit in the virtual directory:
RewriteEngine On
RewriteOptions Inherit
You should read this page if you haven't done it already :
https://httpd.apache.org/docs/2.2/mod/mod_proxy.html#forwardreverse
I think the ProxyRequests directive is what you are looking for.