« HAProxy » : différence entre les versions

De Justine's wiki
Aller à la navigation Aller à la recherche
Aucun résumé des modifications
 
(9 versions intermédiaires par la même utilisatrice non affichées)
Ligne 1 : Ligne 1 :
[[Category:linux]]
= Présentation, installation, configuration de base =
= Présentation, installation, configuration de base =
[https://www.haproxy.com/fr/blog/haproxy-configuration-basics-load-balance-your-servers/ Doc]
[https://www.haproxy.com/fr/blog/haproxy-configuration-basics-load-balance-your-servers/ Doc]
Ligne 88 : Ligne 89 :
</source>
</source>


== Savoir à qui parler : définir un frontend ==
== Savoir à qui parler : définir un backend ==
Avec HAProxy, on a un frontend qui reçoit le traffic; ce traffic est ensuite dispatché vers un backend, soit un pool de serveurs webs / applicatifs qui sauront répondre. Ici, je vais pour ma part ajouter un backend qui correspondra à mon wiki.
Avec HAProxy, on a un frontend qui reçoit le traffic; ce traffic est ensuite dispatché vers un backend, soit un pool de serveurs webs / applicatifs qui sauront répondre. Ici, je vais pour ma part ajouter un backend qui correspondra à mon wiki.


Ligne 153 : Ligne 154 :
== Configuration pour SSL ==
== Configuration pour SSL ==
Mon but va désormais être de faire du SSL offloading : mes serveurs web continuent à servir le port 80, mais les clients arrivent sur HAProxy qui répond en SSL avec des certificats gérés par certbot.
Mon but va désormais être de faire du SSL offloading : mes serveurs web continuent à servir le port 80, mais les clients arrivent sur HAProxy qui répond en SSL avec des certificats gérés par certbot.
Pour cela, je vais avoir besoin d'un certificat SSL que je vais faire avec [[Certbot]]. J'ai certbot en place, je vais installer une crontab pour lui demander de générer un certificat wildcard pour chacun de mes domaines. exécutant ce script pour le renouveler régulièrement :
<source lang="bash">
#!/bin/bash
domains="squi.fr squirrelsystem.fr"
for domain in $domains
do certbot certonly -n --agree-tos --email justinepelletreau@gmail.com --dns-ovh --dns-ovh-credentials ~/.secrets/certbot/ovh.ini -d ${domain} -d *.${domain} &&\
cat /etc/letsencrypt/live/${domain}/fullchain.pem /etc/letsencrypt/live/${domain}/privkey.pem > /etc/haproxy/ssl/certs/${domain}.pem;
done
</source>
Ce script ne renouvellera les certificats qu'en cas de besoin. Je créée le dossier dans le répertoire de HAProxy:
<source>
mkdir /etc/haproxy/ssl/certs
</source>
...puis je lance le script une première fois et vérifie que tout se passe bien. Ensuite je le mets dans une crontab qui tournera tous les mois.
<source>
0 0 1 * * /root/certauto.sh >> /var/log/cron.log 2>&1
</source>
Puis je vais configurer mon haproxy :
<source>
frontend fronthttp
bind :80
#ssl crt correspond sert a dire a haproxy de chercher ses certificats dans les dossier en question
#Il va chercher les certifs selon leur nom
bind :443 ssl crt /etc/haproxy/ssl/certs/
#Redirige http > https avec une 302
#http-request redirect scheme https unless { ssl_fc }
    #Pour utiliser une redirection 301, que Google préfère
    http-request redirect scheme https code 301 if !{ ssl_fc }
acl is_wiki hdr(host) -i wiki.squi.fr
acl is_bookstack hdr(host) -i bookstack.squi.fr
use_backend wiki if is_wiki
use_backend bookstack if is_bookstack
backend wiki
server wiki1 10.0.0.112:80
backend bookstack
server bookstack1 10.0.0.105:80
</source>
Puis je vérifie et reload HAProxy:
<source>
haproxy -c -V -f /etc/haproxy/haproxy.cfg
systemctl reload haproxy
</source>
== Authentification utilisateurs ==
[https://www.haproxy.com/documentation/hapee/latest/security/authentication/basic-authentication/ Doc pas claire]
Trois directives.
Un groupe d'utilisateurs :
<source>
userlist myusers
group gallery
user gallery password $5$h9.GezjJQM8dEjSB$h5b0.zQFXXU2ku/6DINE1T4cBZgIzCFnieN/QXt2k.8 groups gallery
</source>
Ici, je créée une liste myusers, contenant un groupe gallery. Le mot de passe est généré avec la commande mkpasswd du paquet whois:
<source>
mkpasswd -m sha-256 'mypasswd'
</source>
Puis dans mon frontend:
<source>
#Cette acl inclut tous les gens authentifiés dans myusers / gallery
acl req_auth http_auth_group(myusers) gallery
#Si pas concerné par l'ACL, accès limité pour ma gallerie
http-request auth realm "Limited Access" if !req_auth is_gallery
</source>
...is_gallery étant l'acl vérifiant juste si un utilisateur se rend sur le site de ma gallerie de photos privées.
== Health Checks ==
[https://www.haproxy.com/documentation/hapee/latest/load-balancing/health-checking/active-health-checks/ Documentation sur les health checks]
HAproxy peut vérifier à intervalles réguliers la santé de ses backends selon divers critères, et envoyer une erreur par exemple si ce critère échoue (le site est alors innacessible). C'est aussi utile avec checkMK qui le fait remonter dans ses sondes.
La façon la plus simple consiste à envoyer un paquet tcp sur le port du serveur et à attendre une réponse. Cela se fait en ajoutant une simple directive check sur les serveurs:
<source>
server varnish1 127.0.0.1:81 alpn h2,http/1.1 check
</source>
Mais il existe bien d'autres checks, par exemple en fonction du code retour ou en fonction du texte dans la page HTML. Voir la doc ci-dessus en fonction des besoins.
== Durcissement et headers HTTP ==
Dans le but de durcir un peu la configuration de notre HAProxy, on peut rajouter à la volée des headers HTTP.
Ces configurations peuvent être mises au niveau du backend ou directement au niveau du frontend http.
Le format est du type:
http-response set-header Nom-du-header "valeur"
=== HSTS (HTTP Strict Transport Security) ===
Ici, on va dire au browser de se rappeller pendant 6 mois que le site est en https; cela va inclure les sous-domaines.
http-response set-header Strict-Transport-Security "max-age=16000000; includeSubDomains;"
=== X-frame : anti clickjacking ===
Ce header, une fois sur deny, indique aux browsers qu'ils ne devraient pas afficher notre page via une iframe sur un autre site, via un embed, etc. Le but est de se protéger contre le clickjacking; soit le fait que d'autres puissent s'interposer entre l'utilisateur et notre site afin de voler des données, par exemple.
http-response set-header X-Frame-Options deny
=== XSS-Protection ===
Ce header charge une protection dans Explorer / Chrome / Safari qui empêche l'exécution de XSS qu'ils trouveraient dans la page. Ce header est un peu désuet car il peut être remplacé par des headers CSP (Content Security Policy) qui empêchent l'exécution de javascript inline, par exemple. Mais comme le CSP n'est pas au programme pour moi tout de suite, autant l'implémenter.
http-response set-header X-XSS-Protection 1;mode=block
=== Mime-Sniffing ===
Ce header va dire aux browser de ne pas sniffer les contenus chargés avec la page (les images par exemple) afin de déterminer par eux-même le mimetype du contenu en question; ils vont se baser sur ce que dit la balise (img par exemple). Si le mime-sniffing est utile, il peut être utilisé pour faire du XSS. C'est un peu tordu, je vous l'accorde, et je ne suis pas sûre de l'utilité réelle de cette conf.
http-response set-header X-Content-Type-Options nosniff

Dernière version du 29 mars 2023 à 09:12

Présentation, installation, configuration de base

Doc

HAProxy est un load balancer très puissant et économe.

Je vais ici l'installer sur une machine seule, et il fera le relai vers mes différents sites.

L'installation sur Debian 11 se fait via les dépôts: <source> apt install haproxy </source>

Configurer l'IP et le port : définir un frontend

Nous allons définir l'adresse et le port sur lesquels HAProxy va attendre son traffic. Cela passe par la création d'un frontend : un frontend est l'élement qui reçoit le traffic. Toute la configuration se fait de base dans /etc/haproxy.cfg. Nous allons donc y créer un frontend en y ajoutant les lignes suivantes : <source> frontend fronthttp bind *:80 </source> Je nomme mon frontend qui recevra du trafic http; le bind se fait sur * car les requêtes qu'il recevra viendront de l'extérieur. Je pourrais le bind sur 127.0.0.1, mais à ce moment-là il n'écouterait que depuis son propre serveur.

On pourrait configurer bind de plusieurs façons:

  • bind 0.0.0.0:80 : similaire à étoile
  • bind :80 : pareil
  • bind :80,81 Ecouter sur les ports 80 et 81. Ne pas mettre d'espace entre les deux.
  • bind :5555-5600 Ecouter sur tous les ports entre 5555 et 5600

Sur ma version Debian 11, le fichier de base est assez chargé: <source> global log /dev/log local0 log /dev/log local1 notice chroot /var/lib/haproxy stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners stats timeout 30s user haproxy group haproxy daemon

# Default SSL material locations ca-base /etc/ssl/certs crt-base /etc/ssl/private

# See: https://ssl-config.mozilla.org/#server=haproxy&server-version=2.0.3&config=intermediate

       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-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
       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

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 </source> Je ne vais pas passer toute la conf en revue, mais certains éléments de defaults sont intéressants :

  • timeout connect correspond au timeout de connexion aux backends
  • timeout client correspond au timeout de connexion aux clients
  • timeout server correspond au temps d'attente pour que le backend envoie ses données
  • Le mode http signifie que l'on va travailler en http, mais HAProxy peut aussi fonctionner en mode tcp et balancer beaucoup d'autres protocoles. En mode http, HAProxy peut inspecter les headers, les métadonnées, etc.

Je restart et essaye. <source> root@lb:/etc/haproxy# systemctl restart haproxy root@lb:/etc/haproxy# curl http://localhost

<html><body>

503 Service Unavailable

No server is available to handle this request. </body></html> </source> Ça marche ! On a pas encore de backends.

Vérifier ma config

Je peux vérifier ma config avec la commande suivante: <source lang="bash"> haproxy -c -V -f /etc/haproxy/haproxy.cfg

root@lb:/etc/haproxy# haproxy -c -V -f haproxy.cfg Configuration file is valid </source>

Savoir à qui parler : définir un backend

Avec HAProxy, on a un frontend qui reçoit le traffic; ce traffic est ensuite dispatché vers un backend, soit un pool de serveurs webs / applicatifs qui sauront répondre. Ici, je vais pour ma part ajouter un backend qui correspondra à mon wiki.

Je modifie mon frontend et ajoute un backend dans ma configuration (puis reload d'HAProxy) : <source> backend wiki server wiki1 10.0.0.112:80 </source>

En me rendant sur http://mon-serveur-haproxy, je suis bien redirigée vers mon wiki. Je n'ai qu'un serveur dans mon backend, mais je pourrais en avoir plusieurs et HAproxy ferait le relai de façon cyclique: <source> backend myservers

 server server1 127.0.0.1:8000
 server server2 127.0.0.1:8001
 server server3 127.0.0.1:8002

</source>

Créer des ACLs (de base)

Documentation sur les ACLs Syntaxe des ACLs

Nous avons un backend, mais il serait plus intéressant d'en avoir plusieurs et de demander à HAProxy de faire le tri en fonction de ce que je lui demande. Pour cela, nous allons créer des règles (appellées ACL). Une façon simple de faire est de définir comme suit : <source> frontend fronthttp bind *:80 use_backend wiki if { hdr(host) -i wiki.ju.lab } use_backend bookstack if { hdr(host) -i bookstack.ju.lab }

backend wiki server wiki1 10.0.0.112:80

backend bookstack server bookstack1 10.0.0.105:80 </source> Ainsi, en fonction de ce que je lui demande, le traffic est redirigé au bon endroit.

Mais je peux également définir une acl en tant que telle, soit une règle complexe à laquelle je pourrais donner un nom. Cette règle représente un contrôle d'accès et permettra de pouvoir vérifier si cela s'applique à une demande et d'effectuer une action si c'est le cas. L'intérêt de pouvoir utiliser une acl plusieurs fois par exemple. Un exemple d'acl simple: <source> acl is_wiki hdr(host) -i wiki.ju.lab wiki.ns2.tld2 </source>

  • acl est le mot-clef pour dire que l'on créée une acl.
  • is_wiki est le nom de mon acl
  • hdr(host) correspond à un contrôle sur l'hôte demandé
  • -i est un flag, soit le comparateur d'égalité (is)
  • ensuite, je peux donner plusieurs noms d'hôte qui déclencheront l'acl.

L'ACL se base sur le header HTTP Host.

Les ACLs permettent de faire beaucoup de choses. Se réferer à la doc, je ferais aussi plus d'exemples ici quand j'aurais poussé un peu mon utilisation d'HAProxy.

En attendant, je vais reprendre mon fichier de configuration et appliquer des ACLs pour rediriger correctement mon traffic. Mon frontend: <source> frontend fronthttp bind *:80

acl is_wiki hdr(host) -i wiki.ju.lab acl is_bookstack hdr(host) -i bookstack.ju.lab

use_backend wiki if is_wiki use_backend bookstack if is_bookstack </source>

Configuration avancée

Configuration pour SSL

Mon but va désormais être de faire du SSL offloading : mes serveurs web continuent à servir le port 80, mais les clients arrivent sur HAProxy qui répond en SSL avec des certificats gérés par certbot.

Pour cela, je vais avoir besoin d'un certificat SSL que je vais faire avec Certbot. J'ai certbot en place, je vais installer une crontab pour lui demander de générer un certificat wildcard pour chacun de mes domaines. exécutant ce script pour le renouveler régulièrement : <source lang="bash">

  1. !/bin/bash

domains="squi.fr squirrelsystem.fr"

for domain in $domains do certbot certonly -n --agree-tos --email justinepelletreau@gmail.com --dns-ovh --dns-ovh-credentials ~/.secrets/certbot/ovh.ini -d ${domain} -d *.${domain} &&\ cat /etc/letsencrypt/live/${domain}/fullchain.pem /etc/letsencrypt/live/${domain}/privkey.pem > /etc/haproxy/ssl/certs/${domain}.pem; done </source> Ce script ne renouvellera les certificats qu'en cas de besoin. Je créée le dossier dans le répertoire de HAProxy: <source> mkdir /etc/haproxy/ssl/certs </source> ...puis je lance le script une première fois et vérifie que tout se passe bien. Ensuite je le mets dans une crontab qui tournera tous les mois. <source> 0 0 1 * * /root/certauto.sh >> /var/log/cron.log 2>&1 </source>

Puis je vais configurer mon haproxy : <source> frontend fronthttp bind :80 #ssl crt correspond sert a dire a haproxy de chercher ses certificats dans les dossier en question #Il va chercher les certifs selon leur nom bind :443 ssl crt /etc/haproxy/ssl/certs/

#Redirige http > https avec une 302 #http-request redirect scheme https unless { ssl_fc }

   #Pour utiliser une redirection 301, que Google préfère
   http-request redirect scheme https code 301 if !{ ssl_fc }

acl is_wiki hdr(host) -i wiki.squi.fr acl is_bookstack hdr(host) -i bookstack.squi.fr

use_backend wiki if is_wiki use_backend bookstack if is_bookstack

backend wiki server wiki1 10.0.0.112:80

backend bookstack server bookstack1 10.0.0.105:80 </source>

Puis je vérifie et reload HAProxy:

<source> haproxy -c -V -f /etc/haproxy/haproxy.cfg systemctl reload haproxy </source>

Authentification utilisateurs

Doc pas claire Trois directives.

Un groupe d'utilisateurs : <source> userlist myusers group gallery user gallery password $5$h9.GezjJQM8dEjSB$h5b0.zQFXXU2ku/6DINE1T4cBZgIzCFnieN/QXt2k.8 groups gallery </source> Ici, je créée une liste myusers, contenant un groupe gallery. Le mot de passe est généré avec la commande mkpasswd du paquet whois: <source> mkpasswd -m sha-256 'mypasswd' </source>

Puis dans mon frontend: <source>

  1. Cette acl inclut tous les gens authentifiés dans myusers / gallery

acl req_auth http_auth_group(myusers) gallery #Si pas concerné par l'ACL, accès limité pour ma gallerie http-request auth realm "Limited Access" if !req_auth is_gallery </source> ...is_gallery étant l'acl vérifiant juste si un utilisateur se rend sur le site de ma gallerie de photos privées.

Health Checks

Documentation sur les health checks HAproxy peut vérifier à intervalles réguliers la santé de ses backends selon divers critères, et envoyer une erreur par exemple si ce critère échoue (le site est alors innacessible). C'est aussi utile avec checkMK qui le fait remonter dans ses sondes.

La façon la plus simple consiste à envoyer un paquet tcp sur le port du serveur et à attendre une réponse. Cela se fait en ajoutant une simple directive check sur les serveurs: <source> server varnish1 127.0.0.1:81 alpn h2,http/1.1 check </source> Mais il existe bien d'autres checks, par exemple en fonction du code retour ou en fonction du texte dans la page HTML. Voir la doc ci-dessus en fonction des besoins.

Durcissement et headers HTTP

Dans le but de durcir un peu la configuration de notre HAProxy, on peut rajouter à la volée des headers HTTP.

Ces configurations peuvent être mises au niveau du backend ou directement au niveau du frontend http.

Le format est du type:

http-response set-header Nom-du-header "valeur"

HSTS (HTTP Strict Transport Security)

Ici, on va dire au browser de se rappeller pendant 6 mois que le site est en https; cela va inclure les sous-domaines.

http-response set-header Strict-Transport-Security "max-age=16000000; includeSubDomains;"

X-frame : anti clickjacking

Ce header, une fois sur deny, indique aux browsers qu'ils ne devraient pas afficher notre page via une iframe sur un autre site, via un embed, etc. Le but est de se protéger contre le clickjacking; soit le fait que d'autres puissent s'interposer entre l'utilisateur et notre site afin de voler des données, par exemple.

http-response set-header X-Frame-Options deny

XSS-Protection

Ce header charge une protection dans Explorer / Chrome / Safari qui empêche l'exécution de XSS qu'ils trouveraient dans la page. Ce header est un peu désuet car il peut être remplacé par des headers CSP (Content Security Policy) qui empêchent l'exécution de javascript inline, par exemple. Mais comme le CSP n'est pas au programme pour moi tout de suite, autant l'implémenter.

http-response set-header X-XSS-Protection 1;mode=block

Mime-Sniffing

Ce header va dire aux browser de ne pas sniffer les contenus chargés avec la page (les images par exemple) afin de déterminer par eux-même le mimetype du contenu en question; ils vont se baser sur ce que dit la balise (img par exemple). Si le mime-sniffing est utile, il peut être utilisé pour faire du XSS. C'est un peu tordu, je vous l'accorde, et je ne suis pas sûre de l'utilité réelle de cette conf.

http-response set-header X-Content-Type-Options nosniff