Aller au contenu

Terminaison SSL HAProxy

Terminer TLS à HAProxy — bundles PEM, options ssl-default-bind, ALPN pour HTTP/2, agrafage OCSP, et les vérifications de production avant de basculer.

HAProxy termine TLS en attachant un frontend à un port avec le mot-clé ssl et une référence crt. La mécanique est simple ; la surface est large. Cet article couvre la configuration de production qu’on utilise : disposition du bundle PEM, défauts de cipher sains, ALPN pour HTTP/2, agrafage OCSP, et les étapes de vérification avant de pointer le vrai trafic vers le nouveau listener.

Comment vérifier

Avant la bascule, vérifiez que le listener présente bien le certificat que vous comptez, supporte les protocoles attendus et chaîne correctement.

sudo haproxy -c -f /etc/haproxy/haproxy.cfg
openssl s_client -connect example.com:443 -servername example.com -alpn h2,http/1.1 < /dev/null
openssl s_client -connect example.com:443 -servername example.com -status < /dev/null | grep -i 'ocsp response'
sudo journalctl -u haproxy -n 100 --no-pager | grep -iE 'ssl|crt'

Le drapeau -alpn négocie h2 d’abord, puis http/1.1 ; la réponse montre ce que HAProxy a choisi. -status demande un agrafage OCSP — « OCSP Response Status: successful » confirme que HAProxy agrafe. Si vous voyez « no response sent » avec l’agrafage configuré, le cache OCSP est vide ou le répondeur est inaccessible.

Ce qui se passe

La pile TLS de HAProxy lit les fichiers crt comme des bundles PEM concaténés : certificat, chaîne intermédiaire et clé privée dans un fichier. (Optionnellement, une réponse OCSP dans un fichier voisin .ocsp et un bloc de paramètres DH.) Les options par bind vivent sur la ligne bind ; les défauts qui s’appliquent à tous les binds vivent dans global sous ssl-default-bind-*.

Le flux sur un frontend TLS :

  1. Le client se connecte au port 443.
  2. HAProxy lit le ClientHello, choisit un certificat par SNI (correspondance avec les entrées crt-list ou le fichier dans crt).
  3. La poignée de main TLS se termine ; ALPN négocie h2 ou http/1.1.
  4. HAProxy parse la requête HTTP et applique les ACL / use_backend.

Deux séparations importantes :

  • Par listener vs global : ssl-default-bind-options est le plancher. Un bind par ssl-min-ver TLSv1.3 ne remonte le plancher que pour ce listener-là.
  • Côté front vs côté back : terminer à HAProxy et rechiffrer vers le backend sont deux configs indépendantes. La plupart des clients terminent à HAProxy et transmettent en HTTP sur un VLAN privé ; le rechiffrement est optionnel et se configure dans la ligne server avec ssl verify required ca-file ....

