By Pierre-Alain B on Monday, March 12 2018, 08:00
Les versions 1.8.x d'HAProxy (premier représentant de la branche publié fin 2017) supportent le protocole HTTP/2 pour la communication frontale (section frontend). L'utiliser en amont de votre infrastructure est un moyen facile de rendre ce protocole disponible même si certains de vos serveurs sous-jacents (backend) n'en sont pas encore capables.
Installer HAProxy 1.8.x sur Debian Stretch
La version distribuée officiellement dans Debian Stretch est 1.7.x. Pour installer une version de la branche 1.8.x, il y a au moins 2 alternatives :
- compiler l'outil à partir du code source disponible sur le site officiel
- utiliser https://haproxy.debian.net/ qui va fournir un dépôt spécifique
Configurer HAProxy
La configuration d'HAProxy se fait par défaut dans /etc/haproxy/haproxy.cfg.
Voici un exemple de fichier de configuration dont les principales sections sont commentées plus bas :
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin
stats timeout 30s
user haproxy
group haproxy
daemon
# Default SSL material locations
ca-base /etc/ssl/certs
crt-base /etc/ssl/private
# Default ciphers offered by Mozilla here:
# https://mozilla.github.io/server-side-tls/ssl-config-generator
ssl-default-bind-ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256
ssl-default-bind-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
ssl-default-server-ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256
ssl-default-server-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
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 v4v6
acl host_letsencrypt path_beg /.well-known/acme-challenge/
use_backend letsencrypt if host_letsencrypt
redirect scheme https code 301 if !host_letsencrypt
frontend https-in
bind :::443 v4v6 ssl crt-list /etc/haproxy/crt-list.txt alpn h2,http/1.1
option forwardfor
http-request add-header X-Forwarded-Proto https
# Define hosts
acl host_alpha hdr_end(host) -i a.fr
acl host_beta hdr(host) -i precis.b.fr
acl host_gamma hdr_reg(host) ^d1.c.fr|d2.c.fr$
# figure out which one to use
use_backend alpha if host_alpha
use_backend beta if host_beta
use_backend gamma if host_gamma
default_backend iota
backend alpha
server alpha 192.168.1.1:80
backend beta
server beta 192.168.1.2:80
backend gamma
server gamma 192.168.1.3:80
backend iota
server iota 192.168.1.4:80
backend letsencrypt
server letsencrypt 192.168.1.1:8080
Quelques commentaires pour bien comprendre...
On écoute d'abord en HTTP (donc sur le port 80, en IPv4 et IPv6) et on redirige tout vers l'HTTPS (redirect scheme https code 301) car c'est une bonne pratique aujourd'hui de n'offrir que du contenu protégé par SSL/TLS.
frontend http-in
bind :::80 v4v6
redirect scheme https code 301
Si l'on utilise letsencrypt pour générer ses certificats et qu'ils sont tous générés sur une machine dédiée pour ne pas avoir à se poser de questions sur les différents types de serveurs sous-jacents, on peut rajouter une exception pour les appels utilisés dans l'ACME challenge de letsencrypt et rediriger alors le trafic vers une machine spécifique ici nommée letsencrypt.
frontend http-in
bind :::80 v4v6
acl host_letsencrypt path_beg /.well-known/acme-challenge/
use_backend letsencrypt if host_letsencrypt
redirect scheme https code 301 if !host_letsencrypt
On écoute bien sûr également sur le port 443 pour gérer les connexions en SSL/TLS :
frontend https-in
bind :::443 v4v6 ssl crt-list /etc/haproxy/crt-list.txt alpn h2,http/1.1
option forwardfor
http-request add-header X-Forwarded-Proto https
Il y a beaucoup de choses intéressantes ici :
- bind :::443 v4v6 : IPv4 et IPv6 activés
- bind :::443 v4v6 ssl crt-list /etc/haproxy/crt-list.txt : on active le SSL/TLS et on spécifie un fichier texte qui contient la liste des certificats à charger pour chiffrer les communications
- bind :::443 v4v6 ssl crt-list /etc/haproxy/crt-list.txt alpn h2,http/1.1 : on active l'HTTP/2 (en plus de l'HTTP/1)
- option forwardfor : comme on fonctionne en reverse proxy, on demande à HAProxy de passer les en-têtes X-FORWARDED-FOR aux serveurs sous-jacents (utile pour avoir les vraies adresses IP appelantes dans les logs)
- http-request add-header X-Forwarded-Proto https : on informe les serveurs sous-jacents que la communication est bien chiffrée (et on évite donc une boucle de redirection si le serveur sous-jacent veut forcer le passage en HTTPS, avec ce paramètre il sera déjà satisfait)
On définit ensuite quelques règles ACL pour, selon le nom d'hôte, orienter la connexion vers un backend ou autre. J'ai mis ici plusieurs exemples que j'utilise - il existe des dizaines d'autres filtres y compris sur d'autres critères que le nom d'hôte (on a vu l'URL dans le cas de letsencrypt un peu plus) : filtre sur la fin du nom d'hôte (tout nom d'hôte finissant par a.fr sera redirigé vers le backend alpha), filtre sur le nom d'hôte complet (precis.b.fr sera envoyé vers beta) ou filtre sur le nom d'hôte avec des expressions régulières.
# Define hosts
acl host_alpha hdr_end(host) -i a.fr
acl host_beta hdr(host) -i precis.b.fr
acl host_gamma hdr_reg(host) ^d1.c.fr|d2.c.fr$
# figure out which one to use
use_backend alpha if host_alpha
use_backend beta if host_beta
use_backend gamma if host_gamma
On définit également un backend par défaut (iota ici) :
default_backend iota
Et il reste alors à définir chaque backend :
backend example
server example 1.2.3.4:80
Pour aller plus loin
Les options possibles dans HAProxy sont fort évidemment beaucoup plus nombreuses ! La documentation est très détaillée et claire ici https://www.haproxy.org/#docs.