haproxy two-way ssl

Setting up a CA for HAProxy Two-Way SSL Authentication

In specific scenarios, having a personalized Certificate Authority (CA) is essential, especially when aiming for an optimal two-way SSL authentication setup in HAProxy.

Generate CA Key and Certificate

The journey to setting up a CA starts with its key creation:

openssl genrsa -out ca.key 4096

The subsequent step involves crafting the certificate. For our purposes, we’re choosing a self-signed variant:

openssl req -new -x509 -days 3650 -sha256 -key ca.key -out ca.crt -subj '/CN=root CA/O=Acme Corp'

Configure Your CA

Having secured our key and certificate, it’s time to delve into the CA configuration. Create a ca.conf file, embedding it with the following details:

[ ca ]
default_ca   = acme_ca

[ crl_ext ]
authorityKeyIdentifier=keyid:always

[ acme_ca ]
DIR               = ./      # Default directory.
unique_subject    = no

certificate       = ${DIR}/ca.crt           
database          = ${DIR}/index.txt        
                                            
new_certs_dir     = ${DIR}/                 

private_key       = ${DIR}/ca.key           
serial            = ${DIR}/serial           

default_md        = sha256

name_opt          = ca_default
cert_opt          = ca_default
copy_extensions   = copyall

default_days      = 730           
default_crl_days  = 730           

policy            = acme_policy    

[ acme_policy ]
C=      optional        # Country
ST=     optional        # State or province
L=      optional        # Locality
O=      supplied        # Organization
OU=     optional        # Organizational unit
CN=     supplied        # Common name

An important security note to highlight: Using copy_extensions = copyall can introduce security vulnerabilities, particularly if you sign Certificate Signing Requests (CSR’s) that you haven’t generated yourself. This approach is recommended only if you maintain full control over the CSR’s.

Proceed by initializing the certificate index file:

touch index.txt

Then, set up the serial database:

echo '01' > serial

Concluding the CA configuration, let’s generate the Certificate Revocation List (CRL):

openssl ca -config ca.conf -gencrl -keyfile ca.key -cert ca.crt -out crl.pem

At this point, the list remains empty as no certificates have been revoked. Ensure you relocate the ca.crt and crl.pem files to a location easily accessible by HAProxy, for example:

cp ca.crt /etc/haproxy/certs
cp crl.pem /etc/haproxy/certs

Create HAProxy Key and Certificate

Kick off by generating a private key:

openssl genrsa -out node.key 2048

Follow it up with a CSR:

openssl req -new -key node.key -out node.csr -subj '/CN=node.example.com/O=Acme' -addext 'subjectAltName = DNS:proxy,DNS:proxy.example.com'

In the aforementioned CSR, we’ve designated that HAProxy will operate on a server node.example.com (specified as the CN), while also maintaining a DNS entry proxy.example.com.

Let’s bring our CA into play to sign this certificate:

openssl ca -batch -config ca.conf -notext -in node.csr -out node.crt

For added convenience, package the key and certificate into a PKCS12 key store:

openssl pkcs12 -export -chain -CAfile ca.crt -in node.crt -inkey node.key -passout pass:mypassword > node.p12

Having acquired the HAProxy key and certificate, it’s time to compile a PEM file for HAProxy’s use:

openssl pkcs12 -in node.p12 -passin pass:mypassword -nodes -out haproxy.pem

Shift the PEM file to a directory for future HAProxy access:

cp haproxy.pem /etc/haproxy/certs

It’s important to note that our HAProxy certificate has been endorsed by our homemade CA. While this is vital for enabling two-way SSL authentication, it’s not a strict requirement. You can also opt for a certificate validated by a globally trusted CA. In this demonstration, however, we’re fully embracing our personal CA.

Launching HAProxy

Pen a file /etc/haproxy/haproxy.cfg and populate it with:

global
  log 127.0.0.1 local0 info
  spread-checks 5
  max-spread-checks 15000
  maxconn 50000
  tune.ssl.default-dh-param 2048
  ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:!aNULL:!MD5:!DSS
  ssl-default-bind-options no-sslv3 no-tlsv10 no-tls-tickets
  ssl-default-server-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:!aNULL:!MD5:!DSS
  ssl-default-server-options no-sslv3 no-tlsv10 no-tls-tickets

defaults
  log               global
  retries                   3
  backlog               10000
  maxconn               10000
  timeout connect          3s
  timeout client          30s
  timeout server          30s
  timeout tunnel        3600s
  timeout http-keep-alive  1s
  timeout http-request    15s
  timeout queue           30s
  timeout tarpit          60s
  option            dontlognull
  option            http-server-close
  option            redispatch

frontend http_in
  bind *:80
  mode http
  redirect scheme https code 301

frontend https_in
  bind *:443 ssl crt /etc/certs/haproxy.pem ca-file /etc/certs/ca.crt verify required crl-file /etc/certs/crl.pem
  mode http
  use_backend webdav if { path -i -m beg /shared }
  use_backend prometheus if { path -i -m beg /prometheus }

backend prometheus
  mode http
  option forwardfor
  http-request set-header X-Forwarded-Port %[dst_port]
  http-request add-header X-Forwarded-Proto https if { ssl_fc }
  reqirep  "^([^ :]*) /prometheus/?(.*)" "1 /2"
  server prometheus prometheus:9090

backend webdav
  mode http
  option forwardfor
  http-request set-header X-Forwarded-Port %[dst_port]
  http-request add-header X-Forwarded-Proto https if { ssl_fc }
  server webdav webdav:80

To unleash the power of HAProxy with Docker, execute:

docker run -d -p 80:80 -p 443:443 
    -v /etc/haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg 
    -v /etc/haproxy/certs:/etc/certs 
    haproxy:2.0-alpine

Constructing the Client Certificate

With HAProxy now actively exposing our WebDAV and Prometheus services, a client certificate is paramount for unhindered access.

Begin with the generation of a client key:

openssl genrsa -des3 -passout pass:myclientpw -out client.key 2048

Then, move to CSR creation:

openssl req -new -key client.key -passin pass:myclientpw -out client.csr -subj '/CN=My Client/O=Acme Corp'

Endorse the CSR to yield a certificate:

openssl ca -batch -config ca.conf -notext -in client.csr -out client.crt

For efficient distribution, compile the elements into a PKCS12 key store:

openssl pkcs12 -export -chain -CAfile ca.crt -in client.crt -inkey client.key -passin pass:myclientpw -passout pass:myclientpw > client.p12

Finally, integrate this key store into your browser’s certificate chain, ensuring effortless access to both WebDAV and Prometheus.

Feel free to contact me for any help.

Share this
Send this to a friend