Configuration de HAProxy avec routage SNI sur OPNSense

opnsense haproxy reverse-proxy nginx apache2 Réseau sni

Un petit article sur la configuration de HAProxy sur OPNSense permettant de rediriger les flux sur un backend Nginx ou un backend Apache2 suivant l'hôte destinataire ou le SNI.

De manière générale :

  • Une passerelle OPNSense fonctionnelle
  • Deux serveurs web (Apache2 et Nginx) avec un FQDN respectif pointant sur chacun d'eux.
  • Un nom de domaine de portée globale

Dans mon cas, Nginx sert reverse-proxy à l'application Nextcloud et Apache2 est utilisé par LemonLDAP::NG.

Sur la passerelle OPNSense, rendez-vous dans Système > Firmware > Greffons et installer HAProxy

Une fois installé se rendre sur Services > HAProxy > Paramètres.

Voici l'ordre global de configuration pour régler HAProxy :

  1. On créé les serveurs réels qui seront déclarés juste après dans la partie backend
  2. On créé les backendpool dans lesquels seront déclarés donc les serveurs réels
  3. On créé les conditions (basé dans ce cas sur le nom du host si HTTP et le SNI si HTTPS)
  4. On créé une règle pour chaque condition. (Note : une règle peut contenir plusieurs conditions)
  5. On créé les Services Publics (= frontend) HTTP et HTTPS. Par défaut chaque frontend a un backend défini plus haut par défaut. Ce sont les règles associées au frontend qui décideront de la route des requêtes.

Les règles HTTP sont nécessaires car utilisées par Certbot si génération/renouvellement de certificat par challenge HTTP.

Création du serveur réel HTTP :

hap01

Renseignez le nom, l'IP du backend HTTP et le port et cochez le mode avancé :

hap02

Renseignez la dernière case "Option pass-through" (ici pour Nginx) :

hap03

IMPORTANT : "send-proxy-v2" permet d'envoyer du trafic à un serveur de backend en utilisant le protocol PROXY. Il faudra que Nginx soit configuré en conséquence (cf https://blog.raspot.in/fr/blog/utilisation-du-sni-avec-haproxy)

Pour le HTTPS (ici un serveur apache2, celui de LLNG, notez que j'utilise send-proxy tout court car la v2 n'est pas supporté par Apache2. Comme pour Nginx il faudra configurer Apache2 en conséquence) : hap04

Rappel : "send-proxy-v2" est nécessaire pour récupérer la véritable adresse IP dans Nginx et "send-proxy" pour Apache2. Les deux backends devront être alors configuré en conséquence.

On créé les backendpool dans lesquels seront déclarés donc les serveurs réels créés ci-dessus.

hap05

Pour le HTTP, renseigner le nom et le mode (HTTP dans notre cas) puis les Serveurs réels :

hap06

Pour le HTTPS, le mode est TCP (couche 4) et on déclare le serveur réel correspondant :

hap07

On créé les conditions basé sur le nom du host si HTTP et le SNI si HTTPS.

hap08

Dans le cas du HTTP pour le FQDN cloud.domain.tld par exemple :

hap09

Dans le cas du HTTPS pour le FQDN cloud.domain.tld par exemple, vous pourrez constater que le type de condition a changé avec l'utilisation de l'extension SNI :

hap10

On créé une règle pour chaque condition définie juste au dessus.

hap11

Exemple règle : Si la condition App_http_cloud est vérifiée alors on redirige vers NGINX_HTTP_BACKEND :

hap12

On peut utiliser plusieurs conditions dans une seule règle. L'opérateur logique sera alors "OU". Exemple avec les adresses HTTPS menant vers les vhost Apache2 de LemonLDAP::NG :

hap13

Dernière étape, on créé les Services Publics (= frontend) HTTP et HTTPS. Par défaut chaque frontend a un backend par défaut. Ce sont les règles associées au frontend qui décideront de la route des requêtes.

hap14

