Socat, sslh and stunnel to share https port 443

The instructions below assume you are using Ubuntu 16.04 but they will work for other Linuxes with minor modifications. The instructions below will also conflict with a webserver listening on port 443 ( https ) so you’ll need to disable it. Once the setup below is complete your https connections will get seamlessly forwarded to port 80.

Setup letsencrypt keys

For the SSL connection to be secure and trusted by browsers and other software you need to have a certificate signed by a recognised certificate authority. The easiest way to do this is to use letsencypt’s certbot. I’m not going to go into how to get the certificate as there are too many ways depending on your configuration. Just follow letsencrypt’s documentation to generate a key for your "servernname.com" that will get used in the rest of these instructions.

You could also use a self signed key but that may cause you problems with stateful firewalls.

Setup stunnel

With your certificate installed on the server you can now setup stunnel to use it. crow shows a partial setup here. I think he’s limited the ciphers for increased security but I found it was not necessary.

Install stunnel4

sudo apt-get install stunnel4

So the setup in /etc/stunnel/stunnel.conf I am using looks like this

pid = /var/run/stunnel.pid
cert = /etc/letsencrypt/live/servername.com/fullchain.pem
key = /etc/letsencrypt/live/servername.com/privkey.pem
[ssh]
accept = servername.com:443
connect = localhost:80

You also need to enable stunnel in /etc/default/stunnel4.conf by setting ENABLED=1

restart stunnel to use the new configuration.

systemctl restart stunnel4

At this point you can test the stunnel setup by going to “http://servername.com” with your browser and you will have a secure connection to your http server.

To prep for the sslh configuration change

connect = localhost:80

in /etc/stunnel/stunnel.conf to

connect = localhost:1022

and then restart stunnel again.

Setup sslh

sslh will redirect the sessions decrypted by stunnel to the correct port on your server.

You need to install sslh

sudo apt-get install sslh

The minimum services I wanted are ssh and http so my configuration in /etc/default/sslh looks like this.

RUN=yes

# binary to use: forked (sslh) or single-thread (sslh-select) version
DAEMON=/usr/sbin/sslh

DAEMON_OPTS="--user sslh --listen 127.0.0.1:1022 --http 127.0.0.1:80 --ssh 127.0.0.1:22 --pidfile /var/run/sslh/sslh.pid"

The sslh documentation says that OpenVPN, tinc, XMPP are also supported but I didn’t need those so my configuration doesn’t support them. You can now restart sslh

sudo systemctl restart sslh

This would be another good time to test the stunnel -> sslh -> httpd redirection by visiting “http://servername.com” in your browser.

Client side ssh setup

Once all of the above is complete and assuming that you have an ssh server that you can connect to on port 22 of your server the ssh client can be setup to use the ssl tunnel. The ssh session needs to wrapped in the ssl session to be able to connect to the server so I used the ssh ProxyCommand to accomplish this. Add the section below to your ~/.ssh/config on your client machine

Host servername.com
ProxyCommand /usr/bin/socat - OPENSSL:servername.com:443

From the client you should now be able to connect to your server by doing

ssh servername.com

If you get errors from ProxyCommand about your keys or if you used a self signed certificate you will need to turn off key verification.

Host servername.com
ProxyCommand /usr/bin/socat - OPENSSL:servername.com:443,verify=0

There is usually one other modification I have in my ssh config and that is a DynamicProxy so that stateful packet inspection doesn’t interfere. So the final configuration looks like this.

Host servername.com
DynamicForward localhost:2121
ProxyCommand /usr/bin/socat - OPENSSL:servername.com:443

The interested reader should look into FoxyProxy to see how this might be used.

Using letsencrypt with stunnel

I wanted to use letsencrypt keys and stunnel to encrypt sessions with a valid server key. Once setup the system needed to look like a regular https website with a valid certificate. I’ll explain why I did this in a later posting.

I’m not going to go into getting the original key from letsencrypt as there are too many ways to do it and letsencrypt’s certbot is already well documented.

These instructions are also specific to Ubuntu 16.04 but could be modified for other Linux’s. The instructions below will also conflict with any webserver listening on port 443 (https).

Setup stunnel

In all of the instructions and scripts below replace <servername> with your hostname. <servername> also needs to match you letsencrypt hostname.

sudo apt-get install stunnel4

Edit /etc/default/stunnel4 and change ENABLED=1

Now create a new stunnel conf file in /etc/stunnel/ with the contents below

pid = /var/run/stunnel.pid
cert = /etc/stunnel/stunnel.pem
[ssh]
accept = <servername>:443
connect = 127.0.0.1:80

If you want to connect to something other than you local webserver change the “connect = 127.0.0.1:80” line above.

Now because stunnel needs the fullchain.pem and the privkey.pem in the same file we need to combine the letsencrypt files. Here’s a script ( combine_certs.sh ) that will check the md5sums of the letsencrypt file and generate a new stunnel.pem whenever the originals change.

#!/bin/bash
#
# Copyright (c) 2017 Angus Ainslie 
#

IN_PATH="/etc/letsencrypt/live"
CERT_NAME=$1
OUT_PATH=$2
PEM_NAME=$3

CHAIN_SUM=`md5sum ${IN_PATH}/${CERT_NAME}/fullchain.pem`
KEY_SUM=`md5sum ${IN_PATH}/${CERT_NAME}/privkey.pem`

echo "Chain sum ${CHAIN_SUM}"
echo "Key sum ${KEY_SUM}"

if [ ! -e ${OUT_PATH}/sums ]; then
  echo ${CHAIN_SUM} > ${OUT_PATH}/sums
  echo ${KEY_SUM} >> ${OUT_PATH}/sums
fi

md5sum --status -c ${OUT_PATH}/sums

if [ $? -eq 0 ]; then
  echo "Keys match"
else
  echo "Keys don't match. re-creating pem file"
  cat ${IN_PATH}/${CERT_NAME}/fullchain.pem ${IN_PATH}/${CERT_NAME}/privkey.pem > ${OUT_PATH}/${PEM_NAME}
  echo ${CHAIN_SUM} > ${OUT_PATH}/sums
  echo ${KEY_SUM} >> ${OUT_PATH}/sums
fi

To generate the stunnel.pem file run combine_certs.sh like this

sudo combine_certs.sh <servername> /etc/stunnel stunnel.pem

Because the letsencrypt certificates are short lived their install process adds a cron job that will renew any keys expiring in 30 days or less. So we need to rerun the combine script to keep our stunnel.pem current. Put this crontab in /etc/cron.d

# Copyright (c) 2017 Angus Ainslie <angus at akkea.ca>
#
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

5 */12 * * * root test -x /usr/local/bin/combine_certs.sh && combine_certs.sh  /etc/stunnel stunnel.pem

Now restart stunnel

systemctl restart stunnel4

You will now have a fully functional stunnel listener that will function as an https server.