Les hébergeurs diffèrent de par leurs offres, leur taille, etc., mais également de par leurs choix d’infrastructures. Or ces derniers, qui sont un vecteur majeur de l’efficacité d’une offre d’hébergement, sont souvent peu connus. NBS System se lance donc dans une série d’articles présentant ses infrastructures et les outils utilisés, pour plus de transparence et pour faire connaître cette partie cachée du secteur de l’hébergement informatique. Après notre article sur les reverse proxies, nous nous intéressons aujourd’hui aux pare-feux et load balancers.

Infrastructure NBS System - pare-feu load balancer

Pare-feux

Rôle et fonctionnement d’un pare-feu

Un pare feu, c’est un péage, un point de passage obligatoire pour contrôler et diriger les flux entre les réseaux. Ces deux fonctionnalités s’effectuent en ouvrant et interprétant des paquets au niveau IP (Internet Protocol, donc sur la couche réseau).

Pour vérifier si un paquet peut continuer sa route, et le cas échéant pour savoir où l’envoyer, le pare-feu va donc ouvrir le paquet et regarder les informations suivantes : adresse IP source, adresse IP de destination et le protocole utilisé (ICMP, TCP, UDP, etc.). Si le paquet utilise les protocoles TCP ou UDP, alors le pare-feu regardera également le port source et le port de destination, qui apportent alors plus d’informations.

icone pare-feuCe sont ces informations qui seront comparées à une série de règles de filtrage déterminant si le paquet est accepté ou rejeté. Chaque paquet subit donc plusieurs tests (un test par règle) : s’il ne correspond pas à une règle, il passe à la suivante. S’il correspond, il sera soit accepté, soit « laché », donc perdu. Si, à la fin de tous les tests, il n’a correspondu à aucune règle, il sera également « lâché » par défaut. Une bonne pratique à mettre en place sur les pare-feux consiste à bloquer tout type de connexion qui n’est pas autorisé.

Tracking de connexion

Dans le cas où le paquet est accepté et transmis, reste à gérer la réponse à la requête ! C’est pourquoi les pare-feux peuvent enregistrer les connexions : on dit qu’ils sont « stateful » ou qu’ils font du « conntrack » (CONNection TRACKing). Cela signifie que lorsqu’ils reçoivent et interprètent un paquet, ils vérifient en premier lieu si une connexion existe déjà entre l’IP source et l’IP destination dans le même protocle, c’est-à-dire s’il a déjà géré des paquets transitant entre ces deux entités, avec les mêmes caractéristiques. C’est ainsi que les paquets contenant les réponses aux requêtes envoyées dans les paquets précédents sont acceptées par défaut : la connexion était existante et donc pré-validée par le pare-feu. Il est important de noter qu’à chaque fois que le pare-feu reçoit un nouveau paquet correspondant à une connexion existante, il met à jour l’enregistrement de cette connexion : il a donc l’historique de tous les échanges.

Ces enregistrements de connexion ont une durée de vie limitée (TTL ou Time To Live). Quand ce TTL est atteint, le pare-feu va fermer la connexion, afin d’éviter de conserver des connexions trop vieilles ou inactives et de ne pas surcharger la mémoire. Si une connexion a été fermée ou n’existe tout simplement pas, le pare-feu la créera.

NAT : Network Address Translation

NAT Network Address TranslationCe principe de tracking de connexion permet notamment d’utiliser le NAT (Network Address Translation ou Translation d’Adresse Réseau). Il consiste à réécrire les paquets à la volée en remplaçant une adresse IP par une autre, que ce soit l’IP source, l’IP destination ou les 2. Prenons un exemple*, illustré par le schéma ci-contre :

Le pare-feu reçoit un paquet depuis la machine A, dont l’adresse IP sera nommée IPa, avec pour destinataire l’adresse IP publique B correspondant au service B. Connaissant cette adresse IP et ce à quoi elle correspond, le pare-feu va la remplacer dans le paquet (IP destination) par la VIPb, la Virtual IP privée correspondant en interne au service B. C’est seulement ensuite qu’il effectuera la série de tests.

