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 :
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 :
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 :
Renseignez le nom, l'IP du backend HTTP et le port et cochez le mode avancé :
Renseignez la dernière case "Option pass-through" (ici pour Nginx) :
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) :
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.
Pour le HTTP, renseigner le nom et le mode (HTTP dans notre cas) puis les Serveurs réels :
Pour le HTTPS, le mode est TCP (couche 4) et on déclare le serveur réel correspondant :
On créé les conditions basé sur le nom du host si HTTP et le SNI si HTTPS.
Dans le cas du HTTP pour le FQDN cloud.domain.tld par exemple :
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 :
On créé une règle pour chaque condition définie juste au dessus.
Exemple règle : Si la condition App_http_cloud est vérifiée alors on redirige vers NGINX_HTTP_BACKEND :
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 :
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.
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.
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