Protecting services with client certificates using Haproxy

What we want to achieve #

We want to be able to connect to services inside a private network using client certificates, in this example we will be connecting to Redis.

haproxy-client-certs-overview.png

Install or compile Haproxy #

I am using Debian, so this is what I use to compile Haproxy for testing out this setup.

apt-get -y install make gcc g++ libssl-dev

wget http://www.haproxy.org/download/1.8/src/haproxy-1.8.4.tar.gz
tar xzf haproxy-1.8.4.tar.gz
cd haproxy-1.8.4
make TARGET=generic USE_OPENSSL=1
make install PREFIX=/usr/local

Install and configure firewall #

I am testing with a Redis-server, but it can be anything. First firewall off everything except for port 22 (ssh) and port 88 (our external redis port):

apt-get -y install ufw

ufw default deny incoming
ufw default allow outgoing
ufw allow 22
ufw allow 88

Install Redis #

apt-get -y install redis-server

Since Debian and Debian-related distros have the terrible idea that software need to run by default, you should now have a running redis-server on port 6379.

Generate certificates #

This is just for testing, please use some better tooling like easy-rsa to manage your certificates:

# MASTER CERTS:
openssl genrsa -nodes -out ca.key 4096
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt # with cname=haproxytest.kaspergrubbe.com
cat ca.crt ca.key > ca.pem

# CLIENT CERTS:
openssl genrsa -out client.key 4096
openssl req -new -key client.key -out client.csr # FQDN=client.kaspergrubbe.com
openssl x509 -req -days 3650 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt
cat client.crt client.key > client.pem

Setup Haproxy server #

This let Haproxy listen on port 88, and it will verify the connecting certificates, and if the authentication is succesful, it will forward the traffic to redis on 127.0.0.1:6379.

Create a file named: haproxy_server.cfg:

global
  pidfile /var/run/haproxy.pid
  maxconn 4000
  tune.ssl.default-dh-param 2048

listen redis
  bind 0.0.0.0:88 ssl crt /root/ca.pem ca-file /root/ca.crt verify required
  mode tcp
  server redis-1 127.0.0.1:6379 maxconn 1024

Run the config in a non-daemonized way:
haproxy -f haproxy_server.cfg.

Connecting to Redis through another Haproxy #

Create a file named: haproxy_client.cfg:

global
  pidfile haproxy.pid
  maxconn 4000

listen redis
  bind 0.0.0.0:7945
    mode tcp
  server redis-serv01 haproxytest.kaspergrubbe.com:88 ssl crt client.pem ca-file ca.crt

Run the config in a non-daemonized way:
haproxy -f haproxy_client.cfg.

Now you should be able to connect to Redis from your terminal:

$ redis-cli -h localhost -p 7945
localhost:7945> ping
PONG

Connect to Redis through Ruby #

If you do not want to install a Haproxy instance just to manage the client certificates, you can connect directly from your favorite programming language, here is an example on how to do it in Ruby:

require 'socket'
require 'openssl'

sock = TCPSocket.new('haproxytest.kaspergrubbe.com', 88)

ctx = OpenSSL::SSL::SSLContext.new
ctx.cert = OpenSSL::X509::Certificate.new(File.read('/root/client.pem'))
ctx.key  = OpenSSL::PKey::RSA.new(File.read('/root/client.key'))
# ctx.key = OpenSSL::PKey::RSA.new(File.read('client.key'), 'secret_password')

@socket = OpenSSL::SSL::SSLSocket.new(sock, ctx).tap do |socket|
  socket.sync_close = true
  socket.connect
  socket.write "PING\n"
  socket.each_line do |line|
    puts line
  end
end

Todo: #

Consider using CRL-files for making your life easier, CRL stands for Client Revocation List, and it is a list of certificates that you no longer trust for one reason or another, so if a server gets hacked, or an employee laptop goes missing, you can revoke the certificate and sleep nicely afterwards knowing that they can’t use the certificate for anything.

When you have a crl-file, It is as easy as adding crl-file <crlfile> to the server configuration.

There is a nice article about crls here: https://jamielinux.com/docs/openssl-certificate-authority/certificate-revocation-lists.html

 
4
Kudos
 
4
Kudos

Now read this

Enforcing host parameter usage for URLs in e-mail templates with Hamlcop

At Billetto we have multiple domains for our ticketing website depending on the market, as an example our danish organisation lives at billetto.dk and our swedish counterpart lives at billetto.se. All of our organisations shares the same... Continue →