⚠️ Disclaimer
La méthode que je présente ici correspond à ma propre démarche d’apprentissage. Elle peut contenir des approximations ou des erreurs, car j’apprends et je progresse chaque jour un peu plus. Ne prenez donc pas ce que je fais comme une référence absolue, mais plutôt comme un retour d’expérience personnel.
Contexte et objectifs
Une entreprise du secteur financier voulait déployer un prototype d’infrastructure EXTRANET exposant des services web publics tout en maintenant une séparation stricte entre ressources publiques et interfaces d’administration. Le cahier des charges imposait un service de transfert de fichiers sécurisé (FTPS) avec gestion granulaire des permissions, une architecture bi-interface, et une défense multicouche contre les attaques DDoS.
Le projet utilisait Apache comme serveur web principal. J’ai aussi configuré NGINX en parallèle pour comprendre les différences architecturales, pas avec l’idée de le déployer en production, mais pour comparer les approches.
Les objectifs clairs étaient : héberger le site extranet avec haute disponibilité, restreindre SSH et l’interface admin au réseau interne uniquement, chiffrer tout en HTTPS/FTPS, implémenter une défense en profondeur contre les slow connections et les DDoS simples.
Architecture : une séparation physique par interface
Le serveur avait deux interfaces réseau distinctes : c’était la clé du projet. L’interface interne (172.16.10.11/24) exposait SSH, l’admin en HTTPS sur port 5502, et FTPS. L’interface externe (10.20.30.11/24) exposait uniquement HTTPS sur 443.
| Composant | Interface Interne | Interface Externe |
|---|---|---|
| Réseau | 172.16.10.0/24 | 10.20.30.0/24 |
| Services | SSH (22), Admin HTTPS (5502), FTPS (21) | HTTPS uniquement (443) |
| Politique firewall | ACCEPT sélectif | DROP par défaut |
Cette séparation physique offrait un isolement réel, pas juste une isolation logique dans les règles iptables. Si quelque chose compromettait le serveur web via l’interface externe, les services critiques restaient cloisonnés sur l’interface interne. C’était le vrai bénéfice : en cas de brèche, l’attaquant n’avait accès qu’à ce qui était exposé sur cette interface-là.
Apache : deux virtual hosts, deux niveaux de sécurité
J’ai configuré deux virtual hosts avec des contraintes différentes. D’abord l’EXTRANET public sur 443, accessible de partout, qui servait le contenu web. Ensuite l’ADMIN privé sur 5502, écoutable uniquement sur l’IP interne (172.16.10.11), réservé à l’administration.
| |
Le second virtual host écoutait sur le port 5502 mais uniquement sur 172.16.10.11:5502 (la directive Listen limitait l’accès à cette interface). Le certificat était un wildcard auto-signé (*.company.local), suffisant pour un lab. En production, ce serait Let’s Encrypt ou une CA d’entreprise.
Générer le certificat :
| |
Les tests montaient qu’accéder à l’EXTRANET depuis l’externe fonctionnait (curl -k https://10.20.30.11/ → HTTP 200). Accéder à l’ADMIN depuis l’externe échouait (curl -k https://10.20.30.11:5502/ → Connection refused). Depuis l’interne, les deux marchaient.
FTPS : permissions différenciées par groupe Linux
Le service vsftpd en mode FTPS utilisait SSL/TLS obligatoire pour les données et les logins. La configuration de base était classique : pas d’utilisateurs anonymes, chroot jail pour isoler les utilisateurs, plage passive 40000-40100.
ssl_enable=YES
force_local_data_ssl=YES
force_local_logins_ssl=YES
ssl_tlsv1=YES
ssl_sslv2=NO
ssl_sslv3=NO
anonymous_enable=NO
local_enable=YES
chroot_local_user=YES
allow_writeable_chroot=YES
pasv_min_port=40000
pasv_max_port=40100
La partie intéressante était la gestion des permissions différenciées. J’ai créé deux groupes Linux : developers et designers. Les développeurs avaient accès complet à /var/www/extranet et /var/www/admin (permissions 770). Les graphistes n’accédaient qu’à /var/www/extranet/images (permissions 750).
| |
En testant, un développeur pouvait lister et modifier partout. Un graphiste n’accédait qu’au dossier images et voyait une erreur “Permission denied” ailleurs. Le chroot jail vsftpd empêchait de remonter en dehors de son répertoire racine. Tout fonctionnait comme prévu jusqu’au moment où j’ai découvert la complexité réelle.
Protection contre les connexions lentes : mod_reqtimeout
L’attaque Slowloris envoie des requêtes HTTP extrêmement lentement (quelques octets à la fois, sur une longue période). Le but est d’épuiser les connexions du serveur. Apache peut fermer automatiquement ces connexions “lentes” avec le module mod_reqtimeout.
| |
Les paramètres : 10 à 40 secondes pour les headers, 20 à 40 secondes pour le body, débit minimum 500 octets/seconde. Si une connexion envoie moins vite, Apache la ferme.
J’ai testé avec slowhttptest, un outil simulant exactement Slowloris :
| |
200 connexions lentes lancées. Résultat : 95% de disponibilité maintenue, environ 190 connexions fermées après 10-15 secondes, récupération rapide (moins de 5 secondes) une fois l’attaque arrêtée. Les connexions légitimes passaient sans problème.
Défense en profondeur : iptables, mod_reqtimeout, Fail2Ban
Plutôt qu’un seul mécanisme de protection, j’ai empilé trois couches :
En couche réseau, iptables limite les nouvelles connexions HTTPS sur le port 443. La règle drop automatiquement les IPs qui ouvrent plus de 50 connexions en 60 secondes.
| |
En couche application, mod_reqtimeout ferme les connexions lentes (déjà décrit plus haut).
En couche détection, Fail2Ban bannit automatiquement les IPs suspectes en analysant les logs. J’ai configuré cinq jails :
| |
La jail apache-ddos-extranet détectait les HTTP floods en lisant les logs Apache en temps réel. Quand une IP dépassait 100 requêtes en 60 secondes, Fail2Ban la bannissait automatiquement pour 5 minutes.
Pour tester, j’ai lancé 150 requêtes curl en boucle :
| |
L’IP source était bannie après environ 100 requêtes. Vérification : sudo fail2ban-client status apache-ddos-extranet montrait l’IP bannissable.
Pare-feu iptables : policy DROP, exceptions explicites
La politique par défaut était DROP : tout refuser sauf autorisation explicite. C’est l’approche restrictive, plus sûre, mais qui demande de bien documenter chaque exception.
Sur l’interface externe (ens19), j’ai autorisé uniquement HTTPS :
| |
Sur l’interface interne (ens18), j’ai autorisé SSH, l’admin HTTPS, et FTPS :
| |
Tests de validation : depuis l’externe, HTTPS passait, SSH et l’admin 5502 étaient refusés. Depuis l’interne, tout était accessible. C’était le comportement attendu.
Apache vs NGINX : deux architectures, deux philosophies
J’ai configuré une instance NGINX en parallèle pour comparer. Apache et NGINX ne pensent pas la performance de la même façon.
Apache (notamment avec le MPM prefork ou worker) crée un processus ou un thread par connexion. Consommation mémoire : 10-15 MB par worker. C’est idéal pour du contenu dynamique (PHP, Python) qui s’exécute directement dans Apache. Configuration verbale, beaucoup de modules disponibles.
NGINX utilise un event loop asynchrone. Un seul processus gère des centaines de connexions simultanées. Consommation mémoire : 1-2 MB par worker. C’est excellent pour du contenu statique ou du reverse proxying. Configuration concise, modules limités nativement.
La configuration NGINX équivalente était plus courte :
| |
Pour ce projet (contenu mixte, trafic modéré), Apache était le choix adapté. NGINX aurait été surqualifié pour 100% du contenu statique, mais si l’extranet avait hébergé du PHP dynamique (qui l’était probablement), NGINX aurait demandé de router vers un PHP-FPM externe. Apache intègre ça nativement.
Tests de sécurité : validation réelle
Au-delà des configurations, j’ai validé tout en testant concrètement :
Scan de ports avec nmap depuis l’externe : seul le port 443 (HTTPS) était visible. SSH et l’admin 5502 n’existaient pas du point de vue du réseau externe.
| |
Analyse SSL avec sslscan : TLSv1.2 et TLSv1.3 acceptés. SSLv2, SSLv3, TLSv1.0, TLSv1.1 rejetés. Bon.
| |
Le test Slowloris confirmait les 95% de disponibilité, déjà documenté plus haut.
Côté logs, grep "Timeout" /var/log/apache2/extranet-error.log montrait 187 connexions fermées par mod_reqtimeout lors du test Slowloris. Fail2Ban avait banni 3 IPs lors des tests HTTP flood.
Difficultés et solutions trouvées
J’ai d’abord essayé le module mod_evasive pour détecter les floods. Les résultats étaient contradictoires, probablement une interaction avec la redirection HTTP vers HTTPS. J’ai abandonné pour une approche multicouche (mod_reqtimeout + Fail2Ban + iptables), qui couvrait mieux le périmètre.
Permissions FTPS complexes. Gérer des droits différenciés entre développeurs et graphistes nécessitait de comprendre les ACL Linux. J’ai choisi les groupes Linux classiques avec chmod 770/750, soutenus par les chroot jail vsftpd pour isoler physiquement.
Configuration iptables interface-spécifique. Appliquer des règles différentes selon l’interface (ens18 vs ens19) sans créer de fuite était la partie la plus délicate. La solution : utiliser -i (interface inbound) et -o (interface outbound) systématiquement, avec policy DROP par défaut pour capturer tout ce qu’on n’avait pas explicitement autorisé.
Validation DDoS dans un lab. Simuler un vrai DDoS avec ressources limitées demande des outils dédiés (slowhttptest). L’objectif était valider que le mécanisme de protection fonctionnait, pas tester la résistance absolue.
Ce que retient de ce projet
L’architecture bi-interface était la clé. Physiquement séparer réseau public et réseau interne offrait un isolement bien meilleur qu’une simple segmentation logique. En cas de compromission du service web (interface externe), un attaquant n’avait accès qu’aux données/services exposés sur cette interface. SSH et l’admin restaient cloisonnés. C’était la vraie protection.
La défense en profondeur a du sens. Aucune couche seule n’était parfaite : iptables limitait les connexions, mod_reqtimeout fermait les lentes, Fail2Ban bannissait les IPs agressives. Combinées, elles formaient une protection cohérente, pas hermétique (rien ne l’est), mais efficace contre les attaques courantes.
Les tests réels valident plus que la configuration seule. Une règle iptables “correcte” ne prouve pas qu’elle fonctionne. J’ai lanché des attaques Slowloris et des HTTP floods pour vérifier que la protection réagissait. Seul ce test en conditions réelles inspirait confiance.
Fail2Ban automatise la réaction aux incidents. Plutôt que de bannir manuellement les IPs méchantes dans iptables, Fail2Ban lit les logs Apache et agit en temps réel. C’est une partie de ce que font les SIEM en production, ici dans une version minimaliste, mais le principe reste.
Évolutions possibles
Pour supporter une charge importante (10 000+ utilisateurs) :
- Un load balancer (HAProxy ou NGINX) en frontal distribuerait les connexions
- Un cluster de serveurs Apache offrirait la scalabilité
- Un CDN (Cloudflare, Akamai) servirait le contenu statique
- Une base de données répliquée (MySQL Master-Slave ou PostgreSQL) centraliserait l’état
Mais ces évolutions sortaient du périmètre du projet de formation.
Conclusion
Ce projet a consolidé ma compréhension de comment construire une infrastructure vraiment défendue. L’approche n’était pas originale (séparation physique, défense en profondeur, politique restrictive), mais comprendre pourquoi chaque élément était là plutôt que le faire machinalement, ça change beaucoup.
L’architecture bi-interface séparant publique et administration était le point central. Les trois couches de protection (réseau, application, système) ne remplaçaient pas l’une l’autre, elles se complétaient. Et les tests réels (Slowloris, HTTP floods, scans de ports) validaient que la théorie correspondait à la pratique.
Les difficultés (mod_evasive, permissions FTPS, iptables interface-spécifique) étaient des apprentissages : on progresse en échouant d’abord, puis en trouvant la solution. Aucun de ces problèmes n’était insurmontable une fois qu’on avait pris le temps de les diagnostiquer.