Comment limiter la fréquence des demandes dans HAProxy: instructions pas à pas


L'auteur explique comment implémenter dans HAProxy une limite de vitesse de requête (limitation de débit) avec certaines adresses IP. L'équipe Mail.ru Cloud Solutions a traduit son article - nous espérons que vous n'aurez pas à y consacrer autant de temps et d'efforts que vous ne l'avez dû.

Le fait est que c'est l'une des méthodes les plus populaires pour protéger un serveur contre les attaques DoS, mais il est difficile de trouver des instructions claires sur Internet pour le configurer spécifiquement. Par essais et erreurs, l'auteur a contraint HAProxy à limiter la fréquence des requêtes sur une liste d'adresses IP, qui est mise à jour en temps réel.

Aucune connaissance préalable n'est requise pour configurer HAProxy, car toutes les étapes nécessaires sont décrites ci-dessous.

Open source et gratuit HAProxy est un équilibreur de charge et un serveur proxy hautement accessibles. Ces dernières années, il est devenu très populaire car il offre des performances élevées avec un minimum de ressources. Contrairement aux programmes alternatifs, la version à but non lucratif de HAProxy Community Edition offre suffisamment de fonctionnalités pour un équilibrage de charge fiable.

Ce programme est au début assez difficile à comprendre. Cependant, elle dispose d'une documentation technique très méticuleuse et détaillée . L'auteur dit que c'est la documentation la plus détaillée parmi tous les programmes open source qu'il ait jamais utilisés.
Voici donc un guide étape par étape.

Configuration d'un équilibreur de charge


Pour gagner du temps et ne pas vous laisser distraire par la configuration de l'infrastructure, prenez les images Docker et Docker Compose et lancez rapidement les principaux composants.

La première tâche consiste à augmenter l'instance de travail de l'équilibreur de charge HAProxy avec plusieurs serveurs principaux Apache.

Clonez le référentiel


$ git clone git@github.com:stargazer/haproxy-ratelimiter.git
$ cd haproxy-ratelimiter

Vous pouvez consulter Dockerfileet docker-compose.ymlavec les paramètres d'installation. Leur discussion dépasse le cadre de cet article, nous allons donc nous attarder sur le fait qu'ils ont créé une instance HAProxy fonctionnelle appelée loadbalancerdeux serveurs principaux api01et api02. Pour configurer HAProxy, nous allons d'abord utiliser le fichier haproxy-basic.cfg, puis basculer vers haproxy-ratelimiting.cfg.

Par souci de simplicité, le fichier de configuration a été haproxy-basic.cfgréduit au strict minimum et débarrassé de tout excès. Regardons ça:

defaults
mode http
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms

frontend proxy
bind *:80

use_backend api

backend api
balance roundrobin

server api01 api01:80
server api02 api02:80

La section frontend proxydéfinit HAProxy pour écouter sur le port 80 et transmettre toutes les demandes au pool de serveurs apisur le backend.

CATÉGORIE backend apispécifie le pool principal apiavec deux serveurs principaux api01et api02et les adresses correspondantes. Le serveur pour traiter chaque demande entrante est sélectionné par l'algorithme d'équilibrage de charge roundrobin, c'est-à-dire que les deux serveurs disponibles sont utilisés tour à tour.

Lançons les trois de nos conteneurs


$ sudo docker-compose up

Nous avons maintenant un conteneur loadbalancerqui redirige les demandes vers deux serveurs principaux api01et serveurs api02. Nous obtiendrons une réponse de l'un d'eux si nous entrons dans la barre d'adresse http://localhost/.

Il est intéressant de rafraîchir la page plusieurs fois et de consulter les journaux docker-compose.

api01_1 | 192.168.192.3 - - [08 / Jan / 2019: 11: 38: 09 +0000] "GET / HTTP / 1.1" 200 45
api02_1 | 192.168.192.3 - - [08 / Jan / 2019: 11: 38: 10 +0000] "GET / HTTP / 1.1" 304 -
api01_1 | 192.168.192.3 - - [08 / Jan / 2019: 11: 38: 10 +0000] "GET / HTTP / 1.1" 304 -
api02_1 | 192.168.192.3 - - [08 / Jan / 2019: 11: 38: 11 +0000] "GET / HTTP / 1.1" 304 -
api01_1 | 192.168.192.3 - - [08 / Jan / 2019: 11: 38: 11 +0000] "GET / HTTP / 1.1" 304 -
api02_1 | 192.168.192.3 - - [08 / Jan / 2019: 11: 38: 11 +0000] "GET / HTTP / 1.1" 304 -

Comme vous pouvez le voir, deux serveurs apitraitent les demandes à tour de rôle.

Nous avons maintenant une instance HAProxy avec une configuration d'équilibrage de charge très simple, et nous avons une idée de son fonctionnement.

Ajouter une limite au nombre de demandes


