Sécurité et WAF
bext comprend un pare-feu d'applications Web (WAF) de niveau entreprise qui inspecte chaque demande à travers six étapes de défense en couches. Le WAF s'exécute en cours de processus avec un coût de compilation d'expressions régulières nul par requête : tous les modèles sont compilés une fois au démarrage. Il s'agit d'une fonctionnalité Pro ; L'édition communautaire inclut les en-têtes de sécurité par défaut et la limitation du débit de base.
Couches de défense#
Les demandes passent par ces étapes dans l’ordre. La première décision non-Autoriser termine la chaîne :
- Filtre IP -- Listes d'autorisation/refus basées sur CIDR
- GeoIP Blocking - contrôle d'accès au niveau du pays
- DDoS Guard -- limites de connexion, capuchons de taille de corps/en-tête
- Limitation de débit – règles par chemin pour chaque compartiment de jetons
- Bot Detection -- analyse comportementale multi-signaux
- Moteur de règles -- Injection SQL, XSS, parcours de chemin, injection shell, détection de scanner, règles personnalisées
Configuration WAF#
[waf]
enabled = true
# IP filtering (mode: "allow", "deny", or "off")
[waf.ip_filter]
mode = "deny"
deny_list = ["10.0.0.0/8", "203.0.113.50"]
allow_list = ["10.0.0.5"] # Allow list overrides deny list
# GeoIP blocking (requires MaxMind GeoLite2 database)
[waf.geo]
enabled = true
mode = "deny" # or "allow" for whitelist mode
countries = ["CN", "RU"] # ISO 3166-1 alpha-2 codes
bypass_paths = ["/health", "/api/status"]
real_ip_header = "X-Forwarded-For" # Extract real IP from proxy header
# Request inspection rules
[waf.rules]
sql_injection = true
xss = true
path_traversal = true
shell_injection = true
scanner_detection = true
protocol_violations = true
# Bot detection
[waf.bot]
enabled = true
mode = "block" # "block", "challenge", or "log_only"
signal_threshold = 2 # Number of signals to trigger detection
good_bots = [ # Always allowed (case-insensitive match)
"Googlebot", "Bingbot", "Slurp", "DuckDuckBot",
"facebookexternalhit", "Twitterbot", "Applebot"
]
# DDoS mitigation
[waf.ddos]
max_connections_per_ip = 100
max_new_connections_per_second = 500
max_request_body_size = 10485760 # 10 MB
header_count_limit = 100
header_size_limit = 16384 # 16 KB
slowloris_timeout_ms = 10000
# Path-specific rate limiting
[[waf.rate_limit_rules]]
name = "api_global"
pattern = "/api/**"
rpm = 600 # Requests per minute
burst = 50 # Extra burst capacity
key_source = "ip" # "ip", {"header": "X-Api-Key"}, {"cookie": "session"}
[[waf.rate_limit_rules]]
name = "auth_strict"
pattern = "/api/auth/**"
rpm = 20
burst = 5
key_source = "ip"
# Throttle a whole CLASS of clients with one shared bucket. `key_source =
# "global"` means every request matching the rule shares a single budget —
# useful for distributed crawlers that each hit at a low per-IP rate but
# collectively saturate the backend. `user_agent_contains` (case-insensitive)
# gates the rule to specific UAs, so it can target scrapers without ever
# touching real users or search engines (which simply aren't listed).
[[waf.rate_limit_rules]]
name = "aggressive_crawlers"
pattern = "*"
rpm = 30 # whole-class budget (not per-IP)
burst = 15
key_source = "global"
user_agent_contains = ["AhrefsBot", "SemrushBot", "ClaudeBot", "GPTBot", "meta-externalagent"]
# Custom blocking rules
[[waf.custom_rules]]
name = "block-admin-external"
action = "block"
status = 403
reason = "Admin access denied from external network"
[waf.custom_rules.match_config]
path = "/admin/**"
Filtrage IP#
Le filtre IP prend en charge à la fois les adresses individuelles et les plages CIDR pour IPv4 et IPv6. En mode de refus, la liste verte est prioritaire sur la liste de refus, vous permettant ainsi de supprimer des exceptions :
[waf.ip_filter]
mode = "deny"
deny_list = ["192.168.0.0/16"]
allow_list = ["192.168.1.100"] # This specific IP is allowed despite the range deny
Dans , autorisez le mode, seules les adresses IP répertoriées peuvent accéder au serveur - toutes les autres reçoivent un 403.
Le filtre IP prend en charge le rechargement à chaud : mettez à jour la configuration et envoyez SIGHUP pour appliquer de nouvelles règles sans redémarrer le serveur.
Blocage GeoIP#
Le blocage au niveau du pays utilise la base de données GeoLite2-Country de MaxMind. Téléchargez le fichier gratuit .mmdb et pointez-y bext :
geoip_db_path = "/etc/bext/GeoLite2-Country.mmdb"
En mode refuser, les pays inconnus (échecs de recherche) sont autorisés à passer. Dans , autorisez le mode, les pays inconnus sont bloqués. Il s'agit de la valeur par défaut la plus restrictive pour les scénarios de conformité.
Le paramètre real_ip_header gère les chaînes proxy. Pour X-Forwarded-For: 1.2.3.4, 5.6.7.8, bext extrait la première adresse IP ( 1.2.3.4 ) comme adresse client.
Détection des robots#
Le détecteur de robots évalue les requêtes en fonction de cinq signaux comportementaux :
- En-tête
User-Agentmanquant - En-tête
Acceptmanquant - En-tête
Accept-Languagemanquant - En-tête
Accept-Encodingmanquant - Agent utilisateur du scanner connu (sqlmap, Nikto, etc.)
Lorsque le nombre de signaux atteint le seuil, l'action configurée déclenche :
- Bloc -- renvoie 403 immédiatement
- Défi – sert une page de défi JavaScript ; les robots sans tête sans échec du moteur JS
- log_only -- enregistre la détection sans blocage (utile pour régler les seuils)
Les bons robots connus (Googlebot, Bingbot, etc.) sont exemptés par défaut en utilisant une correspondance de sous-chaînes d'agent utilisateur insensible à la casse.
Protection contre les attaques DDoS#
La protection DDoS applique plusieurs limites simultanément :
- Limite de connexion par IP -- empêche une seule IP de monopoliser les ressources du serveur (par défaut : 100 connexions simultanées)
- Taux de connexion global -- plafonne les nouvelles connexions par seconde pour éviter la saturation des flux SYN (par défaut : 500/s)
- Limite de taille du corps -- rejette les charges utiles surdimensionnées avec 413 (par défaut : 10 Mo)
- Abus d'en-tête - limite le nombre d'en-têtes (100) et la taille totale des en-têtes (16 Ko) pour empêcher les attaques de type Slowloris
Le suivi des connexions est limité à 100 000 entrées pour éviter que le garde lui-même ne devienne un vecteur d'épuisement de la mémoire.
Limitation du débit#
Les règles de limite de débit utilisent un algorithme de compartiment à jetons avec une capacité de rafale configurable. Les clés peuvent être extraites de :
| Source clé | Cas d'utilisation |
|---|---|
ip |
Protection générale de l'API (normalise IPv6 à /64) |
header("X-Api-Key") |
Limites par clé API |
cookie("session") |
Limites par session |
global |
Un bucket partagé pour une classe entière (par exemple, des robots d'exploration distribués qui atteignent chacun un faible débit par IP mais saturent collectivement le backend) |
Une règle peut également définir user_agent_contains = [...] (sous-chaînes insensibles à la casse) afin qu'elle ne se déclenche que pour les agents utilisateurs correspondants, ce qui vous permet de limiter une classe d'analyseur sans jamais toucher les utilisateurs réels ou les moteurs de recherche, qui ne sont tout simplement pas répertoriés.
Lorsqu'une requête est limitée en débit, bext renvoie 429 avec des en-têtes standard :
HTTP/1.1 429 Too Many Requests
Retry-After: 12
RateLimit-Limit: 600
RateLimit-Remaining: 0
Accélérateur sur chenilles intégré#
Les robots d'exploration agressifs sans recherche (grattoirs SEO/IA comme AhrefsBot, SemrushBot, ClaudeBot, GPTBot, méta-externalagent, …) peuvent balayer les URL longue traîne d'un site dans des rendus backend froids - sur une boîte multi-tenant occupée, cela représente facilement un quart du temps de rendu total. bext fournit une règle de limite de débit global prête à l'emploi pour cette classe, contrôlée par une variable d'environnement :
BEXT_WAF_CRAWLER_RPM=30 # whole-class budget per minute; 0 (default) = off
C'est désactivé par défaut, les moteurs de recherche (Googlebot/Bingbot/…) ne correspondent jamais et les vrais utilisateurs ne sont jamais affectés. Choisissez le taux intentionnellement : une valeur égale ou supérieure à, le taux constant de la classe des chenilles (par exemple 30) est la protection contre les tempêtes ** — elle ne rejette que les véritables inondations et laisse tranquille l'exploration normale ; une valeur inférieure à (par exemple 10) supprime activement la charge mais limite plus fortement les outils de référencement tiers. Montre
waf_rate_limits_total et waf_crawler_ua_matched_total sur /__bext/metrics pour l'adapter au trafic réel : le second compte les requêtes qui ont atteint le limiteur et correspondent à la porte UA du robot, afin que vous puissiez confirmer que la règle est appliquée avant de lui faire confiance pour se débarrasser de la charge.
En-têtes de sécurité par défaut#
Même sans WAF activé, bext injecte des en-têtes de sécurité de base dans chaque réponse :
| En-tête | Valeur |
|---|---|
X-Content-Type-Options |
nosniff |
X-Frame-Options |
SAMEORIGIN |
Referrer-Policy |
strict-origin-when-cross-origin |
X-XSS-Protection |
0 (désactive l'auditeur existant conformément aux meilleures pratiques modernes) |
Content-Security-Policy |
default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none' |
Strict-Transport-Security |
max-age=63072000; includeSubDomains (TLS uniquement) |
Configuration CORS#
[cors]
allowed_origins = ["https://app.example.com", "https://admin.example.com"]
max_age_secs = 3600
Lorsqu'aucune origine n'est configurée, bext utilise par défaut CORS restrictif (aucune requête d'origine croisée n'est autorisée). Les méthodes autorisées incluent GET, POST, PUT, DELETE, PATCH et OPTIONS. Les en-têtes personnalisés x-tenant-id et x-site-id sont inclus par défaut.
Surveillance#
Les statistiques WAF sont disponibles sur /__bext/admin/api/waf/stats et au format Prometheus sur /__bext/metrics :
{
"total_requests": 184920,
"allowed": 184501,
"blocked": 389,
"rate_limited": 27,
"challenged": 3
}
Le flux d'événements d'audit WAF sur /__bext/waf/events enregistre chaque décision de blocage avec l'adresse IP du client, le chemin d'accès, le nom de la règle et l'horodatage pour une analyse médico-légale.
Le WAF (filtrage IP, GeoIP, détection de robots et moteur de règles) est une fonctionnalité Pro. L'édition communautaire inclut uniquement les en-têtes de sécurité par défaut et la limitation du débit de base. L'activation de [waf] enabled = true sur la communauté supprime silencieusement les couches avancées.
L'en-tête Content-Security-Policy par défaut utilise script-src 'self' 'unsafe-inline' et ne met pas sur liste blanche pour les CDN externes (polices Google, scripts d'analyse, etc.). Les pages qui chargent des ressources provenant d'origines tierces échoueront silencieusement dans les vrais navigateurs, même si les réponses curl semblent correctes. Ajoutez toutes les origines nécessaires au CSP ou auto-hébergez les actifs.
En rapport#
- TLS et HTTPS — HSTS, gestion des certificats et application HTTPS
- Multi-application — CORS par application et remplacements d'en-tête
- Opérations — Surveillance — Métriques Prometheus incluant les compteurs
bext_waf_* - Opérations — Journalisation — accès structuré au journal d'audit WAF
- Capacité d'authentification — couche d'authentification qui complète les contrôles au niveau WAF
- Politique d'authentification — règles d'autorisation précises au-delà du filtrage IP/géo