Title: Configurer un reverse proxy avec haproxy
subtitle: (HTTP/HTTPS)
Date: 2015-12-11 09:16
Modified: 2015-12-11 09:16
Category: Système
Tags: haute disponibilité, hébergement, reverse proxy, nom de domaine, debian, linux, haproxy
keywords: haute disponibilité, hébergement, reverse proxy, nom de domaine, debian, linux, haproxy
Slug: configurer-reverse-proxy-haproxy
Authors: Victor
Status: published

[TOC]

## Ou : utilisons un logiciel dont c'est le taf de proxyfier :-)

<p>(Oui, j'assume le verbe proxyfier, na)</p>

<p>Il y a quelques temps, j'avais fait un article pour configurer <a href="/2012/10/configurer-reverse-proxy-apache.html">apache en mode reverse proxy</a>.</p>

<p>Je vous invite à le relire rapidement (au moins le chapô ;-) ) pour savoir de quoi l'on parle avec le terme reverse proxy.</p>

<p>Bien que ça fonctionne très bien, il existe des logiciels dédiés à la mise en place d'un reverse proxy, à mettre devant le serveur web.</p>

<p>Le logiciel auquel nous allons nous intéresser aujourd'hui est <a href="http://www.haproxy.org/">haproxy</a> et est justement dédié à cet usage.</p>

<p>À noter que nous allons l'utiliser ici en tant que proxy HTTP/HTTPS, mais que haproxy peut servir de proxy pour n'importe quoi, du serveur de messagerie à un serveur mysql, et globalement à tout ce qui utilise TCP.</p>

## Pourquoi que donc ?

<p>Avoir un reverse proxy devant son serveur web présente plusieurs avantages.</p>

<ul>
	<li>&nbsp; Ne pas avoir le serveur web publiquement accessible (par exemple pour sa protection)</li>
	<li>&nbsp; Faire des pré-traitement sur les requêtes (nombre de sessions, limitation de bande passante, etc)</li>
	<li>&nbsp;&nbsp; Balancer les requêtes entre plusieurs serveurs web</li>
	<li>&nbsp; Centraliser les accès publics (pour n'utiliser qu'une IP publique avec différents serveurs web derrière)</li>
</ul>

<p>Une fois n'est pas coutume, attaquons le concret :)</p>

## Pré-requis

<p>Pour que cela fonctionne, il vous faut au minimum un serveur web fonctionnel. Il peut être sous apache, nginx, ou même IIS pourquoi pas.</p>

<p>C'est vers lui que nous redirigerons les requêtes.</p>

<p>Il vous faut également un serveur où installer haproxy.</p>

<p>Dans cet exemple, nous prendrons un serveur dédié à cet usage. Cependant, il est aussi possible d'installer haproxy SUR le serveur hébergeant le serveur web (s'il est sous Linux). Ce n'est pas le sujet ici :-)</p>

<p>&nbsp;</p>

<p>Nous allons donc partir sur un serveur web, et un serveur "passerelle" qui possédera l'adresse publique de votre site, et sur lequel nous installerons haproxy.</p>

<p>Dans notre exemple, ce serveur passerelle sera sous debian 8.</p>

<p>Soit vous avez des serveurs physiques, soit vous les virtualisez.</p>

<p>Si vous vous auto-hébergez, vous ne pourrez pas utiliser votre adresse publique sur votre serveur passerelle. Dans ce cas, mettez en place une redirection de port vers votre serveur passerelle sur votre machinbox (ou routeur). Les ports 80 et 443 suffiront :-)</p>

## Terminologie

<p>Haproxy utilise des termes assez classiques dans sa configuration, mais que nous allons tout de même revoir ici afin de bien savoir de quoi l'on parle dans la suite.</p>