Pour définir une limite sur le nombre de demandes à l'équilibreur de charge, vous devez modifier le fichier de configuration dans l'instance HAProxy. Assurez-vous que le conteneur loadbalancerutilise le fichier de configuration haproxy-ratelimiter.cfg.

Modifiez simplement le Dockerfile pour remplacer le fichier de configuration.

FROM haproxy:1.7
COPY haproxy-ratelimiter.cfg /usr/local/etc/haproxy/haproxy.cfg

Fixer des limites


Tous les paramètres sont enregistrés dans le fichier de configuration haproxy-ratelimiter.cfg. Étudions-le attentivement.

defaults
mode http
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms

frontend proxy
bind *:80

# ACL function declarations
acl is_abuse src_http_req_rate(Abuse) ge 10
acl inc_abuse_cnt src_inc_gpc0(Abuse) gt 0
acl abuse_cnt src_get_gpc0(Abuse) gt 0

# Rules
tcp-request connection track-sc0 src table Abuse
tcp-request connection reject if abuse_cnt
http-request deny if abuse_cnt
http-request deny if is_abuse inc_abuse_cnt

use_backend api
backend api
balance roundrobin

server api01 api01:80
server api02 api02:80

backend Abuse
stick-table type ip size 100K expire 30m store gpc0,http_req_rate(10s)

HAProxy propose un ensemble de primitives de bas niveau qui offrent plus de flexibilité et conviennent à une variété de cas d'utilisation. Ses compteurs me rappellent souvent le registre cumulatif (additionneur) du CPU. Ils stockent des résultats intermédiaires, prennent différentes sémantiques en entrée, mais au final ce ne sont que des nombres. Pour bien comprendre tout, il est logique de commencer à la toute fin du fichier de configuration.

Table Abuse


backend Abuse
stick-table type ip size 100K expire 30m store gpc0,http_req_rate(10s)


Ici, nous avons mis en place un backend factice appelé Abuse(«abus»). Fictif, car il n'est utilisé que pour stick-table, auquel le reste de la configuration peut faire référence par son nom Abuse. Un stick-table est une table stockée dans la mémoire de processus, où pour chaque enregistrement, vous pouvez déterminer la durée de vie.

Notre table présente les caractéristiques suivantes:

  • type ip: Les demandes sont enregistrées dans le tableau par adresse IP sous forme de clé. Ainsi, les demandes provenant de la même adresse IP feront référence au même enregistrement. Essentiellement, cela signifie que nous suivons les adresses IP et les données connexes.
  • size 100K: Le tableau contient un maximum de 100 000 enregistrements.
  • expire 30m: La période de conservation des enregistrements est de 30 minutes d'inactivité.
  • store gpc0,http_req_rate(10s): Le compteur gpc0et le nombre de demandes d'adresses IP pour les 10 dernières secondes sont stockés avec des entrées . Avec l'aide, gpc0nous suivrons combien de fois l'adresse IP est remarquée dans les abus. En fait, une valeur de compteur positive signifie que l'adresse IP est déjà marquée comme suspecte. Appelons ce compteur abuse indicator.

En général, le tableau Abusevous permet de suivre si l'adresse IP est marquée comme suspecte, ainsi que la fréquence actuelle des demandes de cette adresse. Par conséquent, nous avons un historique des enregistrements, ainsi que des informations en temps réel.

Passons maintenant à la section frontend proxyet voyons les nouveautés.

Fonctions et règles ACL


frontend proxy
bind *:80

# ACL function declarations
acl is_abuse src_http_req_rate(Abuse) ge 10
acl inc_abuse_cnt src_inc_gpc0(Abuse) gt 0
acl abuse_cnt src_get_gpc0(Abuse) gt 0

# Rules
tcp-request connection track-sc0 src table Abuse
tcp-request connection reject if abuse_cnt
http-request deny if abuse_cnt
http-request deny if is_abuse inc_abuse_cnt

use_backend api

Les listes de contrôle d'accès (ACL) sont des déclarations de fonctions qui sont appelées uniquement lorsque la règle est définie.

Examinons de plus près les trois entrées ACL. Gardez à l'esprit qu'ils font tous explicitement référence à une table Abusequi utilise des adresses IP comme clé, de sorte que chaque fonction s'applique à l'adresse IP de la demande:

  • acl is_abuse src_http_req_rate(Abuse) ge 10: La fonction is_abuseretourne Truesi la fréquence de requête actuelle est supérieure ou égale à dix.
  • acl inc_abuse_cnt src_inc_gpc0(Abuse) gt 0: La fonction inc_abuse_cntrenvoie Truesi la valeur d'incrément est gpc0supérieure à zéro. Puisque la valeur initiale gpc0est zéro, cette fonction retourne toujours True. En d'autres termes, il augmente la valeur abuse indicator, signalant essentiellement un abus de cette adresse IP.
  • acl abuse_cnt src_get_gpc0(Abuse) gt 0: La fonction abuse_cntrenvoie Truesi la valeur est gpc0supérieure à zéro. En d'autres termes, il dit si cette adresse IP a déjà été repérée dans des abus.

