/ tls

Securing services with stunnel

Stunnel is a tool that allows you to integrate TLS transparently to any existing service reachable over TCP. When stunnel listens on a port, it can either receive encrypted traffic and pass it to an unencrypted destination, or it can receive unencrypted traffic and forward that to an encrypted destination.

Use case 1: Providing HTTPS access to an existing server

We want to provide secure access to an existing traditional webserver. This is known as a TLS terminating proxy.

/etc/stunnel/stunnel.conf

ciphers       = EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH
sslVersion    = TLSv1.2
#setuid       = nobody
#setgid       = nogroup

[example]
client        = no
accept        = 443
connect       = localhost:2368
cert          = server.pem
key           = server.key

Be warned, in order to listen on port 443, stunnel will need to run as root.
This is generally not recommended, your best bet is to run stunnel in a container if you really want to listen on a port

Use case 2: Certificate based authentication

Stunnel can be used for access control by requiring clients present a certificate issued by a trusted authority in order to connect. The trusted CA is created and managed with easyrsa (see earlier tutorial

This is useful for adding an extra layer of security to things like management interfaces on a router or for providing a secure proxy to reach internal hosts from the outside

/etc/stunnel/stunnel.conf

[authenticatedServer]
client        = no
accept        = 4443
connect       = 127.0.0.1:9090
cert          = server.pem
key           = server.key
requireCert   = yes
CAFile        = /etc/stunnel/clientCA.pem
verifyChain   = yes 

Setting up CA

It's recommended but not mandatory you do this on a different machine

git clone https://github.com/OpenVPN/easy-rsa 
cd easy-rsa/easyrsa3
# Create PKI
./easyrsa init-pki && ./easyrsa build-ca
# Transfer ca.crt to stunnel server
scp ca.crt root@stunnelServer:/etc/stunnel/clientCA.pem
# Issue client cert and browser bundle
./easyrsa build-client-full testClient
./easyrsa export-p12 testClient

Now import your P12 file (PKCS12 bundle) into your web browser and access your stunnel server. You should be prompted for your certificate.

To be doubly sure it actually requires a cert, try accessing over curl

Use case 3: Stunnel as a Client (DNS over TLS)

Stunnel can acts as a TLS client too. This mean that stunnel listens for plain connections and forwards them to a TLS secured server.

This is quite a slow way to make use of DNS-over-TLS and instead it's recommended to use Unbound. It's only an example.

/etc/stunnel/stunnel.conf

[tlsClient]
client        = yes
accept        = 127.0.0.53:53
connect       = 1.1.1.1:853
verifyChain   = yes
CAfile        = ca-bundle.crt
checkHost     = cloudflare-dns.com

/etc/resolv.conf

options use-vc
nameserver 127.0.0.53

Use case 4: Stunnel as a Client (with pinned certificates)

Normally we verify the identity of our peer by checking it has a certificate with a matching common name and that certificate issuer is trusted.

Let's forget about CAs and simply check the remote certificate matches our local copy of that certificate.

/etc/stunnel/stunnel.conf

[tlsClientPinned]
client          = yes
accept          = 127.0.0.1:8888
connect         = etherarp.net:443
verifyPeer      = yes
CAfile          = etherarp.net.pem

Downloading certificates from servers

getServerCert() {
echo "" | openssl s_client -connect $1:443 -servername $1 | openssl x509 -out $1.crt
}

Use case 5: Stunnel as a client (with cert authentication)

We saw in use-case-2 how stunnel can require clients to provide a certificate in-order to connect. We can make stunnel provide a certificate when connecting as a client. Simply add cert= as an option to the client and include a pem file containing both the cert and key.

[authenticatedServer]
client          = no
accept          = 127.0.4.20:4444
connect         = example.com:80
cert            = server.crt
key             = server.key
requireCert     = yes
CAfile          = client.crt
verifyChain     = yes


[authenticatedClient]
client          = yes
accept          = 127.1.4.20:4444
connect         = 127.0.4.20:4444
verifyChain     = yes
CAfile          = server.crt
cert            = client.crt
key             = client.key