Haproxy

L'ami Franckpaul m'avait donné l'envie d'utiliser un proxy pour rendre accessible mes serveurs locaux depuis les internets. Comme d'habitude je suis partie de son exemple pour le transformer en usine à gaz.

Je vais essayer de poser les bases de manière simple, ce n'est pas gagné !
Tout d'abord petit rappel de ce qu'est haproxy: C'est un logiciel open source qui gère le load balancing et le reverse proxy. Pour ma part je n'utilise que la partie reverse proxy. Depuis une seule adresse IP je vais redistribuer des noms de domaines vers mes serveurs internes. Enfin, deux adresses IP, une IPv4 et une IPv6.
J'ai au préalable configurer chez mon hébergeur (qui n'héberge plus rien) plusieurs noms de domaine ou sous-domaine, soit en DynHost pour les Ipv4, soit en champs AAAA pour les IPv6, avec en plus un champs CAA pour Let's Encrypt:

chez IN CAA 128 issue "letsencrypt.org"
chez IN AAAA xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx

Ensuite mon routeur-firewall redirige les ports 80 et 443 vers mon serveur haproxy. En réalité c'est un peu plus compliqué que cela, aillant viré la Livebox au profit d'une VM sous Mikrotik Cloud Hosted Router qui sert de passerelle à la fois vers quelques VM et vers mon routeur domotique qui fait lui aussi passerelle vers quelques VM ! Bref, je m'y retrouve c'est l'important. Je parle de ça car par exemple pour la VM OpenVpn les port 80 et 443 passe par la VM haproxy mais les ports 993 et 1194 passe par le routeur domotique. Pour faire simple tout ce qui est port 80 et 443 est dirigé vers haproxy, peut-importe le nom de domaine devant.

;;; exemple simpliste en Nat qui permet de tester rapidement

;;; vers haproxy en http
/ip firewall nat chain=dstnat action=dst-nat to-addresses=10.1.5.111 to-ports=80 protocol=tcp in-interface=WAN dst-port=80 log=no log-prefix=""
;;; vers haproxy en https
/ip firewall nat chain=dstnat action=dst-nat to-addresses=10.1.5.111 to-ports=443 protocol=tcp in-interface=WAN dst-port=443 log=no log-prefix=""
;;; vers openvpn
/ip firewall nat chain=dstnat action=dst-nat to-addresses=10.1.10.111 to-ports=1194 protocol=udp in-interface=WAN dst-port=1194 log=no log-prefix=""

Ensuite haproxy gère donc la distribution des sites web en fonction du nom de domaine appelé. Il gère aussi tout les certificats SSL de ces machines, c'est très pratique et ça simplifie la création de Virtual Host ensuite. J'ai dit tous les certificat SSL ? Non. Tous sauf un, celui de OpenVpn qui est géré par la VM OpenVpn elle même car elle l'enregistre en dur dans sa base de donnée… C'est là que tout se complique dans la configuration de haproxy: Savoir gérer les certificats SSL de certains sites mais d'en laisser d'autres être gérés par leurs VM.
Pour les certificats utilisés par haproxy je suis passé par Cerbot, Let's Encrypt et un petit script Bash de renouvellement et compactage.
Je ne vais pas expliquer comment installer une VM, haproxy, configurer un nom de domaine, etc, ce n'est pas le sujet ici qui est uniquement de me faire un mémo sur le fichier de configuration /etc/haproxy/haproxy.cfg. Let's go !

# Configuration valable pour haproxy 2.2.x

# Section global
global
        # Configuration des logs, de l'utilisateur system, des statistiques.
        log /dev/log    local0
        log /dev/log    local1 notice
        chroot /var/lib/haproxy
        stats socket :9999 level admin expose-fd listeners
        stats timeout 30s
        user haproxy
        group haproxy
        daemon

        # Configuration des niveaux de certificats acceptés pour SSL.
        # Ces niveaux sont ceux de la certification A+ à juillet 2022.
        ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA>
        ssl-default-bind-options no-sslv3 no-tlsv10 no-tlsv11 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:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-R>
        ssl-default-server-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets

# Section defaults
defaults
        # Par default on travail en layer 7 (http)
        mode    http
        option  httplog
        log     global
        option  dontlognull

        # Par default on transmet l'IP cliente
        option forwardfor

        # On configure divers options de timeout et d'erreurs
        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

# Section stats
# On peut complètement enlevé les stats ce n'est pas nécessaire au fonctionnement du proxy.
# On écoute sur http://mon_ip:22222/haproxy?stats
listen stats
        mode http
        bind :22222
        stats enable
        stats uri            /haproxy?stats
        stats realm          Haproxy\ Statistics
        stats auth           un_utilisatuer:un_mot_de_passe
        stats refresh        30s

