On va utiliser un serveur Apache pour servir les fichiers statiques de nos sites web et pour traiter les fichiers écrits dans le langage PHP mais on va faire en sorte que les requêtes web soient d'abord procédées, de façon transparente, par un serveur de cache Varnish qui écoutera sur le port HTTP TCP 80 et qui les redirigera ensuite vers Apache qui lui écoutera sur un port réseau privé.
Cependant, on veut forcer l'utilisation du protocole web sécurisé HTTPS TCP 443 pour nos sites web or Varnish ne sait gérer que le HTTP. On va donc faire tourner également un serveur Nginx qui écoutera sur ce port TCP 443 et qui redirigera ensuite les requêtes vers le proxy Varnish qui lui-même les redirigera une nouvelle fois vers Apache.
Il nous reste malgré tout une dernière étape à effectuer pour que tout ceci soit cohérent : en effet, les internautes doivent explicitement écrire "https://" devant le nom d'hôte d'un de nos sites web, dans la barre d'adresse de leur navigateur internet, pour accéder à sa version sécurisée et rien ne les empêche d'écrire "http://" devant le nom et de tomber alors sur la version HTTP non sécurisée. Il faut donc configurer Varnish de telle sorte à ce qu'il réécrive automatiquement les adresses "http://..." en "https://..." et donc forcer le chemin Nginx -> Varnish -> Apache décrit précédemment.
Tout cela peut être résumé par le schéma suivant :
cas A : http://*.spou.net --> VARNISH --> https://*.spou.net --> NGINX --> VARNISH --> APACHE cas B : https://*.spou.net --> NGINX --> VARNISH --> APACHE
Page d'erreur personnalisée
On va créer tout de suite une page d'erreur personnalisée qui servira dans les cas où les serveurs Varnish ou Apache seraient défaillants, pour cela on met ce genre de contenu dans une page nommée "/var/www/html/custom_50x.html" (mais le but est tout de même de créer une page un peu plus attrayante) :
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Error</title> </head> <body> <h1>ERROR</h1> </body> </html>
Mise en place de Nginx
On s'occupe maintenant du serveur Nginx qui a été installé par le script d'installation de la solution iRedMail, on commence par désactiver la configuration qui écoute sur le port TCP 80 pour ne pas interférer avec notre futur serveur de cache Varnish :
# sudo mv /etc/nginx/sites-enabled/00-default.conf /etc/nginx/sites-available/00-default.conf.bak
La désormais seule configuration active pour NGINX se trouve dans le fichier "/etc/nginx/sites-enabled/00-default-ssl.conf" dont le contenu est pour l'instant le suivant :
server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name _; root /var/www/html; index index.php index.html; include /etc/nginx/templates/misc.tmpl; include /etc/nginx/templates/ssl.tmpl; include /etc/nginx/templates/iredadmin.tmpl; include /etc/nginx/templates/roundcube.tmpl; include /etc/nginx/templates/sogo.tmpl; include /etc/nginx/templates/netdata.tmpl; include /etc/nginx/templates/php-catchall.tmpl; include /etc/nginx/templates/stub_status.tmpl; }
On modifie et on ajoute les lignes suivantes dans le fichier "/etc/nginx/sites-enabled/00-default-ssl.conf" afin que toutes les requêtes, à part pour l'accès aux solutions de webmail ou aux outils web installés par le script d'installation de la solution iRedMail et qui sont déjà configurés dans les templates présents dans ce fichier, soient redirigés vers Varnish qui écoutera sur le port privé TCP 6667 en plus du port TCP 80 comme nous le verrons plus tard, on utilise aussi la page d'erreur personnalisée que l'on a créé précédemment :
server { ... #include /etc/nginx/templates/php-catchall.tmpl; ... proxy_intercept_errors on; error_page 500 502 503 504 /custom_50x.html; location = /custom_50x.html { root /var/www/html; internal; } location / { proxy_pass http://127.0.0.1:6667; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; } }
Dans le cas où l'on hébergerait des sites avec des noms de domaine différents et/ou que l'on aurait généré des certificats SSL différents pour certains noms d'hôte, il faut :
- créer autant de sections "server {}' qu'il y a de certificats en remplaçant la ligne "server_name _;" par "server_name" suivi du ou des noms d'hôte pour lesquels on a généré un certificat différent, par exemple "server_name patate.unautredomaine.net p.unautredomaine.net;" ;
- remplacer la ligne "include /etc/nginx/templates/ssl.tmpl;", toujours en suivant notre exemple, par les lignes "ssl_certificate /etc/letsencrypt/live/patate.unautredomaine.net/fullchain.pem;" puis "ssl_certificate_key /etc/letsencrypt/live/patate.unautredomaine.net/privkey.pem;".
Dans l'exemple suivant, les solutions de webmail et les outils fournis par la solution iRedMail seront accessibles depuis les adresses "https://patate.unautredomaine.net/..." ou "https://p.unautredomaine.net/..." et un site web indépendant sera quant à lui accessible depuis l'adresse "https://patate.spou.net/...". Ici, le certificat pour les noms d'hôte "patate.unautredomaine.net" et "p.unautredomaine.net" a été généré via Certbot et on utilise un certificat payant ou auto-signé pour "patate.spou.net" :
server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name patate.unautredomaine p.unautredomaine.net root /var/www/html; index index.php index.html; include /etc/nginx/templates/misc.tmpl; #include /etc/nginx/templates/ssl.tmpl; ssl_certificate /etc/letsencrypt/live/patate.unautredomaine.net/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/patate.unautredomaine.net/privkey.pem; include /etc/nginx/templates/iredadmin.tmpl; include /etc/nginx/templates/roundcube.tmpl; include /etc/nginx/templates/sogo.tmpl; include /etc/nginx/templates/netdata.tmpl; #include /etc/nginx/templates/php-catchall.tmpl; include /etc/nginx/templates/stub_status.tmpl; proxy_intercept_errors on; error_page 500 502 503 504 /custom_50x.html; location = /custom_50x.html { root /var/www/html; internal; } location / { proxy_pass http://127.0.0.1:6667; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; } } server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name patate.spou.net; ssl_certificate /etc/ssl/STAR_spou_net.fullchain; ssl_certificate_key /etc/ssl/STAR_spou_net.key; proxy_intercept_errors on; error_page 500 502 503 504 /custom_50x.html; location = /custom_50x.html { root /var/www/html; internal; } location / { proxy_pass http://127.0.0.1:6667; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; } }
Mise en place de Apache
On installe Apache ainsi que quelques modules grâce aux commandes ci-dessous dont une qui va notamment activer par défaut l'utilisation de la version 8.1 du langage PHP en passant par l'interface FPM ("FastCGI Process Manager") qui permet de traiter les fichiers PHP dans un processus séparé (il y aura certainement des erreurs lors de l'installation d'Apache car il va essayer d'écouter sur le port TCP 80 qui est pour l'instant déjà utilisé par Nginx à moins que l'on ait arrêté ce dernier pour pouvoir générer des certificats avec l'outil Certbot) :
# sudo apt install -y apache2 apache2-utils libapache2-mod-fcgid # sudo a2dismod mpm_prefork # sudo a2enmod mpm_event alias headers proxy proxy_http proxy_fcgi setenvif remoteip rewrite # sudo a2enconf php8.1-fpm
On modifie le fichier "/etc/apache2/ports.conf" de telle sorte qu'Apache n'écoutera plus que sur le port privé TCP 6666 (on commente toutes les autres lignes présentes dans le fichier en ajoutant le caractère "#" au début de chaque ligne en question) :
... Listen 6666 ...
On modifie le fichier "/etc/apache2/apache2.conf" pour rajouter la ligne ci-dessous pour éviter un message d'alerte (mais absolument sans gravité) au démarrage d'Apache :
... ServerName localhost ...
On modifie ou on crée le fichier "/etc/apache2/mods-enabled/remoteip.conf" qui nous permettra, dans les fichiers d'événements au sujet des erreurs survenues dans Apache, de récupérer la vraie adresse IP des visiteurs de nos sites web qui a d'abord été captée par Varnish (sinon on ne récupèrerait que l'adresse IP du proxy qui est "127.0.0.1") :
RemoteIPInternalProxy 127.0.0.1 RemoteIPHeader X-Forwarded-For
ATTENTION, il semble que le module "remoteip" ne fonctionne qu'avec des versions assez récentes d'Apache, cela fonctionne par exemple très bien avec la version 2.4.57 installée par défaut sur système Debian 12 mais pas avec la version 2.4.38 installée sur système Debian 10, il existe le module "rpaf" qui semble équivalent mais que nous n'aborderons pas ici.
On modifie le fichier par défaut "/etc/apache2/sites-enabled/000-default.conf" ainsi :
<VirtualHost *:6666> ServerName patate.spou.net ServerAlias p.spou.net ServerAdmin webmaster@p.spou.net DocumentRoot /var/www/html ErrorLog "|$/usr/bin/tee -a ${APACHE_LOG_DIR}/error.log |/usr/bin/rotatelogs -l ${APACHE_LOG_DIR}/default/error-%Y-%m-%d.log 86400" CustomLog "|/usr/bin/rotatelogs -l ${APACHE_LOG_DIR}/default/access-%Y-%m-%d.log 86400" combined SetEnv HTTPS on </VirtualHost>
Il faut créer le dossier "/var/log/apache2/default" pour les logs de ce site :
# sudo mkdir /var/log/apache2/default
Mise en place de Varnish
On installe maintenant Varnish grâce à la commande suivante (il y aura certainement des erreurs qui apparaitront car Varnish va essayer d'écouter sur le port TCP 80 qui est toujours utilisé par le serveur Nginx pour l'instant) :
# sudo apt install -y varnish varnish-modules
On modifie le script d'initialisation de Varnish en créant d'abord le dossier "/etc/systemd/system/varnish.service.d" :
# sudo mkdir /etc/systemd/system/varnish.service.d
On crée ensuite le fichier "/etc/systemd/system/varnish.service.d/customexec.conf" dans lequel on met ceci afin notamment d'écouter, en plus du port TCP 80, les requêtes HTTP sur le port privé TCP 6667 :
[Service] ExecStart= ExecStart=/usr/sbin/varnishd -j unix,user=vcache -F -a :80 -a 127.0.0.1:6667 -T localhost:6082 -f /etc/varnish/default.vcl -S /etc/varnish/secret -s default,256m
On modifie le fichier "/etc/varnish/default.vcl" comme ci-dessous, on va notamment rediriger les requêtes effectuées sur le port HTTP TCP 80, qui sera écouté par Varnish, vers le port privé TCP 6666 qui sera écouté par notre serveur Apache et faire en sorte de forcer l'utilisation automatique du protocole sécurisé HTTPS TCP 443 pour tous nos sites web et d'enlever l'adresse de proxy "127.0.0.1" qui risque de s'ajouter systématiquement à l'IP des visiteurs qui se connectent sur nos sites dans les logs d'Apache, on va également utiliser la page d'erreur personnalisée que l'on a créé précédemment (même si dans la pratique, ce sera Nginx qui récupèrera toutes les erreurs et qui fournira cette page) :
... import std; ... backend default { .host = "127.0.0.1"; .port = "6666"; } ... sub vcl_recv { if (client.ip != "127.0.0.1") { set req.http.x-redir = "https://" + req.http.host + req.url; return(synth(850, "")); } if (req.method != "GET" && req.method != "HEAD") { return(pass); } if (req.url ~ "/robots.txt") { return(pass); } if (req.http.Authorization) { return(pass); } if (req.http.x-forwarded-for) { set req.http.X-Forwarded-For = regsub(req.http.X-Forwarded-For, "^([^,]+),?.*$", "\1"); } else { set req.http.X-Forwarded-For = client.ip; } return (hash); } sub vcl_synth { if (resp.status == 850) { set resp.http.Location = req.http.x-redir; set resp.status = 301; return (deliver); } } sub vcl_backend_error { set beresp.http.Content-Type = "text/html; charset=utf-8"; set beresp.http.Retry-After = "5"; synthetic(std.fileread("/var/www/html/custom_50x.html")); return(deliver); } ...
On applique les changements :
# sudo systemctl enable varnish
On redémarre les serveurs Apache, Nginx et Varnish dans l'ordre suivant pour voir s'il y a des erreurs à corriger :
# sudo systemctl restart apache2 # sudo systemctl restart nginx # sudo systemctl restart varnish
ATTENTION, à noter dès maintenant que dans le cas de certificats SSL gratuits générés par Certbot, le renouvellement ne peut se faire qu'en arrêtant temporairement le serveur Varnish et en laissant le serveur web temporaire de Certbot prendre le relais (notre page d'erreur "/var/www/html/custom_50x.html" s'affichera alors pour ceux qui se connecteront à nos sites pendant la procédure) et en relançant Varnish et Nginx à la fin de la procédure, on peut pour cela utiliser cette commande unique :
# sudo systemctl stop varnish && sudo certbot -q renew --standalone && sudo systemctl start varnish && sudo systemctl restart nginxSi il existe, on peut aussi modifier le fichier "/etc/cron.d/certbot" en conséquence :
... SHELL=/bin/sh PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin 0 */12 * * * root test -x /usr/bin/certbot -a \! -d /run/systemd/system && perl -e 'sleep int(rand(43200))' && service varnish stop && certbot -q renew --standalone && service varnish start && service nginx restartCependant, le fichier précédent semble être ignoré sur Debian 12 "bookworm", il faut plutôt exécuter la commande suivante :
# sudo systemctl edit certbot.serviceEt éditer le fichier qui s'ouvre alors ainsi :
### Editing /etc/systemd/system/certbot.service.d/override.conf ### Anything between here and the comment below will become the new contents of the file [Service] ExecStart= ExecStart=/bin/bash -c 'service varnish stop && certbot -q renew --standalone && service varnish start && service nginx restart' ### Lines below this comment will be discarded ...Le service Certbot est alors normalement accompagné d'un timer que l'on édite ainsi si on souhaite que la validité des certificats ne soit vérifiée qu'une fois par semaine, le dimanche à minuit :
# sudo systemctl edit certbot.timerEt on édite le fichier qui s'ouvre alors ainsi :
### Editing /etc/systemd/system/certbot.timer.d/override.conf ### Anything between here and the comment below will become the new contents of the file [Unit] Description=Run certbot on Sunday [Timer] OnCalendar= OnCalendar=Sun *-*-* 00:00:00 ### Lines below this comment will be discarded ...Pour finir, on applique les changements avec cette commande :
# sudo systemctl daemon-reload
Si tout est bon, on redémarre alors entièrement le système :
# sudo reboot
Modules de protection du serveur Apache
Protection contre les attaques DDoS
Pour protéger nos sites des attaques DDoS, nous allons utiliser le module "mod_evasive" pour Apache. Il existe un package sur Debian pour ce module mais qui ne sait pas fonctionner avec Apache en tant que proxy car quand une attaque DDoS est identifiée, c'est l'ensemble des connexions à tous les sites web qui est bloqué et non juste l'adresse IP de l'attaquant. Nous allons installer une version alternative de "mod_evasive", qui corrige ce défaut, grâce à ces commandes :
# sudo apt install -y git apache2-dev # git clone https://github.com/ajardin/mod_evasive.git # cd mod_evasive # sudo apxs -cia mod_evasive.c
On crée le fichier "/etc/apache2/mods-enabled/evasive.conf" dans lequel on met le contenu suivant qui fera en sorte qu'une même page ne peut pas être chargée plus de 3 fois de suite en une seconde et que pas plus de 50 éléments (feuilles de style CSS, images, etc...) ne peuvent être chargées depuis la même page en moins d'une seconde également :
<IfModule evasive_module> DOSHashTableSize 3097 DOSPageCount 3 DOSSiteCount 50 DOSPageInterval 1 DOSSiteInterval 1 DOSBlockingPeriod 10 DOSLogDir "/var/log/apache2/mod_evasive" DOSWhiteList 127.0.0.1 </IfModule>
Il faut alors créer le dossier "/var/log/apache2/mod_evasive" et redémarrer Apache :
# sudo mkdir -p /var/log/apache2/mod_evasive # sudo chown -R www-data:www-data /var/log/apache2/mod_evasive # sudo systemctl restart apache2
On peut rajouter d'autres lignes "DOSWhiteList" dans le fichier "/etc/apache2/mods-enabled/evasive.conf" si l'on souhaite éviter de bloquer certaines adresses IP de confiance, par exemple pour protéger l'adresse IP 10.11.12.13 et toutes les adresses du réseau commençant par 192.168.10, on modifie le fichier ainsi :
... DOSWhiteList 10.11.12.13 DOSWhiteList 192.168.10.* ...
On n'oublie pas de relancer Apache pour valider les changements :
# sudo systemctl restart apache2
Pare-feu applicatif (obsolète)
Nous nous proposons de décrire l'installation du module de pare-feu applicatif "modsecurity" sur Apache mais à titre purement informatif car, dans notre cas, nous allons plutôt utiliser cette fonctionnalité sur Nginx comme nous le verrons juste après.
On commence par installer et activer le module "modsecurity" :
# sudo apt install -y libapache2-mod-security2 # sudo a2enmod security2
Puis on fait une copie du fichier de configuration livré avec le module :
# sudo cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf
On modifie ce fichier de configuration "/etc/modsecurity/modsecurity.conf" ainsi :
... SecRuleEngine On ... SecRequestBodyAccess On ... SecRequestBodyLimit 2147000000 SecRequestBodyNoFilesLimit 13107200 ... SecResponseBodyAccess Off ... SecTmpDir /var/log/modsecurity/SecTmpDir ... SecDataDir /var/log/modsecurity/SecDataDir ... SecUploadDir /var/log/modsecurity/SecUploadDir ... #SecDebugLog /var/log/modsecurity/SecDebugLog/modsec_debug.log #SecDebugLogLevel 3 ... SecAuditLog /var/log/modsecurity/SecAuditLog/modsec_audit.log ... SecAuditLogStorageDir /var/log/modsecurity/SecAuditLogStorageDir ...
On exécute ensuite les commandes suivantes :
# sudo mkdir -p /var/log/modsecurity/{SecUploadDir,SecAuditLog,SecAuditLogStorageDir,SecDebugLog,SecDataDir,SecTmpDir} # sudo chown -R www-data:www-data /var/log/modsecurity
On va améliorer l'efficacité du module "modsecurity" en téléchargeant des nouvelles définitions d'attaques, pour cela on effectue les commandes suivantes :
# sudo mv /usr/share/modsecurity-crs/ /usr/share/modsecurity-crs-BAK # sudo git clone https://github.com/coreruleset/coreruleset /usr/share/modsecurity-crs # sudo mv /usr/share/modsecurity-crs/crs-setup.conf.example /usr/share/modsecurity-crs/crs-setup.conf # sudo mv /usr/share/modsecurity-crs/rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example /usr/share/modsecurity-crs/rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf
On peut modifier le fichier "/usr/share/modsecurity-crs/crs-setup.conf" comme ci-dessous si l'on souhaite que Apache envoie une erreur HTTP 403 par défaut lorsqu'une potentielle attaque est détectée mais on peut aussi attendre un peu avant de modifier ce fichier le temps de repérer d'abord les "false positives" (c'est-à-dire les requêtes considérées comme étant des attaques mais qui n'en sont en fait pas, comme nous le verrons lors du chapitre sur l'installation de l'application ownCloud) :
... #SecDefaultAction "phase:1,log,auditlog,pass" #SecDefaultAction "phase:2,log,auditlog,pass" ... SecDefaultAction "phase:1,log,auditlog,deny,status:403" SecDefaultAction "phase:2,log,auditlog,deny,status:403"
On modifie le fichier "/etc/apache2/mods-enabled/security2.conf" pour qu'il ressemble à ceci :
<IfModule security2_module> SecDataDir /var/log/modsecurity/SecDataDir IncludeOptional /etc/modsecurity/*.conf IncludeOptional /usr/share/modsecurity-crs/crs-setup.conf IncludeOptional /usr/share/modsecurity-crs/rules/*.conf </IfModule>
Et on redémarre Apache :
# sudo systemctl restart apache2
Modules de protection du serveur Nginx
Protection contre les attaques DDoS
Si, pour une raison ou pour une autre, le module "mod_evasive" de Apache ne nous convient pas ou si l'on veut ajouter une couche supplémentaire de protection contre les attaques DDoS, Nginx possède des fonctionnalités qui permettent d'obtenir un résultat plus ou moins équivalent à "mod_evasive".
Nous allons commencer par modifier le fichier "/etc/nginx/nginx.conf" pour y ajouter les lignes ci-dessous :
... http { limit_conn_zone $binary_remote_addr zone=limit_connections:10m; limit_conn limit_connections 15; limit_conn_status 429; ... }
Ces lignes indiquent que l'on crée ce que l'on appelle une "zone" nommée "limit_connections" (on peut donner le nom que l'on veut) dans laquelle on autorise une même adresse IP à faire 15 connexions simultanées sur nos sites web et si ce nombre est dépassé, le client reçoit une erreur 429 qui est l'erreur HTTP pour indiquer qu'il y a trop de connexions sur notre serveur.
De prime abord, on pourrait penser qu'une seule ou deux connexions simultanées par adresse IP serait largement suffisant mais en fait des choses comme l'échange de clés entre notre serveur et le client concernant nos certificats SSL sont comptabilisés dans ce nombre de connexions qui ne doit donc pas être trop bas. Le mieux est d'ajuster ce nombre si le genre de message suivant apparait trop souvent dans les logs d'erreur "/var/log/nginx/error.log" :
2022/06/17 17:30:57 [error] 52827#52827: *2811179 limiting connections by zone "limit_connections", client: 10.11.12.13, server: patate.spou.net, request: "GET /", host: "patate.spou.net", referrer: "https://patate.spou.net/"
On retourne dans le fichier "/etc/nginx/nginx.conf" pour ajouter les lignes suivantes :
... http { ... limit_req_zone $binary_remote_addr zone=limit_requests:10m rate=10r/s; limit_req_status 429; ... }
Ici nous avons créé une "zone", que nous avons nommée "limit_requests", pour laquelle on indique, via le paramètre "rate", qu'une même adresse IP n'est pas autorisée à faire plus de 10 requêtes par seconde sur nos sites web et une erreur HTTP 429 est également envoyée au client qui a dépassé ce nombre de requêtes autorisées. Le terme de "requête" inclus autant l'accès à une page HTML ou PHP que l'accès aux images, aux fichiers CSS et Javascript qui sont référencés depuis la page en question.
Ce nombre de 10 requêtes par seconde est théoriquement trop faible car il n'est pas rare d'avoir plus de 10 images et feuilles de style incluses dans nos pages web que le client va en plus charger dans la seconde. On pourrait augmenter la valeur du paramètre "rate", par exemple en indiquant "30r/m" pour autoriser 30 requêtes en une minute ou "100r/s" pour autoriser 100 requêtes par seconde mais en fait, comme son nom l'indique, ce paramètre de "rate" est plus à prendre comme un ratio de temps minimum qui doit séparer chaque requête (dans notre cas un dixième de seconde, 1 seconde divisée par 10 requêtes).
En fait, il manque une instruction supplémentaire qui va nous permettre d'ajuster le nombre de requêtes autorisées. Avec la ligne ci-dessous, on va permettre d'indiquer plus précisément le nombre de requêtes autorisées par seconde grâce à la variable "burst" (dans cet exemple, on autorise 50 requêtes par seconde pour une même adresse IP) :
limit_req zone=limit_requests burst=50 nodelay;
Soit on veut que ce paramètre soit appliqué à tous nos sites web et dans ce cas on ajoute la ligne précédente au fichier "/etc/nginx/nginx.conf" dans la section "http{}" soit on va faire du cas par cas selon nos sites web et selon les applications qui seront installées dessus en modifiant le fichier "/etc/nginx/sites-enabled/00-default-ssl.conf" comme dans l'exemple ci-dessous :
server { ... server_name patate.spou.net; ... location /application_speciale { proxy_pass http://127.0.0.1:6667; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; } location / { limit_req zone=limit_requests burst=50 nodelay; proxy_pass http://127.0.0.1:6667; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; } } server { ... server_name p.spou.net; ... location / { limit_req zone=limit_requests burst=150 nodelay; proxy_pass http://127.0.0.1:6667; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; } }
Dans l'exemple précédent, on fait en sorte qu'il n'y a aucune limite de requêtes par seconde quand on va sur l'application https://patate.spou.net/application_speciale par contre, pour les autres pages accessibles depuis le site https://patate.spou.net/... on limite le nombre de requêtes par seconde à 50 pour une même adresse IP. Quand aux pages accessibles depuis le site https://p.spou.net/... on limite le nombre de requêtes à 150 pour une même adresse IP.
On peut ajuster ces nombres selon si le genre de messages suivant apparait trop souvent dans les logs d'erreur "/var/log/nginx/error.log" (ici on voit que le client a dépassé le nombre de 50 requêtes par seconde fixées pour le site web patate.spou.net) :
2022/06/17 17:16:32 [error] 48916#48916: *2809809 limiting requests, excess: 50.160 by zone "limit_requests", client: 10.11.12.13, server: patate.spou.net, request: "GET / HTTP/1.1", host: "patate.spou.net", referrer: "https://patate.spou.net/"
Dans tous les cas, on n'oublie pas de relancer Nginx à chaque modification :
# sudo systemctl reload nginx
Pare-feu applicatif
Nous avons vu précédemment, à titre informatif, l'installation du module "modsecurity" sur Apache mais nous allons ici utiliser une méthode plus moderne car il faut savoir que "modsecurity" n'est plus un module dédié à Apache, comme c'était le cas auparavant, mais une librairie à part entière qui peut ensuite s'interfacer, à l'aide de connecteurs, à d'autres applications comme Nginx.
On va installer la dernière version de la librairie, qui est la version 3.0.14 à l'heure où sont écrites ces lignes, en exécutant les commandes suivantes pour compiler cette librairie :
# sudo apt install -y git g++ python3 libgeoip-dev libcurl4-openssl-dev libyajl-dev libxml2-dev libfuzzy-dev libpcre3-dev liblua5.4-dev liblmdb++-dev # git clone --recursive https://github.com/owasp-modsecurity/ModSecurity ModSecurity # cd ModSecurity # ./build.sh # ./configure # make # sudo make install
On visite maintenant la page https://github.com/SpiderLabs/ModSecurity-nginx/releases afin de copier le lien vers l'archive de la dernière version du connecteur pour Nginx, qui est la version 1.0.3 à l'heure où sont écrites ces lignes, et on exécute alors les commandes suivantes afin de télécharger cette archive, puis télécharger le code source de la dernière version de Nginx qui est installée sur Debian, dans notre cas la version 1.22.1, et enfin pour compiler ce connecteur :
# wget https://github.com/SpiderLabs/ModSecurity-nginx/releases/download/v1.0.3/modsecurity-nginx-v1.0.3.tar.gz # tar xvfz modsecurity-nginx-v1.0.3.tar.gz # apt source nginx # cd nginx-1.22.1 # ./configure --add-dynamic-module=../modsecurity-nginx-v1.0.3 --with-compat # make # sudo make install
On améliore l'efficacité de la librairie "modsecurity" en téléchargeant des nouvelles définitions d'attaques, pour cela on visite la page https://github.com/coreruleset/coreruleset/releases pour copier le lien vers la dernière archive de la version 3 (attention, pas la version 4!), la version 3.3.7 à l'heure où sont écrites ces lignes, et on effectue les commandes suivantes afin d'installer ces définitions (on en profite aussi pour télécharger une base de données des adresses IP répertoriées selon leur pays de provenance qui nous servira pour la protection de notre serveur Nginx) :
# wget https://github.com/coreruleset/coreruleset/archive/refs/tags/v3.3.7.tar.gz # tar xvfz v3.3.7.tar.gz # sudo mv coreruleset-3.3.7 /usr/share/modsecurity-crs-3.3.7 # sudo ln -sf /usr/share/modsecurity-crs-3.3.7 /usr/share/modsecurity-crs # sudo mv /usr/share/modsecurity-crs/crs-setup.conf.example /usr/share/modsecurity-crs/crs-setup.conf # wget https://git.io/GeoLite2-Country.mmdb # sudo mkdir -p /usr/share/GeoIP # sudo mv GeoLite2-Country.mmdb /usr/share/GeoIP/
On peut modifier le fichier "/usr/share/modsecurity-crs/crs-setup.conf" comme montré plus bas si l'on souhaite que Nginx envoie une erreur HTTP 403 par défaut lorsqu'une potentielle attaque est détectée.
A noter que pour l'action d'ID 900600, pour la variable "tx.high_risk_country_codes" nous avons mis de faux codes pays pour ne froisser personne mais dans la réalité il faut mettre les codes à deux lettres des pays dont on souhaite bloquer les adresses IP.
On peut ajouter des actions personnelles afin de désactiver certaines règles qui engendrent des "false positives" (c'est-à-dire des requêtes considérées comme étant des attaques mais qui n'en sont en fait pas, comme nous le verrons lors du chapitre sur l'installation de l'application ownCloud), nous avons choisi ici de leur donner un ID en dessous de 100 car on est sûr que ces numéros ne sont pas utilisés par d'autres règles.
On peut appliquer ces actions selon, par exemple, des parties de l'URL ("REQUEST_URI") ou bien selon le nom d'hôte ("REQUEST_HEADERS:Host").
... #SecDefaultAction "phase:1,log,auditlog,pass" #SecDefaultAction "phase:2,log,auditlog,pass" ... SecDefaultAction "phase:1,log,auditlog,deny,status:403" SecDefaultAction "phase:2,log,auditlog,deny,status:403" ... SecAction \ "id:900000,\ phase:1,\ nolog,\ pass,\ t:none,\ setvar:tx.paranoia_level=3" ... SecAction \ "id:900010,\ phase:1,\ nolog,\ pass,\ t:none,\ setvar:tx.enforce_bodyproc_urlencoded=1" ... SecGeoLookupDB /usr/share/GeoIP/GeoLite2-Country.mmdb ... SecAction \ "id:900600,\ phase:1,\ nolog,\ pass,\ t:none,\ setvar:'tx.high_risk_country_codes=CODE_ISO_PAYS1 CODE_ISO_PAYS2 CODE_ISO_PAYS3'" ... SecAction \ "id:900960,\ phase:1,\ nolog,\ pass,\ t:none,\ setvar:tx.do_reput_block=1" ... SecRule REMOTE_ADDR "@ipMatch 127.0.0.1" "id:100,phase:1,pass,nolog,ctl:ruleEngine=Off" ... SecRule REQUEST_URI "@beginsWith /owncloud/"\ "id:99,\ phase:2,\ nolog,\ pass,\ t:none,\ setvar:tx.crs_exclusions_nextcloud=1,\ ctl:ruleRemoveById=920170,\ ctl:ruleRemoveById=920230,\ ctl:ruleRemoveById=920270-920272,\ ctl:ruleRemoveById=920420,\ ctl:ruleRemoveById=921110,\ ctl:ruleRemoveById=921150,\ ctl:ruleRemoveById=921200,\ ctl:ruleRemoveById=930120,\ ctl:ruleRemoveById=931130,\ ctl:ruleRemoveById=932100-932190,\ ctl:ruleRemoveById=932200,\ ctl:ruleRemoveById=933100-933190,\ ctl:ruleRemoveById=933210,\ ctl:ruleRemoveById=941100-941190,\ ctl:ruleRemoveById=941310-941340,\ ctl:ruleRemoveById=942100-942190,\ ctl:ruleRemoveById=942200-942290,\ ctl:ruleRemoveById=942300-942390,\ ctl:ruleRemoveById=942400-942490,\ ctl:ruleRemoveById=942510-942511,\ ctl:ruleRemoveById=949110" SecRule REQUEST_HEADERS:Host "pmail.spou.net\ "id:98,\ phase:2,\ nolog,\ pass,\ t:none,\ ctl:ruleRemoveById=942431,\ ctl:ruleRemoveById=942440,\ ctl:ruleRemoveById=931130" SecRule REQUEST_URI "@beginsWith /mail/"\ "id:97,\ phase:2,\ nolog,\ pass,\ t:none,\ ctl:ruleRemoveById=920170,\ ctl:ruleRemoveById=949110" SecRule REQUEST_URI "@beginsWith /sogo/"\ "id:96,\ phase:2,\ nolog,\ pass,\ t:none,t:lowercase,\ ctl:ruleRemoveById=920170,\ ctl:ruleRemoveById=920272" ...
On fait maintenant une copie du fichier de configuration qui a du être installé lors de la compilation de la librairie "modsecurity" :
# sudo cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf
On modifie ce fichier de configuration "/etc/modsecurity/modsecurity.conf" ainsi :
... SecRuleEngine On ... SecRequestBodyAccess On ... SecRequestBodyLimit 2147000000 SecRequestBodyNoFilesLimit 13107200 ... #SecRequestBodyInMemoryLimit ... SecResponseBodyAccess Off ... SecTmpDir /var/log/modsecurity/SecTmpDir ... SecDataDir /var/log/modsecurity/SecDataDir ... SecUploadDir /var/log/modsecurity/SecUploadDir ... #SecDebugLog /var/log/modsecurity/SecDebugLog/modsec_debug.log #SecDebugLogLevel 3 ... SecAuditLog /var/log/modsecurity/SecAuditLog/modsec_audit.log ... SecAuditLogStorageDir /var/log/modsecurity/SecAuditLogStorageDir ...
On exécute ensuite les commandes suivantes :
# sudo mkdir -p /var/log/modsecurity/{SecUploadDir,SecAuditLog,SecAuditLogStorageDir,SecDebugLog,SecDataDir,SecTmpDir} # sudo chown -R www-data:www-data /var/log/modsecurity
On crée maintenant un fichier "/etc/nginx/modsec/main.conf" dans lequel on met le contenu suivant :
Include /etc/modsecurity/*.conf Include /usr/share/modsecurity-crs/crs-setup.conf Include /usr/share/modsecurity-crs/rules/*.conf
Et on modifie le fichier "/etc/nginx/nginx.conf" ainsi :
user www-data; ... load_module /usr/local/nginx/modules/ngx_http_modsecurity_module.so; ... http { ...
modsecurity on; modsecurity_rules_file /etc/nginx/modsec/main.conf; include /etc/nginx/conf-enabled/*.conf; include /etc/nginx/sites-enabled/*.conf; }
...
On n'oublie pas de relancer Nginx après avoir vérifier qu'il n'y a pas de soucis de configuration :
# sudo nginx -t
# sudo systemctl restart nginx
Utilisateur dédié au contenu web
On peut créer un utilisateur "webadmin" qui pourra ajouter, supprimer et modifier les pages web contenues dans le dossier "/var/www" ainsi que lire les logs d'Apache.
On peut faire en sorte qu'il puisse également exécuter des commandes administrateur en l'ajoutant au groupe "sudo" mais ce n'est pas du tout conseillé :
# sudo adduser webadmin # sudo adduser webadmin www-data # sudo adduser webadmin sudo # sudo chown -R www-data:webadmin /var/www # sudo chmod -R g+rw /var/www # sudo chown -R www-data:webadmin /var/log/apache2 # sudo chmod -R g+rw /var/log/apache2
Pour ajouter un utilisateur déjà existant au groupe "www-data" :
# sudo usermod -a -G www-data utilisateur
Commentaires