Quand il reçoit ensuite la réponse du service B, l’IP de destination est IPa, et l’IP source est VIPb. Etant donné qu’elle vient d’un de nos services, elle sera acceptée par défaut. Avant de renvoyer le paquet, le pare-feu transformera la VIPb en IP publique B, pour que la première ne sorte pas sur Internet (elle est privée et doit rester privée). Il ne peut cependant effectuer cette action que s’il a enregistré cette connexion et les détails des changements effectués, pour pouvoir restituer les bonnes adresses IP, compréhensibles par A, au retour du paquet.

Positionnement dans l’infrastructure

En général, sur la majorité des infrastructures, on trouve un pare-feu dédié par client. Le gros avantage est que les ressources de la machine supportent sans problème le trafic du client. Cependant, il existe un inconvénient : si un changement est à faire sur les pare-feux, il faudra alors aller les modifier un par un. Cela peut être très long !

Chez NBS System

Chez NBS System, en revanche, plutôt qu’un pare-feu par client, nous avons choisi de mutualiser les ressources, tout en gardant la sécurité apportée par les pare-feux. Ainsi, nous disposons de 6 pare-feux, montés deux par deux dans chaque zone : Equinix, Iliad (nos deux datacenters) et CerberHost (pour nos clients disposant de cette offre de Cloud de très haute sécurité, répartis sur les 2 datacenters évoqués).

Pourquoi deux pare-feux par zone ?

Pour nous, il est d’importance majeure d’assurer une redondance de nos équipements. Ainsi, nos zones disposent de deux pare-feux configurés en actif/passif (master/slave). Le pare-feu actif est en charge de tout le trafic de sa zone, tandis que le passif a une fonction de back-up : il récupèrera le trafic si l’équipement actif tombe en panne. Le seul inconvénient de cette configuration est qu’elle exige beaucoup de procédures, assez lourdes, afin de s’assurer que le pare-feu passif aura la capacité de prendre en charge le trafic si besoin, via notamment des tests de redondance nombreux et récurrents.

Comment nous avons amélioré la performance de nos pare-feux

Ce choix d’un pare-feu mutualisé nous permet donc de nous libérer de la contrainte consistant à devoir modifier, un par un, un grand nombre de pare-feux en cas de changement de configuration. Cependant, en termes de ressources, le challenge est plus important : les pare-feux doivent avoir la capacité d’assumer l’ensemble du trafic des sites qu’ils protègent. Pour qu’ils aient cette capacité, nous leurs avons appliqué plusieurs optimisations, que nous allons détailler ci-dessous.

Optimisation des équipements

Les pare-feux utilisent énormément de temps de calcul du CPU (appelé dans la suite de l’article « temps CPU »). Or, si le CPU d’un pare-feu est saturé, il va lâcher tous les paquets qu’il traitait ou que l’on lui envoie, ce qui représente une perte considérable : il est donc vital de faire en sorte que le CPU ne soit pas sur-utilisé. Pour cela, nous avons optimisé nos CPU, mais également notre carte réseau.

En effet, ce qui pèse le plus, ce ne sont pas la lecture et l’interprétation des paquets, mais bien les interruptions de carte réseau. A chaque paquet reçu, la carte réseau va littéralement interrompre le CPU : elle le prévient qu’elle a reçu un paquet, qu’elle va l’ouvrir et qu’elle aura besoin de lui pour le lire et l’interpréter. Le CPU est alors en attente, bloqué, il ne peut pas réaliser d’autres actions tant qu’elle ne lui a rien renvoyé. Sachant que chaque paquet implique une interruption, le temps de traitement s’étend rapidement…

Ainsi, la qualité et les caractéristiques à la fois de la carte réseau et du/des CPU comptent. Optimiser uniquement la carte réseau sans travailler sur le(s) CPU est inutile, et inversement : nous allons voir pourquoi.