Comme mentionné précédemment, les ACL sont simplement des déclarations, c'est-à-dire des déclarations de fonctions. Ils ne s'appliquent pas aux demandes entrantes tant qu'une règle n'est pas déclenchée.

Il est logique de regarder les règles définies dans la même section frontend. Les règles sont appliquées à chaque demande entrante une par une - et exécutent les fonctions de l'ACL que nous venons de définir.

Voyons ce que fait chaque règle:

  • tcp-request connection track-sc0 src table Abuse: Ajoute une requête à la table Abuse. Étant donné que la clé est l'adresse IP dans le tableau, cette règle s'ajoute à la liste des adresses IP.
  • tcp-request connection reject if abuse_cnt: TCP-, IP- , abuse. , TCP- IP-.
  • http-request deny if abuse_cnt: , IP- . IP-, abuse.
  • http-request deny if is_abuse inc_abuse_cnt: , is_abuse inc_abuse_cnt True. , , IP- , IP- .

En substance, nous introduisons deux types de contrôles: en temps réel et dans la liste noire de l'historique des requêtes. La deuxième règle rejette toutes les nouvelles connexions TCP si l'adresse IP a été remarquée lors d'abus. La troisième règle interdit le service des requêtes HTTP pour une adresse IP de la liste noire, quelle que soit la fréquence actuelle des requêtes de cette adresse. La quatrième règle garantit que les demandes HTTP provenant d'une adresse IP sont rejetées au moment même dès que le seuil de fréquence des demandes est dépassé. Ainsi, la deuxième règle fonctionne principalement sur les nouvelles connexions TCP, la troisième et la quatrième sur les connexions déjà établies, la première étant une vérification historique et la seconde une vérification en temps réel.

Essayons le filtre en action!


Nous pouvons maintenant assembler et lancer à nouveau nos conteneurs.

$ sudo docker-compose down
$ sudo docker-compose build
$ sudo docker-compose up

L'équilibreur de charge doit démarrer avant les deux serveurs principaux.

Dirigeons notre navigateur vers http://localhost/. Si nous actualisons rapidement la page une douzaine de fois, nous dépasserons le seuil de dix demandes dans un intervalle de dix secondes - et nos demandes seront rejetées. Si nous continuons à actualiser la page, les nouvelles demandes seront rejetées immédiatement - avant même que la connexion TCP ne soit établie.

Des questions


Pourquoi la limite de dix demandes par dix secondes?


Le tableau Abusedétermine http_req_rate(10s), c'est-à-dire que la fréquence des demandes est mesurée dans une fenêtre de dix secondes. Une fonction is_abusede l'ACL revient Trueà un taux de demande ≥10 dans l'intervalle spécifié. Ainsi, l'abus est considéré comme la fréquence des demandes de dix demandes ou plus en dix secondes.

Dans cet article, par exemple, nous avons décidé de définir une limite basse pour faciliter le contrôle du fonctionnement du limiteur.

Quelle est la différence entre les règles de connexion http-request et tcp-request?


De la documentation :

http-request: l'instruction http-request définit un ensemble de règles qui s'appliquent à la couche réseau 7 [modèle OSI]

De la documentation :
connexion tcp-request: exécution d'une action sur une connexion entrante en fonction d'une condition au niveau de la couche réseau 4

HTTP-, TCP-?


Imaginez que les requêtes HTTP au serveur envoient plusieurs connexions TCP à partir de la même adresse IP. La fréquence des requêtes HTTP dépassera rapidement les seuils. C'est alors que la quatrième règle entre en vigueur, qui rejette les demandes et met l'adresse IP sur liste noire.

Maintenant, il est tout à fait possible que les connexions HTTP à partir de la même adresse IP restent ouvertes (voir connexion HTTP persistante ), et la fréquence des demandes HTTP est tombée en dessous de la valeur seuil. La troisième règle garantit le blocage continu des requêtes HTTP, car il est abuse indicatordéclenché sur cette IP.

Supposons maintenant qu'après quelques minutes, la même IP tente d'établir des connexions TCP. Ils sont abandonnés immédiatement, car la deuxième règle s'applique: il voit l'adresse IP étiquetée et abandonne immédiatement la connexion.

Conclusion


Au début, il peut être difficile de comprendre la limitation de la vitesse de traitement des demandes à l'aide de HAProxy. Pour tout faire correctement, vous avez besoin d'une réflexion assez "de bas niveau" et d'un certain nombre d'actions non intuitives. La documentation de cette partie est probablement trop technique et souffre de l'absence d'exemples de base. Nous espérons que ce guide comblera la lacune et montrera la direction à tous ceux qui souhaitent emprunter cette voie.

Quoi d'autre à lire :

  1. Comment l'architecture tolérante aux pannes est implémentée dans la plateforme Mail.ru Cloud Solutions .
  2. Les 10 meilleurs trucs et astuces de Kubernetes .
  3. Notre chaîne Telegram sur la transformation numérique .

All Articles