Using NGINX to Wrap Servatrice Websockets in SSL - Cockatrice/Cockatrice GitHub Wiki
Background
Since #3545 was merged, Servatrice now allows users to connect using websockets instead of raw TCP streams. We can take advantage of this to allow clients to leverage SSL to protect sensitive information sent to the server, such as their password.
Important Notes
- In order to prevent ban evasion, it's recommended that you use a copy of Servatrice compiled with QT 5.6 or newer. Older versions of QT don't provide some features used by Servatrice to determine the real IP of proxied users.
- If you want to support non-ssl websockets, it is important to not allow unproxied connections, since a user could spoof their IP by sending an
X-Real-IP
header. - Please don't use the below configs as-is. There are parts which NEED to be changed to match your specific system.
Configuring Servatrice
In order for us to accept websocket connections, we have to configure Servatrice to accept them:
[server]
; Servatrice can listen for clients on websockets, too. Unfortunately it can't support more than one thread.
; Set to 0 to disable the websocket server.
websocket_number_pools=1
; The TCP port number servatrice will listen on for websocket clients; default is 4748
websocket_port=4748
Normally, this is enough to allow a websocket connection. However, since we will be putting nginx between Servatrice and the internet, all of the IP addresses saved to the database for WS clients will be the IP of nginx instead of the IP of the users. This is bad, since it allows users to circumvent IP bans by connecting over a websocket.
To fix this and get the correct IP for each user, we have to read an HTTP header set by nginx, called X-Real-IP
:
; The header to check for the client's actual IP address
web_socket_ip_header="X-Real-IP"
Configuring NGINX
Now that we have Servatrice set up to accept websocket connections, we have to tell nginx how to pass them along securely.
NOTE: This section of the guide does not cover how to set safe SSL parameters. If you are running your own server, it is assumed you already know how to do this. The configuration in this section is only as safe as your SSL settings!
NOTE: This section assumes you already have an SSL certificate. If you don't, get one now.
upstream
Directive
The First, we need to tell nginx how to find your cockatrice server. This is pretty easy, since nginx provides a handy upstream
directive:
upstream servatrice {
server 127.0.0.1:4748;
}
This tells nginx that it can find Servatrice at 127.0.0.1 (localhost) on port 4748 (the default websocket port).
server
Directive
The Now, we need to tell nginx where to listen for incoming connections. This is done using the server
directive. If you want to use an existing server block, skip ahead to the next section.
server {
# Our server needs to accept connections using ssl on port 443,
# so we using the listen directive to declare that:
listen 443 ssl;
# Here, we specify the hostname of this server. Yours WILL be different
server_name mtg.tetrarch.co;
# This turns on SSL support:
ssl on;
# Here, we declare the locations of our SSL certificates. Yours WILL be different
ssl_certificate /etc/letsencrypt/live/mtg.tetrarch.co/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mtg.tetrarch.co/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/mtg.tetrarch.co/fullchain.pem;
}
Great! Restart nginx and make sure you can connect using a web browser. If you can, you're ready to move to the next section.
location
Directive
The Now that you have a server block configured to accept secure connections, we need to actually forward those connections to Servatrice. To do this, we create a location
inside our server block and use proxy_pass
to actually forward the traffic.
An important thing to note is that when a Cockatrice client connects to a server via a websocket, it always connects to your.hostname.here:port/servatrice
. Note the /servatrice
at the end! This is to enable you to have a webpage on the same domain as your Servatrice, without requiring users to enter even more server info than they already do.
Since we are proxying a websocket, in addition to sending the regular websocket data, we also need to pass along a couple headers, and explicitly specify the HTTP version we use. Thankfully, nginx's proxy module provides us with easy ways to do all of that:
- To specify the HTTP version we use to talk with Servatrice, we use the
proxy_http_version
directive. We choose version1.1
, since it supports keepalive connections. Currently, HTTP/2 is not supported by this module. - To tell Servatrice that the client wants to use a websocket connection (as opposed to a stray browser connection), we have to explicitly pass through 2 headers:
Upgrade
andConnection
. We use theproxy_set_header
directive to do this. - Next, we need to tell Servatrice the real IP of the client. We again use
proxy_set_header
, this time to setX-Real-IP
to$remote_addr
, a variable that contains the client's IP. - Lastly, we need to actually pass along the connection to Servatrice. To do this, we use
proxy_pass
with the name of ourupstream
from before.
Put together, we should have a location that looks like this:
location /servatrice {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://servatrice;
}
The Full Config
Please don't just copy/paste this! There is important info in the above steps on how and what to change!
upstream servatrice {
server 127.0.0.1:4748;
}
server {
listen 443 ssl;
server_name mtg.tetrarch.co;
ssl on;
ssl_certificate /etc/letsencrypt/live/mtg.tetrarch.co/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mtg.tetrarch.co/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/mtg.tetrarch.co/fullchain.pem;
location /servatrice {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://servatrice;
}
}