🚨 Cette édition est relativement longue, votre boîte mail risque de la tronquer. Je vous conseille de cliquer sur le titre ci-dessus pour l’ouvrir dans votre navigateur web. ☝️
Au programme aujourd’hui :
Protéger sans ralentir : tests optimisés avec Rack Attack par Ines
Temps de lecture : 10 minutes
Hello les petits Biscuits !
Bienvenue sur la 26ème édition de Ruby Biscuit.
Vous êtes maintenant 530 abonnés 🥳
Maintenant Ruby biscuit, c’est aussi votre meilleur allié pour recruter des devs Ruby !
Si vous n’avez pas encore rejoint le club, RDV sur https://recrutement.rubybiscuit.fr
Bonne lecture.
Protéger sans ralentir : tests optimisés avec Rack Attack
En tant que développeuse junior, lorsque j'ai tenté d'implémenter Rack Attack et surtout d'accélérer les tests que j'avais rédigés, l'expérience s'est révélée être un véritable casse-tête. Il y a très peu d'informations là-dessus sur internet et j'ai eu du mal à comprendre certains aspects de l'utilisation du cache de Rack Attack afin de pouvoir les mocker*. J'ai souhaité écrire un article sur le sujet pour vous épargner ces difficultés.
* Mock : En programmation orientée objet, les mocks sont des objets simulés qui reproduisent le comportement d'objets réels de manière contrôlée.
Rack Attack est une gem en Ruby qui implémente un middleware. Elle est particulièrement utile pour protéger les applications Ruby en Rails contre certaines attaques :
par force brute : un attaquant essaye chaque combinaison possible de mot de passe pour un nom d'utilisateur/email, afin d'obtenir l'accès au service ciblé
par bourrage d'identifiants (credential stuffing) : un attaquant tente une liste d'identifiants compromis récupérés dans des fuites de données, toujours dans l'objectif d'accéder au service ciblé
par déni de service (Denial of Service) : un attaquant submerge votre service de requêtes jusqu'à ce que le traffic normal ne puisse être traité
Rack Attack se positionne entre votre application et les requêtes HTTP brutes que celles-ci reçoit, et vous permet d'intercepter celles ci : définir des règles de limitation de requêtes, bloquer les IP suspectes ou abusives et même mettre en place des listes blanches et noires. Rack Attack utilise moins de ressources par requête que votre application Rails, ce qui lui permet de ne pas ralentir les requêtes tout en les filtrant.
Aujourd'hui, nous allons voir :
Un exemple simple de configuration Rack Attack
Des tests basiques
Une explication détaillée des interactions de Rack Attack avec le cache
Des tests ultra-rapides, indépendants du nombre de requêtes autorisées
Quelques recommendations pour utiliser Rack Attack
Configuration
Pour simplifier cette configuration, nous allons nous concentrer sur les envois de formulaires publics, c'est-à-dire les requêtes POST où un formulaire est généralement envoyé au serveur.
En effet, c'est en spammant le formulaire de login qu'une attaque par force brute peut être menée, et lors d'une attaque de type Denial of Service (DoS), les requêtes POST sont bien plus coûteuses pour le serveur que les requêtes GET, puisqu'il ne s'agit pas seulement de répondre à une demande, mais aussi de traiter les données reçues.
Installation
Ajouter la gem au Gemfile :
gem 'rack-attack'
Ensuite, exécutez la commande suivante pour installer la gem :
bundle install
Configuration Rack Attack
# config/initializers/rack_attack.rb | |
class Rack::Attack | |
# Configuration for Rack::Attack | |
BAN_TIME = 10.minutes.freeze | |
# GET requests | |
MAX_ATTEMPTS_GET = 20 | |
OBSERVATION_TIME_GET = 1.minute | |
PUBLIC_PATHS_GET = ["/", "/sign_in", "/sign_up"].freeze | |
# POST requests | |
PUBLIC_PATHS_POST = ["/sign_in", "/password", "/sign_up"].freeze | |
MAX_ATTEMPTS_POST = 25 | |
OBSERVATION_TIME_POST = 2.minutes | |
# Blocks GET requests | |
# Blocks for BAN_TIME after MAX_ATTEMPTS_GET requests in OBSERVATION_TIME_GET | |
Rack::Attack.blocklist("block abusive get requests") do |req| | |
Rack::Attack::Allow2Ban.filter("public-get:#{req.ip}", maxretry: MAX_ATTEMPTS_GET, | |
findtime: OBSERVATION_TIME_GET, | |
bantime: BAN_TIME) do | |
req.get? && PUBLIC_PATHS_GET.include?(req.path) | |
end | |
end | |
# Blocks POST requests | |
# Blocks for BAN_TIME after MAX_ATTEMPTS_POST requests in OBSERVATION_TIME_POST | |
Rack::Attack.blocklist("block abusive post requests") do |req| | |
Rack::Attack::Allow2Ban.filter("public-post:#{req.ip}", maxretry: MAX_ATTEMPTS_POST, | |
findtime: OBSERVATION_TIME_POST, | |
bantime: BAN_TIME) do | |
req.post? && PUBLIC_PATHS_POST.include?(req.path) | |
end | |
end | |
# Custom response for blocked requests | |
BLOCKED_HTTP_CODE = 503 | |
Rack::Attack.blocklisted_responder = lambda do |request| | |
[BLOCKED_HTTP_CODE, {}, []] | |
end | |
end |
Si vous n'avez jamais utilisé Rack Attack, pas de panique, on va détailler tout ça.
Créer la classe Rack::Attack
Paramètres
On définit tout d'abord les paramètres du filtre dans l'initializer :
Note : Dans cet article, j'utilise une limite de tentatives très basse pour simplifier les explications. Je recommande d'utiliser en réalité une limite bien moins sévère : 50 tentatives en une minute par exemple.
Sélecteurs
Les sélecteurs sont les caractéristiques des requêtes qui passeront par les filtres. Ils déterminent quelles seront les requêtes concernées par les limitations.
Puis on choisit le ou les sélecteurs. Nous sélectionnons donc la méthode : POST
et le chemin : PUBLIC_PATHS_POST
, on obtient le sélecteur suivant :
Dans cet exemple, seules les requêtes vers l'un des chemins spécifiés et avec pour méthode post, seront limitées. Les requêtes avec pour méthode get ne seront pas affectées.
Note : il existe de nombreux autres sélecteurs possibles (params, url, user-agent, content_length, body...). Vous trouverez ici la liste des clés utilisables pour request.env et ici leur implémentation (et traduction) par Rack.
Filtre
Avec notre nom, nos paramètres et nos sélecteurs nous pouvons construire le filtre par lequel passeront les requêtes choisies.
Blocklist
Lorsque, pour une ip donnée, l’utilisateur atteint MAX_ATTEMPTS_POST
requêtes POST
durant OBSERVATION_TIME_POST
sur l’ensemble (ou une partie) des PUBLICS_PATH_POST
, nous souhaitons qu’il soit bloqué de l’ensemble de l’application : nous plaçons donc notre filtre dans une blocklist inconditionnelle.
Note : Il est possible de n’appliquer le blocage qu’à certains types de requêtes de même que pour les filtres.
Un second filtre pour l'exemple
Pour l’exemple et afin de mieux comprendre le fonctionnement de Rack Attack, nous allons définir un autre filtre sur les requêtes GET sur des chemins publics, défini avant le filtre sur les requêtes post dans notre fichier de configuration (cela aura son importance pour la suite).
Réponse personnalisée
Il est possible de définir une réponse HTTP personnalisée pour les requêtes bloquées, la réponse par défaut étant un 403 (Forbidden) pour les blocklists et 429 (Too many requests) pour les throttles.
J'ai fait le choix dans cette configuration d'un code 503 sans corps pour une raison très simple. Le code 503 est habituellement utilisé par une plateforme pour signifier qu'elle est hors-service et ne peut traiter les requêtes entrantes. C'est une façon de simuler un crash suite à une éventuelle attaque par DoS (un peu comme un opossum qui feindrait la mort pour décourager un prédateur peu minutieux).
Tester la configuration
Maintenant qu'on a correctement configuré Rack Attack, testons ce que l'on a implémenté.
L'idée est de vérifier que les IPs sont bien bloquées après un certain nombre de tentatives, puis que le déblocage se fait correctement une fois le délai de bannissement écoulé.
Nous commencerons par des tests simples et fonctionnels mais lents, puis nous verrons comment les améliorer.
Tests triviaux
Première approche des tests de Rack Attack
require "rails_helper" | |
RSpec.describe "Rack::Attack", type: :request do | |
before(:all) do | |
Rack::Attack.enabled = true | |
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new | |
end | |
after(:all) do | |
Rack::Attack.enabled = false | |
end | |
before(:each) do | |
Rack::Attack.cache.store.clear # Avoid tests overlaps | |
end | |
let(:ban_time) { Rack::Attack::BAN_TIME } | |
let(:store) { Rack::Attack.cache.store } | |
let(:filters_list_regex) { /public-(post|get)/i } | |
let(:ip) { "1.2.3.4" } | |
# Tester que Rack Attack bloque seulement après MAX_ATTEMPTS tentatives | |
shared_examples "rate limiting and IP blocking" do |http_method, path, max_attempts, filter| | |
it "only blocks IP after MAX_ATTEMPTS #{http_method} requests", aggregate_failures: true do | |
max_attempts.times do | |
public_send( | |
http_method, | |
path, | |
params: nil, | |
headers: { "REMOTE_ADDR" => ip } | |
) | |
expect(response.status).not_to eq(503) | |
end | |
public_send( | |
http_method, | |
path, | |
params: nil, | |
headers: { "REMOTE_ADDR" => ip } | |
) | |
expect(response.status).to eq(503) | |
end | |
end | |
# Tester que Rack Attack débloque après BAN TIME | |
shared_examples "IP unblocking" do |http_method, path, max_attempts, filter| | |
before do | |
(max_attempts + 1).times do | |
public_send( | |
http_method, | |
path, | |
params: nil, | |
headers: { "REMOTE_ADDR" => ip } | |
) | |
end | |
end | |
it "unblock IP after BAN_TIME for #{http_method} requests" do | |
travel_to(Time.current + ban_time + 1.minute) do | |
public_send( | |
http_method, path, | |
params: nil, | |
headers: { "REMOTE_ADDR" => ip } | |
) | |
expect(response.status).not_to eq(503) | |
end | |
end | |
end | |
describe "POST requests" do | |
context "when the ip is not banned" do | |
it_behaves_like "rate limiting and IP blocking", :post, Rack::Attack::PUBLIC_PATHS_POST.first, | |
Rack::Attack::MAX_ATTEMPTS_POST, "public-post" | |
end | |
context "when the ip is banned" do | |
it_behaves_like "IP unblocking", "post", Rack::Attack::PUBLIC_PATHS_POST.first, Rack::Attack::MAX_ATTEMPTS_POST, "public-post" | |
end | |
end | |
describe "GET requests" do | |
context "when the ip is not banned" do | |
it_behaves_like "rate limiting and IP blocking", :get, Rack::Attack::PUBLIC_PATHS_GET.first, | |
Rack::Attack::MAX_ATTEMPTS_GET, "public-get" | |
end | |
context "when the ip is banned" do | |
it_behaves_like "IP unblocking", "get", Rack::Attack::PUBLIC_PATHS_GET.first, Rack::Attack::MAX_ATTEMPTS_GET, "public-get" | |
end | |
end | |
end |
Limites des tests triviaux
De tels tests fonctionnent, et permettent effectivement de vérifier que la configuration Rack Attack a été correctement réalisée. Cependant, leur durée dépend du nombre de requêtes autorisées par chaque filtre, puisqu’il faut en simuler autant pour observer le blocage.
Ces tests peuvent donc s’avérer très longs lorsque l’on souhaite utiliser des périodes d’observation plus longues avec des nombres de tentatives maximales pour un filtre élevées ou lorsqu’il y a plusieurs filtres à tester (jusqu’à 15 secondes pour un seul filtre testé avec 100 requêtes GET autorisées sur un ordinateur relativement puissant).
De la même façon, le succès du test dépend dans ce contexte de la vitesse d'exécution de l'environnement où le test est lancé. Si les requêtes mettent trop de temps à s'exécuter on peut ne jamais atteindre la limite durant le temps d'observation. Et si l'on souhaite également autoriser beaucoup de requêtes sur une très courte période, le blocage est pratiquement impossible à tester : il est très difficile d'envoyer un nombre élevé de requêtes en une période très courte avec les helper fournis par RSpec (ou rack-test).
Nous allons donc voir comment les appels au cache sont faits afin de mocker le nombre de requêtes qui nous intéresse (voire directement un blocage) et rendre ainsi le temps de test indépendant du nombre de requêtes autorisées pour chaque filtre.
Comprendre les appels au cache
RackAttack utilise le cache pour stocker des informations sur les requêtes effectuées par les utilisateurs ou les adresses IP afin d'optimiser les performances et d'appliquer des règles de limitation en coûtant peu de ressources.
Afin d'illustrer les appels au cache effectués par Rack Attack pour surveiller le traffic et bloquer les clients abusifs, nous allons jouer un petit scénario. Mais d'abord, un petit point sur les clés du cache.
Génération des clés du cache
Nous observerons dans notre scénario deux types de clés utilisées par Rack Attack pour communiquer avec le cache.
Clés de blocklist
Les clés des blocklists permettent de vérifier (dans notre cas) qu'une ip donnée a été marquée dans le cache comme appartenant à une blocklist.
Clés de compteur (ou clés de filtre)
Les clés de compteur permettent à Rack Attack de garder la trace du nombre de requêtes concernées par le filtre (et donc sélectionnées par le discriminant) réalisées pour une ip durant le temps d'observation.
Elle contient notamment une clé timestamp. Cette clé est calculée au moment où la première requête pour une ip durant la période d'observation est reçue :
clé timestamp = date et heure de la première requête / durée d'observation
La clé timestamp est donc propre à une ip, et à une période d'observation.
Comprendre les interactions avec le cache
Imaginons un petit scénario pour illustrer les appels au cache de Rack Attack.
Un jeune bot naïf et malveillant accède à votre plateforme dans l'intention de récupérer les identifiants de vos utilisateurs grâce à une attaque par force brute. Il arrive sur le chemin "/sign_in", et en moins d'une seconde, tente 11 combinaisons différentes nom d'utilisateur - mot de passe. Au bout de la 11 ème, une page d'erreur s'affiche.
Le jeune bot est stoppé net : la plateforme semble hors-service. Il rentre bredouille. Pourtant, il y a actuellement de nombreux humains disciplinés qui y surfent en toute tranquillité. Comment est-ce possible ? Rembobinons.
Première requête
Lorsque la première requête est reçue par l'application (et interceptée par Rack Attack), Rack Attack consulte tout d'abord les blocklists. Il les consulte dans l'ordre dans lequel elles ont été définies dans l'initializer.
Souvenez vous : on avait défini dans notre configuration un filtre et une blocklist sur public-get avant celui sur public-post (dans l'initializer). Étants définis avant, ils seront consultés en premier, tout au long de ce scénario.
Les blocklists sont consultées et aucune entrée ne correspond à l'ip de notre bot malveillant, la requête sera donc autorisée, mais il faut quand même suivre le nombre de requêtes.
Après avoir consulté les blocklists, Rack Attack identifie que la requête correspond à celles filtrées par le filtre public-post : il consulte donc le compteur associé.
Cette fois encore, aucune entrée ne correspond à notre bot malveillant. Rack Attack génère donc un timestamp, puis ajoute une entrée qui contient l'ip du bot malveillant (1.2.3.4) :
[READ] Key: rack::attack:allow2ban:ban:public-get:1.2.3.4, Result: nil
[READ] Key: rack::attack:allow2ban:ban:public-post:1.2.3.4, Result: nil
[READ] Key: rack::attack:14376799:allow2ban:count:public-post:1.2.3.4, Result: nil
[WRITE] Key: rack::attack:14376799:allow2ban:count:public-post:1.2.3.4, Value: 1, Expires in: 19 seconds
La clé timestamp sera ensuite conservée et utilisée pour suivre les requêtes de cette ip (qui satisfont le discriminant du filtre), jusqu’à la fin du temps d’observation.
Requêtes suivantes n < 10
Notre jeune bot naïf et malveillant ne le sait pas, mais il est désormais fiché. Il continue, en toute innocence, à spammer notre page de login, et rien ne semble sortir de l'ordinaire. Pourtant il est surveillé de près.
Lorsqu'une nouvelle requête est envoyée, Rack Attack commence également par consulter toutes les blocklists concernées, celles-ci ne contiennent toujours pas l'ip correspondante : le jeune bot peut continuer à spammer le login (pour le moment).
À chacune de ses tentatives, après la lecture des blocklists, le compteur de public-post est incrémenté, mais il n'atteint pas encore la valeur seuil attendue.
[READ] Key: rack::attack:14376799:allow2ban:count:public-post:1.2.3.4, Result: 2
[WRITE] Key: rack::attack:14376799:allow2ban:count:public-post:1.2.3.4, Value: 3, Expires in: 92 seconds
10ème requête : la dernière pour notre jeune bot
Lors de la dixième tentative, les blocklists sont à nouveaux consultées et ne contiennent toujours pas l'ip recherchée et laissent donc passer de nouveau cette requête. Finiront t'elles par servir à quelque chose ?
Puis, le compteur de public-post est consulté : il renvoie 9. Il est alors incrémenté pour atteindre 10 : le maximum autorisé. C'est à ce moment que les choses tournent mal pour le jeune bot malveillant. Un nouvel appel en écriture au cache est effectué : cette fois-ci, non pas au compteur, mais bien à la blocklist de public-post. Une entrée contenant son ip y est inscrite et sera conservée durant le BAN_TIME défini plus haut.
[WRITE] Key: rack::attack:allow2ban:ban:public-post:1.2.3.4, Value: 1, Expires in: 600 seconds
Note : Les clés de compteur servent exclusivement à suivre le nombre de requêtes durant une période d'observation. La lecture d'une valeur supérieure ou égale à n-1 déclenchera l'écriture d'un bannissement qui bloquera les requêtes suivantes. Mais la lecture seule du compteur, même si elle renvoie un nombre de requêtes supérieur à celui autorisé, ne permet pas de bloquer une requête.
11ème requête :
Lors de la réception de cette dernière requête, Rack Attack consulte les blocklists : il consulte la blocklist de public-get sans succès puis il consulte la blocklist de public-post : cette fois-ci, il y trouve l'ip correspondante. Rack Attack ne prend même pas la peine de lire le compteur : cette fois-ci c'est cuit pour notre jeune bot, la requête ne passera pas. Rack Attack envoie un simple code HTTP 503.
Rappelez-vous, notre jeune bot malveillant est aussi naïf : il pense que la plateforme est hors-service, et s'arrête là. Il s'en va chercher une nouvelle victime.
Note : Dès que Rack Attack reçoit une réponse positive d'une blocklist, il refuse la requête et s'arrête là. Si l'ip du bot avait été dans la blocklist de public-get, la blocklist de public-post n'aurait même pas été consultée pour cette requête.
On peut également remarquer que le compteur n'est pas consulté pour cette requête : il n'est d'ailleurs jamais bloquant. Peu importe la valeur renvoyée par le compteur, la requête ne sera pas bloquée sans écriture dans la blocklist.
Tests rapides : simuler les appels au cache
Maintenant que nous avons compris la façon dont Rack Attack traque et banni les requêtes abusives, nous pouvons mocker les appels et écritures du cache des requêtes et vérifier que le blocage (et le déblocage) sont correctement réalisés par Rack Attack.
Au lieu de simuler l’entièreté du processus, nous allons simplement nous insérer au niveau de la n-eme requête, et faire croire à Rack Attack qu’il s’agit bien de la n-ème requête et non de la première.
Simuler n - 1 requêtes, envoyer réellement la n-ème et observer le blocage
Nous allons mocker un appel au cache en lecture : au lieu de renvoyer nil (pas de requête observée) nous souhaitons qu'il renvoie 9 (déjà n-1 requêtes observées) et expect les appels au cache en écriture et leurs conséquences dans nos tests. Les blocklists étant vides à ce stade, il n’est pas nécessaire de les mocker, elles renverront le résultat attendu.
Note : Cependant, étant donné que nous mockons un appel au cache avec des arguments spécifiques, il faut également expect un appel au cache aux blocklist et demander d’appeler l’original pour ne pas avoir de message d’erreur.
Ensuite on envoie la (pseudo) 10ème requête, celle-ci déclenche le bannissement. On envoie la 11ème et on vérifie que l’on est banni.
Une fois implémenté, voilà le résultat :
Simuler un blocage, observer le déblocage
C'est bon, on sait désormais que Rack Attack bloque correctement les requêtes abusives. Il ne reste plus qu'à vérifier que le déblocage est correctement effectué après BAN_TIME, et on a terminé.
Ce que l'on souhaite maintenant, c'est s'insérer après la n-ème tentative et le déclenchement du blocage.
On implémente cette logique :
On ajoute le setup et on obtient nos tests finaux :
require "rails_helper" | |
RSpec.describe "Rack::Attack", type: :request do | |
before(:all) do | |
Rack::Attack.enabled = true | |
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new | |
end | |
after(:all) do | |
Rack::Attack.enabled = false | |
end | |
before(:each) do | |
Rack::Attack.cache.store.clear # Avoid tests overlaps | |
end | |
let(:ban_time) { Rack::Attack::BAN_TIME } | |
let(:store) { Rack::Attack.cache.store } | |
let(:filters_list_regex) { /public-(post|get)/i } | |
let(:ip) { "1.2.3.4" } | |
# Tester que Rack Attack bloque seulement après MAX_ATTEMPTS tentatives | |
shared_examples "rate limiting and IP blocking" do |http_method, path, max_attempts, filter| | |
let(:read_count_key) { /rack::attack:\d+:allow2ban:count:#{filter}:#{ip}/ } | |
let(:read_ban_key) { /rack::attack:allow2ban:ban:#{filters_list_regex}:#{ip}/ } | |
it "only blocks IP after MAX_ATTEMPTS #{http_method} requests", aggregate_failures: true do | |
expect(store).to receive(:read).with(read_count_key, anything).and_return(max_attempts - 1) | |
expect(store).to receive(:read).with(read_ban_key).at_least(:once).and_call_original | |
public_send(http_method, path, params: nil, headers: { "REMOTE_ADDR" => ip }) | |
expect(response.status).not_to eq(503) | |
public_send(http_method, path, params: nil, headers: { "REMOTE_ADDR" => ip }) | |
expect(response.status).to eq(503) | |
end | |
end | |
let(:ip) { "1.2.3.4" } | |
let(:ban_time) { Rack::Attack::BAN_TIME } | |
# Tester que Rack Attack débloque bien après BAN_TIME | |
shared_examples "IP unblocking" do |http_method, path, filter| | |
let(:ban_key) { "rack::attack:allow2ban:ban:#{filter}:#{ip}" } | |
it "unblock IP after BAN_TIME for #{http_method} requests", aggregate_failures: true do | |
store.write(ban_key, 1, expires_in: ban_time) | |
public_send(http_method, path, params: nil, headers: { "REMOTE_ADDR" => ip }) | |
expect(response.status).to eq(503) | |
travel_to(Time.now + ban_time + 1.minute) do | |
public_send(http_method, path, params: nil, headers: { "REMOTE_ADDR" => ip }) | |
expect(response.status).not_to eq(503) | |
end | |
end | |
end | |
describe "POST requests" do | |
context "when the ip is not banned" do | |
it_behaves_like "rate limiting and IP blocking", | |
"post", | |
Rack::Attack::PUBLIC_PATHS_POST.first, | |
Rack::Attack::MAX_ATTEMPTS_POST, | |
"public-post" | |
end | |
context "when the ip is banned" do | |
it_behaves_like "IP unblocking", | |
"post", | |
Rack::Attack::PUBLIC_PATHS_POST.first, | |
"public-post" | |
end | |
end | |
describe "GET requests" do | |
context "when the ip is not banned" do | |
it_behaves_like "rate limiting and IP blocking", | |
"get", | |
Rack::Attack::PUBLIC_PATHS_GET.first, | |
Rack::Attack::MAX_ATTEMPTS_GET, | |
"public-get" | |
end | |
context "when the ip is banned" do | |
it_behaves_like "IP unblocking", | |
"get", | |
Rack::Attack::PUBLIC_PATHS_GET.first, | |
"public-get" | |
end | |
end | |
end |
Tadam ! Désormais, peu importe la configuration que vous choisissez (notamment des périodes d'observation plus longues avec par conséquent beaucoup de requêtes autorisées) vos tests auront une durée minimale et invariable.
Quelques recommandations pour la fin
Bon, je vais quand même vous laisser avec quelques recommandations utiles, notamment si vous souhaitez étendre la configuration proposée dans cette article à d'autres types de requêtes.
Chaque filtre, throttle ou blocklist défini dans l'initializer doit avoir un nom unique pour être correctement traité par Rack Attack.
Utiliser un cache séparé du cache qui gère les fonctionnalités de l'application, afin de ne pas surcharger ce dernier.
Eviter les filtres trop généraux, couvrant trop de routes ou qui traquent abusivement les requêtes GET : vous ralentiriez le traitement des requêtes concernées pour un gain moindre.
Désactiver Rack Attack en environnement de test, et ne l'activer que pour le fichier de test Rack Attack.
Attention aux bots de référencement : adaptez votre nombre de tentatives autorisées, évitez les filtres trop sévères sur les requêtes GET ou utilisez un site différent pour la vitrine (référencement) et la logique application.
— Ines
Une mini remarque pour l'avoir subi plusieurs fois. Attention a quelle IP est envoyé a Rack::Attack. Dans certain environment derriere beaucoup de proxy, le RealIP est pas toujours la bonne. Rails s'est bien améliorer sur la detection, Mais cela peut toujours arriver.
Si par hasard l'IP d'un proxy est detecté, vous vous retrouver a bannir tous vos utilisateurs tres vite. Il faut donc bien monitorer le nombre de bannissement pour couper au plus vite le system. Potentiellement un circuitbreaker sur variable d'env est apprécié.