Nous avons choisi des CPU de 3GHz (GigaHertz) : cela correspond à 3 milliards d’opérations par seconde, ce qui permet de traiter rapidement les Configuration des files de traitementcalculs et notamment les interruptions. Mais ce n’est pas tout : il faut mettre en relation cette capacité de calcul avec le nombre de CPU présents dans la machine, ainsi que le nombre et les caractéristiques de leurs cores.

En l’occurrence, nous avons installé sur nos pare-feux deux CPU à 6 cores hyper-threadés. Cette caractéristique d’hyper-threading consiste à placer, sur chaque core, deux processeurs ; ainsi chaque core a la capacité de traiter deux tâches en parallèle. Cela nous amène à bénéficier de 24 threads ou fils d’exécution, par pare-feu. Les 2 CPU offrent donc, au total, 24 files de traitement (une pour chaque fil d’exécution).

Mais cela ne suffit pas en soi. En effet, si les calculs « simples » se répartissent automatiquement sur les 24 fils d’exécution, les interruptions, elles, ne suivent pas le même fonctionnement. Les interruptions générées par une carte réseau classique ne sont, par défaut, traitées que par un seul fil d’exécution : elles ne proposent qu’une file de traitement. Si l’on ne change rien, on n’a donc aucun intérêt à disposer de plusieurs fils d’exécution sur le CPU, puisque ce sera toujours le même qui sera choisi par la carte réseau, et l’interruption suivante attendra qu’il se libère pour être traitée. Or, toutes les cartes réseau récentes offrent la possibilité de multiplier les files de traitement. Ainsi, nous avons configuré nos deux cartes réseaux (une par CPU) de manière à proposer chacune 12 files de traitement, pour un total de 24 permettant une correspondance optimisée entre les CPU et les cartes réseaux. Ainsi, les interruptions se répartissent sur l’ensemble des ressources, pour pouvoir être traitées en parallèle et, exploiter au maximum les capacités de nos cartes réseau, et gagner du temps CPU.

Il est également important de noter qu’il y a 4 ans, quand nous avons mis ces pare-feux en place, notre objectif était de pouvoir assumer 10G de trafic par pare-feu. Grâce à notre investissement dans des cartes réseaux de 4x10G, c’est cette dernière capacité que nous sommes en mesure d’assumer aujourd’hui sur l’ensemble de nos pare-feux.

Optimisation des règles

Comme expliqué en première partie d’article, les paquets arrivant sur les pare-feux subissent une série de tests consécutifs permettant de déterminer leur acceptation ou rejet. Cependant, les pare-feux classiques contiennent en moyenne 80 000 règles, soit 80 000 tests à effectuer, ce qui est énorme. Cela signifie, par exemple, qu’un paquet correspondant à la dernière règle aura subi 79 999 tests inutiles avant d’être enfin accepté ! C’est une grosse perte de temps CPU.

Nous souhaitions donc changer cette structure pour ne pas dépasser 200 tests par paquet afin d’accélérer le traitement de ceux-ci et de limiter la charge des CPU.

Netfilter logoNos pare-feux fonctionnent sous Linux. Nous utilisons donc un module du noyau Linux qui nous permet de mettre en place les règles de filtrage, appelé Netfilter, et sa surcouche Iptables permettant de le configurer. Or, Netfilter a la capacité de créer des sous-chaînes, nous permettant donc d’organiser la suite de règles en forme d’arbre de recherche.

Les règles de nos pare-feux sont donc structurées de la façon suivante :

– Si le paquet vient de services d’infrastructure définis (ex : monitoring) vers un port de monitoring sur n’importe quelle machine de notre parc, il sera par défaut accepté.

– S’il va vers certains services d’infrastructures définis (DNS, mail, NTP…), alors il sera également accepté, peu importe son origine.