La procédure

  1. Définir des défauts globaux sains. Mettez ceci dans global pour que toute nouvelle ligne bind hérite d’une base sécuritaire :

    global
        ssl-default-bind-ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305
        ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
        ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
        tune.ssl.default-dh-param 2048

    ssl-default-bind-ciphers contrôle les ciphers TLS 1.0-1.2 ; ssl-default-bind-ciphersuites contrôle TLS 1.3. Ce sont deux chaînes distinctes — ne définir que la première laisse TLS 1.3 sur les défauts intégrés.

  2. Construire le bundle PEM. Depuis un Let’s Encrypt ou une CA commerciale, concaténer certificat final, chaîne intermédiaire et clé privée :

    sudo mkdir -p /etc/haproxy/certs
    sudo cat fullchain.pem privkey.pem > /etc/haproxy/certs/example.com.pem
    sudo chmod 640 /etc/haproxy/certs/example.com.pem
    sudo chown root:haproxy /etc/haproxy/certs/example.com.pem

    Mode 640 avec le groupe haproxy rend la clé illisible aux autres utilisateurs. Possession root et lecture seule pour le groupe haproxy : c’est le motif de production.

  3. Attacher le frontend avec TLS. Listener à un seul certificat avec ALPN :

    frontend fe_https
        bind *:443 ssl crt /etc/haproxy/certs/example.com.pem alpn h2,http/1.1
        http-request redirect scheme https code 301 unless { ssl_fc }
        default_backend be_app

    alpn h2,http/1.1 est ce qui débloque HTTP/2 côté front. Sans cela, les navigateurs retombent à HTTP/1.1.

  4. Ajouter HSTS au niveau réponse. Une fois HTTPS opérationnel de bout en bout et que vous vous êtes engagé à rester en HTTPS, ajoutez HSTS — mais pas avant, et pas avec preload avant d’avoir vécu avec max-age au moins un mois :

    frontend fe_https
        bind *:443 ssl crt /etc/haproxy/certs/example.com.pem alpn h2,http/1.1
        http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains"
        default_backend be_app
  5. Plusieurs certificats via crt-list. Quand vous servez plusieurs domaines, utilisez une crt-list afin de pouvoir ajouter des certificats sans modifier la ligne bind :

    cat <<'EOF' | sudo tee /etc/haproxy/certs/crt-list.txt
    /etc/haproxy/certs/example.com.pem example.com www.example.com
    /etc/haproxy/certs/api.example.com.pem api.example.com
    EOF
    frontend fe_https
        bind *:443 ssl crt-list /etc/haproxy/certs/crt-list.txt alpn h2,http/1.1

    Les noms d’hôtes en fin de ligne sont les motifs SNI contre lesquels HAProxy matche ; le CN/SAN du certificat doit également correspondre pour que les navigateurs l’acceptent.

  6. Agrafage OCSP. HAProxy agrafe une réponse OCSP si un fichier .ocsp existe à côté du .pem. Générez-la une fois et rafraîchissez sur minuterie :

    openssl ocsp -issuer chain.pem -cert cert.pem \
      -url $(openssl x509 -in cert.pem -noout -ocsp_uri) \
      -no_nonce -respout /etc/haproxy/certs/example.com.pem.ocsp
    echo "set ssl ocsp-response $(base64 -w0 /etc/haproxy/certs/example.com.pem.ocsp)" \
      | sudo socat /run/haproxy/admin.sock -

    Rafraîchissez la réponse OCSP toutes les 24h via cron ; le chargement par l’API d’exécution rend la nouvelle agrafe vivante sans reload.

  7. Valider de bout en bout. Les labs TLS (qualys, mozilla observatory) et un openssl s_client local confirment la poignée de main. Puis chargez le site dans un vrai navigateur et vérifiez le panneau réseau pour HTTP/2.

Pièges courants

  • Un mauvais ordre de fichier dans le bundle PEM (clé avant cert, intermédiaire manquant) échoue au parse avec un « unable to load certificate » peu utile — openssl x509 -in cert.pem -noout -text confirme que le fichier est parsable comme cert.
  • Oublier alpn h2,http/1.1 signifie que les clients modernes obtiennent HTTP/1.1 même si les deux côtés supportent h2. Testez avec openssl s_client -alpn h2.
  • HSTS avec preload est une porte à sens unique — une fois soumis à la liste preload, vous ne pouvez pas le retirer sans délais navigateur. Ajoutez preload seulement après un mois de HSTS stable.
  • tune.ssl.default-dh-param 2048 ne compte que pour les ciphersuites DH ; sur les lignes bind modernes avec ECDHE-only, les params DH sont inutilisés. Les définir reste inoffensif et compatible vers l’avant.
  • La permission du fichier bundle PEM doit être lisible par l’utilisateur haproxy. Si vous avez copié le fichier en root avec mode 600, HAProxy démarre mais le bind échoue silencieusement à charger le cert et retombe sur un auto-signé par défaut.

Stack Harbor pilote la rotation TLS comme une préoccupation infogérée — Let’s Encrypt ou CA commerciale, renouvellement automatisé, rafraîchissement OCSP, rechargement à chaud via l’API d’exécution, et un rapport mensuel « expire dans 30 jours » qui fait remonter les certificats non auto-renouvelés. Pour les clients multi-régions avec des dizaines de certificats, c’est ce qu’on gère dans le cadre des Environnements multi-régions.