Renseignez le nom, le socket (adresse d'écoute / Port) et le backend par défaut. Ici j'ai déclaré le socket IP_PUB:80. Toutes les requêtes arrivant sur le port 80 de ma passerelle OPNSense seront alors redirigées vers le backend par défaut si pas de règles associées. Pour les requêtes vérifiant les règles, elles seront soit routées vers le serveur Apache2 soit vers le serveur Nginx.

hap15

hap16

Suivant la complexité de nos routes, on peut facilement se perdre et il peut être très utile de voir la configuration dans son ensemble. Pour cela, connectez-vous sur le shell d'OPNSense puis :

cat /usr/local/etc/haproxy.conf
#
# Automatically generated configuration.
# Do not edit this file manually.
#

global
    uid                         80
    gid                         80
    chroot                      /var/haproxy
    daemon
    stats                       socket /var/run/haproxy.socket group proxy mode 775 level admin
    nbthread                    1
    hard-stop-after             60s
    no strict-limits
    tune.ssl.default-dh-param   2048
    spread-checks               2
    tune.bufsize                16384
    tune.lua.maxmem             0
    log                         /var/run/log local0 info
    lua-prepend-path            /tmp/haproxy/lua/?.lua

defaults
    log     global
    option redispatch -1
    timeout client 30s
    timeout connect 30s
    timeout server 30s
    retries 3
    default-server init-addr last,libc

# autogenerated entries for ACLs

# autogenerated entries for config in backends/frontends

# autogenerated entries for stats

# Frontend: FRONTENDHTTP ()
frontend FRONTENDHTTP
    bind IP_PUB:80 name IP_PUB:80 
    mode http
    option http-keep-alive
    default_backend NGINX_HTTP_BACKEND

    # logging options
    # ACL: App_http_cloud
    acl acl_6551fb1026c015.34599072 hdr(host) -i cloud.domain.tld
    # ACL: auth_sso_http
    acl acl_6554a6da3c9d53.73669408 hdr(host) -i auth.domain.tld
    # ACL: manager_sso_http
    acl acl_6554a6f3f106b9.34099202 hdr(host) -i manager.domain.tld
    # ACL: test1_sso_http
    acl acl_6554be7c469646.51317664 hdr(host) -i test1.domain.tld
    # ACL: test2_sso_http
    acl acl_6554a7289c94f8.60440990 hdr(host) -i test2.domain.tld

    # ACTION: Rule_App_cloud
    use_backend NGINX_HTTP_BACKEND if acl_6551fb1026c015.34599072
    # ACTION: Rule_LLNG_HTTP
    use_backend LLNG_HTTP_BACKEND if acl_6554a6da3c9d53.73669408 || acl_6554a6f3f106b9.34099202 || acl_6554be7c469646.51317664 || acl_6554a7289c94f8.60440990

# Frontend: FRONTEND_HTTPS ()
frontend FRONTEND_HTTPS
    bind IP_PUB:443 name IP_PUB:443 
    mode tcp
    default_backend NGINX_HTTPS_BACKEND

    # logging options
    # ACL: App_https_cloud
    acl acl_65521352282d94.89158556 req.ssl_sni -m sub -i cloud.domain.tld
    # ACL: auth_sso_https
    acl acl_6554bdcb362892.36059122 req.ssl_sni -m sub -i auth.domain.tld
    # ACL: manager_sso_https
    acl acl_6554bde3da2173.51082566 req.ssl_sni -m sub -i manager.domain.tld
    # ACL: test1_sso_https
    acl acl_6554a719bf7d55.62291739 req.ssl_sni -m sub -i test1.domain.tld
    # ACL: test2_sso_https
    acl acl_6554be0d2d02e5.31613271 req.ssl_sni -m sub -i test2.domain.tld

    # ACTION: Rule_App_cloud_HTTPS
    use_backend NGINX_HTTPS_BACKEND if acl_65521352282d94.89158556
    # ACTION: Rule_LLNG_HTTPS
    use_backend LLNG_HTTPS_BACKEND if acl_6554bdcb362892.36059122 || acl_6554bde3da2173.51082566 || acl_6554a719bf7d55.62291739 || acl_6554be0d2d02e5.31613271
    # WARNING: pass through options below this line
    tcp-request inspect-delay 5s
    tcp-request content accept if { req_ssl_hello_type 1 }

# Backend: NGINX_HTTP_BACKEND ()
backend NGINX_HTTP_BACKEND
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    http-reuse safe
    server srv_nginx_http IP_PRIV01:80 send-proxy-v2

# Backend: NGINX_HTTPS_BACKEND ()
backend NGINX_HTTPS_BACKEND
    # health checking is DISABLED
    mode tcp
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    server srv_nginx_https IP_PRIV01:443 send-proxy-v2

# Backend: LLNG_HTTP_BACKEND ()
backend LLNG_HTTP_BACKEND
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    http-reuse safe
    server srv_llng_http IP_PRIV02:80 send-proxy

# Backend: LLNG_HTTPS_BACKEND ()
backend LLNG_HTTPS_BACKEND
    # health checking is DISABLED
    mode tcp
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    server srv_llng_https IP_PRIV02:443 send-proxy

# statistics are DISABLED

Article précédent Article suivant