Configurer HAProxy avec LetsEncrypt et plusieurs sous-domaines

Voici une méthode pour voir comment configurer haproxy et letsencrypt et gérer facilement https avec docker.

Configurer HAProxy avec LetsEncrypt et plusieurs sous-domaines

Nous avons un serveur qui héberge plusieurs services liés à des sous-domaines différents et qui doivent tous avoir un certificat.

En prérequis, les entrées DNS des sous-domaines pointent toutes sur le même serveur.

Sur un serveur qui héberge plusieurs services en https sur des sous-domaines différents, voici une méthode (parmi d'autres) pour configurer HAProxy avec des certificats LetsEncrypt qui se renouvellent automatiquement.

  • Reprendre le fichier /etc/haproxy/haproxy.cfg ci-dessous et compléter avec votre domaine et services :
global
	log /dev/log	local0
	log /dev/log	local1 notice
	chroot /var/lib/haproxy
	stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
	stats timeout 30s
	user haproxy
	group haproxy
	daemon

	# Default SSL material locations
	ca-base /etc/ssl/certs
	crt-base /etc/ssl/private
	tune.ssl.default-dh-param 2048 

	# Default ciphers to use on SSL-enabled listening sockets.
	# For more information, see ciphers(1SSL). This list is from:
	#  https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/
	# An alternative list with additional directives can be obtained from
	#  https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=haproxy
	ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS
	ssl-default-bind-options no-sslv3

defaults
	log	global
	mode	http
	option	httplog
	option	dontlognull
        timeout connect 5000
        timeout client  50000
        timeout server  50000
	errorfile 400 /etc/haproxy/errors/400.http
	errorfile 403 /etc/haproxy/errors/403.http
	errorfile 408 /etc/haproxy/errors/408.http
	errorfile 500 /etc/haproxy/errors/500.http
	errorfile 502 /etc/haproxy/errors/502.http
	errorfile 503 /etc/haproxy/errors/503.http
	errorfile 504 /etc/haproxy/errors/504.http

frontend http-in
	bind *:80

	acl http ssl_fc,not
	http-request redirect scheme https if http

frontend https-in
	bind *:443 ssl crt-list /etc/haproxy/certs/domains_list.txt

	option forwardfor
	option forwardfor header X-Real-IP
	http-request add-header X-Real-IP %[src]
	reqadd X-Forwarded-Proto:\ https

	acl letsencrypt-acl path_beg /.well-known/acme-challenge/
	use_backend letsencrypt-back if letsencrypt-acl

	acl host_dom1 hdr_end(host) -i subd1.mydomain.com
	acl host_dom2 hdr_end(host) -i subd2.mydomain.com

	use_backend dom1-back if host_dom1
	use_backend dom2-back if host_dom2

	default_backend dom1-back

backend dom1-back
	redirect scheme https if !{ ssl_fc }
	server dom1 127.0.0.1:8080 check

backend dom2-back
	redirect scheme https if ! { ssl_fc }
	server dom2 127.0.0.1:9980 check

backend letsencrypt-back
	server letsencrypt 127.0.0.1:8888
  • Pour créer un certificat la première fois, exécutez la commande :
certbot certonly --standalone -d subd2.mydomain.com --email me@email.com --agree-tos --non-interactive --http-01-port=8888
  • Concaténez les fichiers de certificats en un seul fichier pour haproxy :
cat /etc/letsencrypt/live/subd2.domain.com/fullchain.pem /etc/letsencrypt/live/subd2.domain.com/privkey.pem | tee /etc/haproxy/certs/subd2.domain.com.pem
  • Créez ou complétez le fichier /etc/haproxy/certs/domains_list.txt :
/etc/haproxy/certs/subd1.domain.com.pem subd1.domain.com
/etc/haproxy/certs/subd2.domain.com.pem subd2.domain.com
  • Redémarrez haproxy pour prendre en compte les ajouts sudo haproxy reload
  • Pour le renouvellement, copiez le script ci-dessous dans /opt/certif-renewal.sh et ajoutez le en crontab :
#!/bin/sh

certbot renew --force-renewal --tls-sni-01-port=8888

cat /etc/letsencrypt/live/subd1.domain.com/fullchain.pem /etc/letsencrypt/live/subd1.domain.com/privkey.pem | tee /etc/haproxy/certs/subd1.domain.com.pem
cat /etc/letsencrypt/live/subd2.domain.com/fullchain.pem /etc/letsencrypt/live/subd2.domain.com/privkey.pem | tee /etc/haproxy/certs/subd2.domain.com.pem

service haproxy reload

echo `date +'%F %T'` " ---- Certificat renewal done ----"

Et voilà...

Explications

HAProxy est un load balancer très performant et simple de mise en oeuvre.

Dans ce mode de fonctionnement, les appels vers le serveur et les sous-domaines sont forcés en https par haproxy et c'est lui qui gère les certificats. Les appels aux services backend sont alors fait en http. Cela facilite leur mise en oeuvre car vous évite de gérer des certificats, surtout quand ces services sont des containers docker.

Le problème général est que haproxy écoute sur le port 80 et 443 de votre serveur et que certbot a besoin de ces ports pour créer les certificats ou les renouveler. Vous pourriez stopper haproxy le temps de faire le renouvellement mais ici nous préférons utiliser certbot sur un port dédié (8888 par exemple) et donner l'instruction à HAProxy de l'utiliser quand il détecte l'appel à l'URL de LetsEncrypt.

Du coup, pour les commandes certbot, il faut ajouter l'attribut --http-01-port=8888 pour préciser sur quel port se connecter. Celui-ci correspond à ce que vous paramétrez dans la configuration du backend de HAProxy.

Dans la section global, l'instruction tune.ssl.default-dh-param 2048 augmente la sécurité lors de l'échange de clés (1024 par défaut).