– Table FROM : ce sont les tests de filtrage en sortie. On contrôle uniquement la source. Dans cette table se trouvent plusieurs branches de tests, qui mènent soit au refus du paquet soit à la table suivante. Aucun paquet ne peut directement être accepté dans cette table.

– Table TO : ce sont les tests de filtrage en entrée. On contrôle ici uniquement la destination du paquet. Ici également se trouvent plusieurs branches de tests, et le paquet est soit accepté, soit refusé. C’est la dernière étape de test.

Cette structure permet de chaîner correctement les différentes règles. Mais ce n’est pas tout : à l’intérieur même des tables, nous avons essayé de nombreuses possibilités afin de déterminer la configuration apportant les meilleurs résultats en termes d’optimisation. En effet, il fallait trouver un équilibre entre le nombre de tests par branches et le nombre de branches lui-même, puisque le saut d’une branche à une autre a également un coût en termes de temps CPU.

Les adresses IP sont constituées de 32 bits. C’est sur le nombre de bits testés à chaque branche que l’on a joué pour trouver la meilleure configuration possible. Par exemple, pour tester 512 000 adresses IP, le meilleur choix est de fractionner l’analyse de l’ adresse IP en tests de 3 bits par 3 bits. Cela donne 8 tests par branche, avec 8 sauts de branche en branche (match et jump / paquet), et un total de 51 tests (évaluation / paquet) maximum effectués par adresse IP. Cette configuration nous offre une performance de 4,2 millions de paquets par seconde. En comparaison, tester uniquement 2 bits par 2 bits (4 tests par branche et 39 tests au maximum, mais 11 sauts de branche en branche) fait chuter cette performance à 3,9 millions de  paquets par seconde.

Benchmark tests

Nous avons donc configuré nos règles selon les résultats obtenus lors de nos benchmarks, pour optimiser au maximum nos pare-feux. Toutes les tables sur tous les pare-feux ont la même configuration pour que le même niveau de performance soit appliqué partout.

Seul inconvénient de cette structure : même si on fait passer le nombre de tests par paquet de 80 000 à 200, le nombre de règles dépasse allègrement les 80 000 pour atteindre 750 000 ! Cela demande donc une grande quantité de RAM. En effet, les règles sont stockées directement dans un espace mémoire (RAM) dédié au module Netfilter. Heureusement, nos pare-feux possèdent cette quantité de RAM : ce sont de grosses machines, puisqu’elles gèrent chacune un grand nombre de clients.

Prenons un exemple fictif, dans lequel l’arbre de recherche est constitué de 3 branches de 6 tests chacune. On imagine ici que les deux premiers tests expliqués plus haut (concernant des cas particuliers) ont été négatifs.

Schéma tables FROM TO

A chaque règle « matchée », on passe à la branche sous-jacente jusqu’à arriver à la dernière branche. Dans notre exemple, l’IP source correspond à la règle 6.2.4, le test 6.2.4 est donc réussi et le paquet passe en table TO, où l’IP de destination sera à son tour examinée de la même manière. Seule différence : à la dernière table, l’IP de destination est directement acceptée, puisqu’il n’y a plus de table où l’envoyer.

Ainsi, trois réponses sont possibles pour chaque test des tables FROM et TO : passage à la branche de tests sous-jacente, ACCEPT (ce qui se traduit, pour la table FROM, à un passage à la table TO), et DROP (abandon du paquet, qui peut arriver soit directement si une règle est matchée ou bien si, à la fin de tous les tests, l’adresse IP n’a matché aucune règle).

Optimisation du tracking de connexion

Une dernière optimisation a été effectuée. Comme indiqué dans la présentation générale des pare-feux, ces derniers sont en général stateful, c’est-à-dire qu’ils enregistrent les connexions entre les entités échangeant des paquets (conntrack). Cet état est très utile pour le pare-feu, mais coûte cher en temps CPU (recherche de l’existence de la connexion dans le conntrack) et en RAM (stockage des données).