<ul>
	<li>bind :&nbsp; <em>attacher </em>en Anglais, permet de dire sur quelle IP et quel port haproxy va écouter. Par exemple, 192.168.1.1 sur le port 80</li>
	<li>frontend : c'est un bloc de configuration qui permet de définir toutes les règles qui s'appliqueront (domaines écoutés, limitations, etc). Un frontend peut s'appliquer à un ou plusieurs bind.</li>
	<li>backend : c'est un autre bloc de configuration, que l'on place derrière un frontend. Si le frontend gère ce qui est publique (à "l"avant" du serveur), le backend gère "l'arrière". C'est là que vous définirez les serveurs web vers lesquels envoyer les requêtes.</li>
	<li>acl : une "access control list" permet de définir des conditions dans un bloc, par exemple "si le domaine contient site1, alors faire cela, si la requête est en https, alors faire ceci"</li>
</ul>

## Aller go : le cambouis c'est pas si salissant

### Installation

<p>On commence par installer haproxy. Il est de base dans debian (depuis la version 6)</p>

<p>Si vous voulez faire de l'https, il vous faudra la version au moins 1.5 de haproxy.&nbsp; (disponible dans les backports debian 7 ou nativement sous debian 8)</p>

<p>Si vous voulez plus de détails sur les versions à installer, rendez vous sur <a href="http://haproxy.debian.net/">http://haproxy.debian.net/</a></p>

```
aptitude update
aptitude install haproxy
```

### Global et default

<p>Les 2 sections global et default permettent de définir des variables qui seront appliquées à tout le reste de la configuration (sauf redéfinition plus précise dans un sous bloc).</p>

<p>Les paramètres définit à l'installation sont corrects, vous pouvez donc laisser ces sections ainsi pour l'instant.</p>

### Où configurer

<p>Le fichier de configuration de haproxy est placé dans<em> /etc/haproxy/haproxy.cfg. </em>Néanmoins, pour que cela soit plus pratique à configurer, vous pouvez écrire vos configurations personnalisées dans un fichier <em>/etc/haproxy/haproxy.local</em>, cela évitera d'avoir à modifier le fichier par défaut.</p>

### Configuration du frontend

<p>Nous allons voir ici une configuration de frontend basique. Il est possible de faire des choses très compliquées, mais on va rester simple pour l'instant :-)</p>

```
frontend http-in
    bind IP_PUBLIQUE:80
    mode http
    option httplog
    acl your_acl  hdr(host)   votresiteweb.tld
    use_backend backend1 if your_acl
```

<p>Attention, l'indentation est importante !</p>

<p>Tous les paramètres du bloc doivent être décalés en dessous pour que haproxy voit que ces paramètres font parti du bloc. En général, une tabulation ou X espaces, selon les préférences.</p>

<p>Vous définissez :</p>

<ul>
	<li>frontend http-in : le mot clef <em>frontend</em> indique la présence d'un bloc de configuration frontend. ici,<strong> http-in</strong> est un nom pour ce frontend, choisi arbitrairement. Vous pouvez nommer le frontend comme vous le souhaitez, une bonne pratique est de prendre un nom clair et pas trop à rallonge :-)</li>
	<li>IP_PUBLIQUE : l'adresse IP sur laquelle haproxy va écouter. Vous pouvez préciser une IP précise, ou bien 0.0.0.0 pour écouter sur toutes les IP présentes sur le serveur. Vous pouvez aussi mettre plusieurs lignes l'une en dessous de l'autre pour ajouter des IP précises</li>
	<li>mode http : on définit que ce frontend va traiter uniquement le protocole HTTP (et donc aussi HTTPS). Cela permet déjà à haproxy d'analyser les requêtes, et de rejeter tout ce qui n'est pas formaté correctement vis à vis des RFC</li>
	<li>option httplog : permet de logguer le détail des requêtes http. Cela permet d'avoir plus d'informations dans les logs haproxy (headers, session http, ...).</li>
	<li>acl : on définit une ACL, qui sera reconnue si la partie HOST de la requête http correspond <strong>exactement</strong> à <em>votresiteweb.tld</em>. Il est aussi possible de chercher l<strong>a fin</strong> d'un host (tout ce qui termine par votresiteweb.tld), <strong>commence par</strong>, <strong>contient</strong> tel mot, etc. Ici si ça correspond, l'acl se nommera <em>your_acl</em>, nous pourrons la réutiliser dans la suite du bloc.</li>
	<li>use_backend : on définit ici qu'on va utiliser le backend <em>backend1</em> SI l'acl <em>your_acl</em> est active. Donc dans notre cas, si la requête traitée contient <strong>exactement</strong> <em>votresiteweb.tld</em> dans la partie HOST, l'acl est active. Tout se recoupe !</li>
</ul>

<p>Et voila, en 5 lignes, vous avez configuré un premier frontend pour rediriger votresiteweb.tld vers un backend (qu'il reste à écrire)</p>

<p>On voit déjà que la syntaxe est très simple par rapport à apache (plus de ProxyPass avec des paramètres pas toujours clairs).</p>

<p>On a ici une configuration très simpliste. Je présenterai quelques options montrant la puissance d'haproxy à la fin de cet article.</p>

### Configuration du backend

<p>Maintenant que notre frontend est prêt à recevoir les requêtes publiques, il faut créer le backend qui sera à même de savoir <strong>où</strong> envoyer ces requêtes.</p>

```
backend backend1
    mode http
    option httpchk
    option forwardfor except 127.0.0.1
    http-request add-header X-Forwarded-Proto https if { ssl_fc }
    server web-server1  IP_SERVEUR_WEB:80 maxconn 32
```

<p>De la même manière que pour le frontend, attention à l'indentation pour que les paramètres soient décalés en dessous du mot clef <em>backend</em></p>

<p>Ici vous définissez :</p>

<ul>
	<li>backend backend1 : le mot clef <em>backend</em> permet d'indiquer le début d'un bloc de backend. Le nom <strong>backend1</strong> est au choix, comme pour le nom du frontend. Il sera celui à utiliser dans le frontend au moment d'écrire le <em>use_backend</em></li>
	<li>mode http : comme pour le frontend, cela indique que ce backend s'occupera d'http, et permet d'utiliser diverses options pratiques (réécriture de header notamment)</li>
	<li>option httpchk : le httpchk permet de faire en sorte que haproxy vérifie à tout moment l'état des serveurs web derrière lui. Il peut ainsi savoir si le serveur est prêt à recevoir des requêtes, basculer vers un serveur de secours, afficher une page d'erreur en cas de panne, etc. De base, c'est un simple check HTTP sur le / qui est effectué, mais il est possible par exemple de spécifier un script ou un chemin précis</li>
	<li>forwardfor except 127.0.0.1 : cette option va permettre d'ajouter un en tête <strong>xforwardfor</strong> dans les requêtes qui passent par le backend, en tête contenant la véritable adresse IP du visiteur. En effet, les requêtes passant par le proxy, c'est son IP qui sera vu niveau réseau par le serveur web ce qui peut être gênant pour faire des statistiques de visites par exemple, car vous auriez l'impression que toutes les visites viennent du serveur proxy... Le <strong>except 127.0.0.1</strong> permet d'éviter d'ajouter cet en tête si c'est 127.0.0.1 qui a généré la requête.</li>
	<li>server web-server1 : cette définition va permettre d'indiquer le serveur vers lequel transmettre les requêtes. IP_SERVEUR_WEB est bien sûr l'adresse IP du serveur web. :80 permet d'indiquer le port ou transmettre. Il est possible d'indiquer plusieurs lignes pour définir plusieurs serveur web et faire de la répartition de charge.</li>
	<li>maxconn 32 : permet de limiter le nombre maximum de connexions gérées par ce serveur, ici 32. Cela permet d'éviter de surcharger le serveur web au dessus de sa capacité par exemple, et de mitiger directement et à peu de coût une attaque.</li>
</ul>

<p>A partir de là, c'est tout bon. Vous avez une configuration, certes basique, qui vous permettra de recevoir des requêtes pour votre site, et les transmettre vers votre serveur web :-)</p>

<p>Bien sûr, redémarrez haproxy pour appliquer la configuration.</p>

```
systemctl restart haproxy
```

## Un peu plus loin : https

### frontend

<p>Jusqu'ici, le frontend était uniquement http, pas de certificat.</p>

<p>Mettre en place un frontend https est tout ce qu'il y a de plus simple. On va tout simplement créer un deuxième frontend, cette fois bindé sur le port 443</p>

```
frontend https-in
    bind IP_PUBLIQUE:443 ssl crt /etc/haproxy/cert/ no-sslv3
    mode http
    option httplog
    acl your_acl  hdr(host)   votresiteweb.tld
    use_backend backend1 if your_acl
```

<p>On voit quelques différences par rapport à la configuration définie plus haut.</p>

<ul>
	<li>https-in : le nom est là encore au choix, mais il faut qu'il soit différent du premier, sinon haproxy renverra une erreur au démarrage.</li>
	<li>bind : la ligne bind change. On voit qu'on va cette fois se mettre sur le port 443 au lieu de 80, l'HTTPS étant par défaut sur le port 443. Rien ne vous empêche de mettre un autre port selon vos besoins.</li>
	<li>ssl : ce mot clef (utilisable sur la même ligne que bind) permet d'indiquer à haproxy qu'il va devoir faire du SSL sur ce bind</li>
	<li>crt /etc/haproxy/cert/ : définit le répertoire dans lequel vous mettre vos certificats. haproxy gère les certificats au format pem, que vous pouvez simplement créer de la façon suivante en mergeant le .crt et le .key :</li>
</ul>

```
cat domain.tld.crt domain.tld.key &gt; domain.tld.pem
```

<ul>
	<li>no-sslv3 : cela permet de spécifier à haproxy de refuser d'utiliser le protocole sslv3, considéré désormais comme non sécurisé.</li>
</ul>

<p>Toutes les autres options sont les même que pour le frontend HTTP (acl, use_backend, ...)</p>

### Note sur le dossier des certificats

<p>haproxy gère de manière très efficace les certificats. Vous pouvez les mettre en vrac dans le répertoire, haproxy les parse au démarrage et utilise les certificat de la manière la plus précise possible.</p>

<p>Si par exemple vous possédez un certificat wildcard (*.domain.tld) et un certificat plus précis (sous.domain.tld)</p>

<ul>
	<li>Les visites sur <em>sous.domain.tld</em> utiliseront le certificat de <em>sous.domain.tld</em></li>
	<li>Les visites sur domain.tld utiliseront le certificat wildcard</li>
	<li>Les visites sur toto.domain.tld utiliseront le certificat wildcard</li>
	<li>etc :-)</li>
</ul>

### Backend

<p>Comme c'est le frontend qui gère la partie HTTPS, vous pouvez utiliser exactement les même backend pour le frontend http et le frontend https. Rien à faire à ce niveau là donc, tout est déjà bon dans la configuration.</p>

<p>Redémarrez haproxy, et votre site est prêt à fonctionner en HTTPS !</p>

## Encore plus loin : options sympathiques

<p>Cette liste n'est clairement pas exhaustive, haproxy disposant d'une foison incroyable de possibilités. Je ne liste ici que celles qui me semblent les plus utiles de manière générales (en gros, celles que j'utilise moi :-D )</p>

<p>Pour une liste complète, je vous invite à vous référer à la <a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html">documentation haproxy</a> qui est vraiment très claire et détaillée avec tous les exemples nécessaires à la compréhension.</p>

### Plusieurs sites dans un frontend

<p>Comme vous ne pouvez créer qu'un seul frontend écoutant sur le port 80 (ou 443) sur une adresse IP, il va falloir utiliser le même frontend pour gérer plusieurs sites.</p>

<p>Cela se fait très simplement, en utilisant plusieurs acl dans le frontend, par exemple :</p>

```
acl site1 hdr(host) site1.tld
acl sous-domaine hdr(host) sousdomain.site1.tld
acl toto-site2 hdr(host) toto.site2.tld
```

<p>Vous pouvez ensuite utiliser un ou plusieurs backend selon les acl :</p>

```
use_backend backend1 if site1 or sous-domaine or toto-site2
```

<p>Ou encore :</p>

```
use_backend backend1 if site1 or sous-domaine
use_backend backend2 if toto-site2
```

### Test de domaine dans un frontend

<p>Dans mes exemples, j'ai à chaque fois utilisé hdr(host) dans mes acl, qui permet de chercher le contenu exact de la variable HOST de la requête HTTP.</p>

<p>Néanmoins, il est possible de faire plus général, comme par exemple créer des acl en se basant sur la fin du HOST, le début, voir chercher si HOST contient telle ou telle chaîne de caractère.</p>

<p>Cela peut permettre de créer une acl qui gérera tout ce qui se termine par site.tdl, tout ce qui contient www., etc</p>

```
acl test1 hdr_beg(host) www. #On match ce qui commence par www.
acl test2 hdr_end(host) domain.tld #On match tout ce qui termine par domain.tld
acl test3 hdr_reg(host) REGEX #On match tout ce qui correspond à l'expression régulière REGEX
```

<p>Vous êtes ensuite libre d'utiliser ces acl dans les backend que vous voulez.</p>

### Backend par défaut, aussi nommé la poubelle

<p>Une fonctionnalité que je trouve assez intéressante est de prévoir un backend par défaut qui servira de poubelle. Toutes les visites arrivant sur votre serveur et qui ne matchent pas une acl peuvent être considérées comme de la poubelle, voire comme une attaque potentielle, et donc autant ne pas les rediriger vers un site par défaut (comme le fait par exemple de base le vhost apache default)</p>

<p>Pour cela, il suffit d'ajouter dans le frontend le mot clef <em>default_backend</em> qui permet de définir un backend qui sera utilisé pour les requêtes n'ayant matché aucune acl dans le frontend, et de créer un backend qui renverra par exemple une erreur 403 "accès interdit" :</p>

```
frontend http-in
    [...]
    default_backend poubelle

backend poubelle
    mode http
    http-request deny
```

### Répartition de charge avec un backend

<p>Si vous définissez plusieurs lignes de <em>server </em>dans un backend, alors haproxy va automatiquement répartir (via le protocole round robin) les requêtes entrantes de manière équitables entre les serveurs.</p>

<p>Mais il est également possible de définir des poids pour ces serveurs, si par exemple l'un d'eux est plus puissant, plus à même de recevoir des requêtes</p>

```
backend backend1
    [...]
    server server1 ip1:80 weight 1
    server server2 ip2:80 weight 2
```

<p>Ici server2 encaissera 2 fois plus de requêtes que server1</p>

<p>Vous pouvez utiliser le mot clef backup :</p>

```
backend backend1
    [...]
    server server1 ip1:80
    server server2 ip2:80 backup
```

<p>Ici, server1 recevra <strong>toutes</strong> les requêtes, <strong>mais</strong> si haproxy détecte qu'il n'est plus accessible, alors il enverra automatiquement toutes les requêtes vers server2</p>

<p>Il est possible aussi de combiner ces 2 techniques, faire de la répartition de charge entre plusieurs serveurs tout en ayant d'autres disponibles en backup.</p>

### Forcer l'https

<p>Il est possible de dire à haproxy, dans votre frontend sur le port 80, que tel site doit absolument utiliser l'HTTPS. Auquel cas, si haproxy reçoit une requête en HTTP, alors il redirigera immédiatement le visiteur vers exactement la même requête mais en HTTPS :</p>

```
frontend http-in
    [...]
    # On a plusieurs acl
    acl site1 hdr(host) site1.tld
    acl sous-domaine hdr(host) sousdomaine.site1.tld
    acl toto-site2 hdr(host) toto.site2.tld

    redirect scheme https code 301 if !{ ssl_fc } site1.tld or sous-domaine
    use_backend backend2 if toto-site2
```

<p>Ici, on va forcer via un code 301 (redirection permanente) la redirection vers l'HTTPS (on détecte que le protocole ssl n'est pas utilisé) pour les accès sur site1.tld et sousdomaine.site1.tld, mais on reste en HTTP et on utilise backend2 si la visite est sur toto.site2.tld</p>

### Réécrire des en-têtes dans le backend

<p>On a vu un exemple de réécriture d'en tête dans la partie sur les backend avec le xForwarFor.</p>

<p>Il est possible dans un backend d'écrire tous les en têtes que vous voulez.</p>

<p>Par exemple, si le frontend devant le backend est en https, vous pouvez ajouter un en-tête qui indiquera au serveur web derrière qu'il y a eu traitement https, ce même si le serveur web ne voit jamais le certificat puisque c'est haproxy qui s'en occupe :</p>

```
backend backend1
    [...]
    http-request add-header X-Forwarded-Proto https if { ssl_fc }
```

<p>Vous pouvez ainsi écrire (ou réécrire) n'importe quel en-tête.</p>

<p>Par exemple pour réécrire l'en tête serveur :</p>

```
backend backend1
    [...]
    rspidel ^server #On commence par effacer l'en tête serveur déjà présent
    rspadd  Server:\ Vous\ utilisez\ mon\ super\ serveur\ ! #On rajoute un nouvel en-tete Server (attention à bien protéger les espaces par des \)
```

<p>&nbsp;</p>

<p>Voila pour ce tour d'horizon (pas si) rapide sur haproxy !</p>

<p>J'espère que cela vous permettra de vous familiariser avec ce logiciel, qui n'a été ici qu'à peine effleuré :-)</p>

<p>Comme toujours, n'hésitez pas à poser vos questions dans les commentaires !</p>