# Frontend HTTP
frontend frontend_http
        # On écoute sur le port 80 en IPv4 et IPv6
        bind :::80 v4v6

        # On regarde si c'est une vérification pour un renouvèlement de certificat dédié à OpenVpn
        acl acl_domain_openvpn hdr_end(host) -i openvpn.mondomaine.fr

        # On regarde si c'est une vérification pour un renouvèlement de certificat haproxy
        acl acl_letsencrypt_ha path_beg /.well-known/acme-challenge/

        # On utilise le backend spécial OpenVpn
        use_backend backend_openvpn_http if acl_domain_openvpn

        # On utilise le backend spécial Let's Encrypt
        use_backend backend_letsencrypt_ha if acl_letsencrypt_ha !acl_domain_openvpn

        # On redirige tout les reste vers HTTPS
        redirect scheme https code 301 if !acl_letsencrypt_ha !acl_domain_openvpn

# Frontend HTTPS
frontend frontend_https_all
        # On écoute sur le port 443 en IPv4 et IPv6
        bind    :::443 v4v6

        # On travail en layer 4 (tcp) pour outrepasser les certificats non géré par haproxy
        mode    tcp
        tcp-request     inspect-delay 5s
        tcp-request     content accept if { req_ssl_hello_type 1 }

        # On utilise un bakcend pour OpenVpn
        use_backend     backend_proxy_https_openvpn if { req_ssl_sni -i openvpn.mondomaine.fr }

        # Sinon on utilise un backend pour haproxy
        default_backend backend_proxy_https_ha

# Backend proxy pour OpenVpn en HTTPS
backend backend_proxy_https_openvpn
        mode    tcp

        # On utilise haproxy lui-même comme serveur
        server  loopback-for-tls abns@haproxy_openvpn send-proxy-v2

# Backend proxy pour tout le reste en HTTPS
backend backend_proxy_https_ha
        mode    tcp

        # On utilise haproxy lui-même comme serveur
        server  loopback-for-tls abns@haproxy_ha send-proxy-v2

# Frontend proxy OpenVpn en HTTPS
frontend frontend_https_openvpn
        # On dit a haproxy que ça vient de lui-même
        bind    abns@haproxy_openvpn accept-proxy
        mode    tcp
        option  tcplog
        log     global
        tcp-request inspect-delay 5s
        tcp-request content accept if { req.ssl_hello_type 1 }

        # On redirige vers le backend OpenVpn sans vérifier de certificat
        use_backend backend_openvpn_https if { req.ssl_sni -i openvpn.mondomaine.fr }

frontend frontend_https_others
	# On écoute en provenence du proxy et on utilise les certificat haproxy
        bind    abns@haproxy_ha accept-proxy ssl crt /etc/haproxy/certs/ alpn h2,http/1.1

        # On nettoie et ajoute quelques entête à signaler à nos serveurs web
        http-request del-header X-Forwarded-For
        http-request add-header X-Forwarded-Proto https
        http-response set-header Strict-Transport-Security "max-age=16000000; includeSubDomains; preload;"

        # Petite astuce pour CalDav de nextcloud
        acl     acl_nextcloud_url_discovery path /.well-known/caldav /.well-known/carddav
        http-request redirect location /remote.php/dav/ code 301 if acl_nextcloud_url_discovery

        # On distribue nos noms de domaines
        use_backend backend_openvpn_https	if { hdr_end(host) -i openvpn.mondomaine.fr }
        use_backend backend_a_mondomaine_fr	if { hdr_end(host) -i a.mondomaine.fr }
        use_backend backend_b_mondomaine_fr	if { hdr_end(host) -i b.mondomaine.fr }
        use_backend backend_mondomaine_com	if { hdr_end(host) -i z.mondomaine.com }
        use_backend backend_multidom		if { hdr_reg(host) ^xyz.tld|a.xyz.tld|c.mondomaine.fr$ }

        default_backend backend_mondomain_com

# Backends
# Pour les domaines certifiés par haproxy on utilise que le port 80

# server a
backend backend_a_mondomaine_fr
    description Blablabla a.domaine.fr
    server server_a_domaine_fr 10.1.5.120:80 check

# server b
backend backend_b_mondomaine_fr
    description Blablabla b.domaine.fr
    server server_b_domaine_fr 10.1.5.121:80 check

# server c
backend backend_mondomaine_com
    description Blablabla domaine principal
    server server_b_domaine_fr 10.1.5.122:80 check

# server d
backend backend_multidom
    description Multi domaines
    server server_multidom 10.1.5.123:80 check

# Cas particuliers

# renouvelement letsencrypt certbot
backend backend_letsencrypt
        description Cerbot renew
	# On utilise le server certbot qu'on a crée sur le même serveur que haproxy
        server server_letsencrypt 127.0.0.1:8888

# renouvelement letsencrypt openvpn sur sa machine dédié
backend backend_openvpn_http
        description Openvpn certbot renew
        server server_openvpn_http 10.1.10.222

# server openvpn (web GUI) qui gère son propre certificat SSL
backend backend_openvpn_https
        description Openvpn web UI
        mode tcp
        option ssl-hello-chk
        server server_openvpn_https 10.1.10.222:443

C'est une version très simplifié de mon fichier de configuration il y a surement quelques erreurs que vous ne manquerez pas de me signaler. Et j'essayerais de mettre à jour en fonction de mes avancement en connaissance sur haproxy.

Ajouter un commentaire

Les commentaires peuvent être formatés en utilisant une syntaxe wiki simplifiée.

Ajouter un rétrolien

URL de rétrolien : https://chez.jcdenis.fr/trackback/207

Haut de page