Ainsi avons-nous décidé de passer certaines règles de nos pare-feux en « stateless », c’est-à-dire de passer de conntrack à « notrack » certaines connexions. Dans ce cas, les connexions ne sont pas enregistrées par les pare-feux, qui sont donc dans un état hybride.

Nos pare-feux n’enregistrent pas les connexions qui n’ont pas besoin de NAT, ce qui correspond à toutes les requêtes ayant pour destination nos adresses IP de services. En effet, celles-ci (comme on va le voir) sont configurées sur nos load balancers, le pare-feu n’a donc aucun changement d’adresse à effectuer, sa seule mission est d’analyser le paquet et de l’envoyer. Mais comment, dans ce cas, nos pare-feux peuvent-ils repérer les réponses aux requêtes, si le passage de celles-ci n’a pas préalablement été enregistré ? Ils sont simplement configurés de telle manière que les paquets provenant de nos services sont acceptés par défaut : ainsi, même s’il ne sait pas que telle réponse correspond à telle requête, il la laissera passer.

Grâce à cette décision, nous avons réduit nos enregistrements à 20 000 connexions, contre 1 million auparavant. C’est un gros gain de place sur la RAM, et nous permet de ne pas saturer le CPU avec des opérations non nécessaires.

Load balancer ou répartiteur de charge

Généralement : fonctionnement en NAT

Le rôle d’un répartiteur de charge (load balancer) est, comme son nom l’indique, de répartir la charge sur les différents équipements en aval. Il va permettre, par exemple dans le cas d’un site hébergé sur plusieurs serveurs, d’équilibrer le trafic entre ces différents équipements afin de ne pas se trouver dans une situation où l’un des serveurs est surchargé et ne peut pas traiter toutes les demandes tandis qu’un autre n’est pas sollicité.

En général, les répartiteurs de charge utilisent, pour fonctionner, le NAT.

Load Balancing NATImaginons* une machine A (IPa) souhaitant appeler le service B (VIPb pour Virtual IP) présent sur les serveurs X et Y (dont les adresse IP privées, IPx et IPy, ne sont pas non plus connues de l’Internet). Le paquet envoyé aura pour IP source l’IP de A (IPa), et l’IP de destination VIPb. Pour l’instant, on considère que le paquet a atteint sa destination, puisqu’il a atteint le répartiteur de charge d’adresse VIPb. Or celui-ci sait que le service B est hébergé sur les serveurs X et Y, à qui il doit donc envoyer le paquet pour que la requête puisse être traitée.

C’est là que se joue le NAT : le répartiteur de charge va modifier l’IP de destination (VIPb) par IPx ou IPy, selon la charge sur chacun des serveurs, pour qu’ils traitent la requête.

Il va également remplacer l’IP source (IPa) par sa propre IP (IPp pour IP propre), afin que les réponses lui reviennent. En effet, sans cela, le pare-feu ne reconnaîtrait pas le paquet renvoyé par le serveur X ou Y comme une réponse : il aurait envoyé un paquet de IPa vers VIPb mais recevrait en retour un paquet envoyé par IPx/IPy ! Le paquet serait donc refusé, et A n’aurait jamais sa réponse. En modifiant l’IP source et en recevant lui-même sa réponse, le répartiteur de charge a l’opportunité de changer à nouveau les adresses source et destination afin de les faire correspondre à la réponse attendue par le pare-feu (IP source : VIPb, IP destination : IPa), qui validera donc le paquet.

L’inconvénient de cette méthode est qu’elle consomme un nombre d’adresses IP colossal : en effet, on compte une adresse IP par machine dédiée au routage (en plus de l’IP physique réservée à l’administration de la machine). Ce nombre est encore multiplié si l’on définit une IP par service, comme chez NBS System !

Chez NBS System : fonctionnement en Direct Routing

Chez NBS System, nous avons décidé de fonctionner en Direct Routing. Ici, ce n’est pas avec les IP que l’on joue mais avec les adresses physiques des machines (adresse MAC). Chaque carte réseau possède une adresse MAC unique, donc chaque machine possède une ou plusieurs adresses MAC uniques qui la définissent. Avec le Direct Routing, on va, sur le même principe que la NAT, modifier les paquets, mais en réécrivant l’adresse MAC plutôt que les adresse IP.

Load Balancer Direct RoutingSi l’on reprend l’exemple de tout à l’heure* : on a toujours notre source A avec l’IPa, et notre VIPb correspondant au service demandé. Ce qui change dans cette configuration, c’est que la VIPb est montée sur le répartiteur de charge, mais également sur les serveurs X et Y, qui n’ont donc pas d’IP propre destinée au web. X et Y ont donc la même IP ; il est impossible pour le répartiteur de charge de les différencier par ce biais. C’est pour cela qu’il va utiliser leur adresse MAC ! Celle-ci n’est à l’origine pas renseignée dans le paquet, car inconnue de A.

En effet, l’adresse MAC d’une machine n’est connue que des autres machines présentes sur son propre réseau. Le répartiteur de charge, que l’on a placé dans tous nos réseaux internes et qui connaît donc les MAC de tous les équipements de NBS System, va donc la renseigner. Le paquet arrivera sur le serveur avec la bonne adresse IP et la bonne adresse MAC de destination, et le tour est joué !

Une autre différence entre la répartition de charge utilisant NAT et celle utilisant le Direct Routing est la symétrie du routage : dans ce dernier cas, elle est en effet asymétrique, comme cela est visible sur le schéma. Puisque le répartiteur de charge n’a pas touché aux IP, le paquet de retour aura pour source VIPb et pour destination IPa. La réponse ne passe donc pas par le répartiteur de charge, mais sera directement renvoyée à A, via bien sûr le pare-feu, qui filtre tous les échanges (nous ne mentionnons pas, ici, les équipements intermédiaires possibles tels que les Reverse Proxies).

Rien que dans cet exemple, on divise déjà par 3 le nombre d’adresses IP utilisées. Le choix du Direct Routing nous permet donc de rationaliser l’utilisation de nos adresses IP.

Configuration

Les pare-feux et les load balancers sont disposés dans notre infrastructure comme un ensemble. Tout le système de routage de ces équipements est assuré et automatisé par le protocole BGP (Border Gateway Protocol) en interne. Ainsi, si l’on ajoute un service sur l’infrastructure (avec une nouvelle VIP), le BGP va l’annoncer automatiquement et les équipements en seront immédiatement informés !

Cependant, ce fonctionnement exige que les pare-feux et les load balancers aient la même configuration, pour qu’ils puissent bien travailler ensemble. Cette configuration est créée par l’intermédiaire d’un automate. L’administrateur écrira, dans cet automate, ce qu’il souhaite en langage compréhensible par l’humain. L’automate créera et validera alors la configuration adaptée, en langage compréhensible par la machine.

En effet, nous considérons qu’un système mis en place dans une infrastructure n’est efficace que s’il est compréhensible par l’humain. Dans ce cas, c’est un confort pour l’administrateur, mais également pour les personnes qui exploitent les équipements : ils peuvent appliquer des instructions contrôlées, et s’assurer facilement de la stabilité et sécurité d’exploitation de ces équipements.

Après notre article sur les reverse proxies, vous connaissez maintenant comment fonctionnent les pare-feux et les load balancers chez NBS System ! A bientôt pour le prochain article…

 

* Attention : les choix des noms des IP utilisés dans les exemples ne font en aucun cas office de notation de référence générale. Nous avons choisi de les nommer ainsi, dans cet article, pour faciliter la compréhension du lecteur.

Source technique : Denis Pompilio

Lucie Saunois
Lucie Saunois
Passionnée d'informatique, en particulier de sécurité, depuis qu'elle a rejoint l'OT Group en 2015, Lucie se spécialise dans la vulgarisation technique pour permettre à tous d'appréhender ces sujets parfois complexes.