Howto Apache
- Installation
- Configuration de base
- VirtualHost
- Gestion des droits
- SSL
- Logs
- Configuration avancée
- Autorisations
- Rewrite Rules
- Conditions
- Attaques DOS (Denial Of Service) ou XSS
- Statistiques
- Monitoring
- FAQ
- Autorisation DirectoryIndex via .htaccess ?
- Autorisation directives mod_expires ou mod_cache via .htaccess ?
- Autorisation de Rrewrite Rules (mod_rewrite) via .htaccess ?
- Autorisation Options via .htaccess ?
- Authentification LDAP avec serveur LDAP en local
- Page personnalisée lors code erreur HTTP
- Site web cassé en https avec un reverse proxy
- Bloquer un User-Agent
- Bloquer certaines URI
- Site avec accents cassés
- php_value error_reporting
- Ne pas logguer le query-string dans le access.log
- Proxy WebSocket socket.io
- IncludeOptional génère une erreur quand le fichier n’existe pas !
- J’ai activé HTTP/2, mais ça ne fonctionne pas :(
- Too many open files
- CORS Allow-Origin
- NFS et Apache-ITK
- Apache ajoute l’entête HTTP “Vary” inopinément
- Gestion des droits avec Apache-ITK et PHP-FPM
- Apache plante au 1024e restart
- Code 413 Request Entity Too Large
- Modifier le /tmp de Apache ( TMPDIR )
- Documentation : https://httpd.apache.org/docs/2.4/
- Rôle Ansible : https://gitea.evolix.org/evolix/ansible-roles/src/branch/stable/apache
- Statut de cette page : test / bookworm
Apache est le serveur HTTP le plus utilisé sur le web depuis 1996.
Installation
Nous utilisons la version Apache en mode MPM-ITK depuis des années en production sur de nombreux serveurs critiques. MPM-ITK permet de préciser pour chaque VirtualHost un utilisateur, un groupe et une option MaxClients spécifique, ce qui est utile pour la sécurité d’un serveur multi-sites.
# apt install apache2 libapache2-mpm-itk libapache2-mod-evasive apachetop libwww-perl
# apache2ctl -V
Server version: Apache/2.4.57 (Debian)
Server built: 2023-04-13T03:26:51
Server's Module Magic Number: 20120211:127
Server loaded: APR 1.7.2, APR-UTIL 1.6.3, PCRE 10.42 2022-12-11
Compiled using: APR 1.7.2, APR-UTIL 1.6.3, PCRE 10.42 2022-12-11
Architecture: 64-bit
Server MPM: prefork
threaded: no
forked: yes (variable process count)
Server compiled with....
-D APR_HAS_SENDFILE
-D APR_HAS_MMAP
-D APR_HAVE_IPV6 (IPv4-mapped addresses enabled)
-D APR_USE_PROC_PTHREAD_SERIALIZE
-D APR_USE_PTHREAD_SERIALIZE
-D SINGLE_LISTEN_UNSERIALIZED_ACCEPT
-D APR_HAS_OTHER_CHILD
-D AP_HAVE_RELIABLE_PIPED_LOGS
-D DYNAMIC_MODULE_LIMIT=256
-D HTTPD_ROOT="/etc/apache2"
-D SUEXEC_BIN="/usr/lib/apache2/suexec"
-D DEFAULT_PIDLOG="/var/run/apache2.pid"
-D DEFAULT_SCOREBOARD="logs/apache_runtime_status"
-D DEFAULT_ERRORLOG="logs/error_log"
-D AP_TYPES_CONFIG_FILE="mime.types"
-D SERVER_CONFIG_FILE="apache2.conf"
# systemctl status apache2
● apache2.service - The Apache HTTP Server
Loaded: loaded (/lib/systemd/system/apache2.service; enabled; preset: enabled)
Active: active (running) since Fri 2023-07-28 15:59:33 CEST; 1min 33s ago
Docs: https://httpd.apache.org/docs/2.4/
Main PID: 3145 (apache2)
Tasks: 6 (limit: 2356)
Memory: 4.2M
CPU: 27ms
CGroup: /system.slice/apache2.service
├─3145 /usr/sbin/apache2 -k start
├─3147 /usr/sbin/apache2 -k start
├─3148 /usr/sbin/apache2 -k start
├─3149 /usr/sbin/apache2 -k start
├─3150 /usr/sbin/apache2 -k start
└─3151 /usr/sbin/apache2 -k start
Note : Pour Debian 8, il faut installer ainsi :
# apt install apache2-mpm-itk libapache2-mod-evasive apachetop libwww-perl
Configuration de base
Fichiers de configuration :
/etc/apache2
├── apache2.conf
├── conf-available/
│ └── X.conf
├── conf-enabled/
│ └── X.conf -> ../conf-available/X.conf
├── envvars
├── magic
├── mods-available/
│ ├── X.load
│ └── X.conf
├── mods-enabled/
│ ├── X.load -> ../mods-available/X.load
│ └── X.conf -> ../mods-available/X.conf
├── ports.conf
├── sites-available/
│ ├── 000-default.conf
│ ├── default-ssl.conf
│ └── X.conf
└── sites-enabled/
│ ├── 000-default.conf -> ../sites-available/000-default.conf
└── X.conf -> ../sites-available/X.conf
La configuration principale se trouve dans le fichier
/etc/apache2/apache2.conf
qui inclut de nombreux fichiers
séparés.
Nous activons au minimum les modules suivants :
# a2enmod rewrite expires headers rewrite cgi
Le fichier
/etc/apache2/conf-available/z-evolinux-defaults.conf
contient nos optimisations basiques :
ServerTokens Prod
Timeout 10
KeepAliveTimeout 2
MaxKeepAliveRequests 10
ServerLimit 250
#MaxClients 250
MaxRequestWorkers 250
StartServers 50
MinSpareServers 20
MaxSpareServers 30
MaxConnectionsPerChild 100
LimitUIDRange 0 6000
LimitGIDRange 0 6000
<IfModule mod_ssl.c>
SSLProtocol all -SSLv2 -SSLv3
SSLCipherSuite HIGH:MEDIUM:!aNULL:!MD5:!RC4
</IfModule>
<IfModule status_module>
ExtendedStatus On
<IfModule proxy_module>
ProxyStatus On
</IfModule>
</IfModule>
# Go away bad bots (define "bad bots" in zzz-evolinux-custom.conf)
<If "reqenv('GoAway') -eq 1">
Require all denied
</If>
<DirectoryMatch "/\.git">
# We don't want to let the client know a file exist on the server,
# so we return 404 "Not found" instead of 403 "Forbidden".
Redirect 404
</DirectoryMatch>
# File names starting with
<FilesMatch "^\.(git|env)">
Redirect 404
</FilesMatch>
# File names ending with
<FilesMatch "\.(inc|bak)$">
Redirect 404
</FilesMatch>
<LocationMatch "^/evolinux_fpm_status-.*">
Require all denied
</LocationMatch>
et notre liste de « bad bots » dans :
/etc/apache2/conf-available/z-evolinux-badbots.conf
SetEnvIf User-Agent "^BadBot$" GoAway=1
SetEnvIf User-Agent "thesis-research-bot" GoAway=1
SetEnvIf User-Agent "Bytespider" GoAway=1
SetEnvIf User-Agent "SeekportBot" GoAway=1
SetEnvIf User-Agent "PetalBot" GoAway=1
SetEnvIf User-Agent "DotBot" GoAway=1
SetEnvIf User-Agent "SEOkicks" GoAway=1
SetEnvIf User-Agent "serpstatbot" GoAway=1
SetEnvIf User-Agent "MJ12bot" GoAway=1
SetEnvIf User-Agent "SemrushBot" GoAway=1
et les customisations à la demande dans
/etc/apache2/conf-available/zzz-evolinux-custom.conf
:
#MaxClients 500
#ServerLimit 500
#StartServers 100
#MinSpareServers 40
#MaxSpareServers 60
#SetEnvIf User-Agent "Amazonbot" GoAway=1
#SetEnvIf User-Agent "YandexBot" GoAway=1
#SetEnvIf User-Agent "ApacheBench" GoAway=1
#SetEnvIf User-Agent "Nutch" GoAway=1
# Uncomment for SSL strong security
#<IfModule mod_ssl.c>
#SSLCipherSuite EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH
#SSLProtocol All -SSLv2 -SSLv3
#SSLHonorCipherOrder On
#SSLCompression off
#SSLSessionCache shmcb:/var/log/apache2/ssl_gcache_data(512000)
#SSLSessionCacheTimeout 600
## Stapling not activated by default. Need config.
##SSLUseStapling on
##SSLStaplingCache shmcb:${APACHE_RUN_DIR}/stapling-cache(150000)
#</IfModule>
#<FilesMatch ".(eot|ttf|otf|woff)">
# Header set Access-Control-Allow-Origin "*"
#</FilesMatch>
# you need disable EnableCapabilities to use data on NFS mounts
#EnableCapabilities off
que l’on active à l’installation via la commande :
# a2enconf z-evolinux-defaults.conf
# a2enconf z-evolinux-badbots.conf
# a2enconf zzz-evolinux-custom
Le fichier /etc/apache2/ipaddr_whitelist.conf
centralise
les adresses IP privilégiées, on peut ainsi utiliser
Include ipaddr_whitelist.conf
à différents endroits dans la
configuration d’Apache sans dupliquer ces adresses :
Pour la gestion des droits on
ajoute dans le fichier /etc/apache2/envvars
:
Et pour éviter les erreurs liés à la
limite de fichiers ouverts trop basse on ajoute dans le fichier
/etc/apache2/envvars
:
Valider la configuration
Avant de redémarrer le serveur, vérifier que vous n’ayez pas introduit des erreurs de syntaxe dans la configuration :
# apache2ctl configtest
Syntax OK
VirtualHost
Le terme VirtualHost correspond à la séparation de plusieurs sites sur un même serveur HTTP. Apache permet d’avoir des VirtualHost basés sur des noms de domaine différents (ou des adresses IP différentes).
Exemple d’un VirtualHost basé sur un nom de domaine via
/etc/apache2/sites-available/example.conf
:
<VirtualHost *:80>
ServerName www.example.com
ServerAlias example.com
DocumentRoot /home/example/www/
<Directory /home/example/www/>
Options SymLinksIfOwnerMatch
AllowOverride AuthConfig Limit FileInfo Indexes
</Directory>
ScriptAlias /cgi-foo /usr/lib/cgi-bin/
<Directory /usr/lib/cgi-bin/>
Options +ExecCGI -MultiViews
AllowOverride None
AuthType Basic
AuthName "Restricted"
AuthUserFile /home/example/.htpasswd
require valid-user
Include ipaddr_whitelist.conf
Require ip 192.0.2.43
</Directory>
AssignUserID www-example example
MaxClientsVHost 150
CustomLog /var/log/apache2/access.log vhost_combined
CustomLog /home/example/log/access.log combined
ErrorLog /home/example/log/error.log
RewriteEngine On
UseCanonicalName On
RewriteCond %{HTTP_HOST} !^www.example.com$
RewriteRule ^/(.*) http://%{SERVER_NAME}/$1 [L,R]
</VirtualHost>
# vim: set filetype=apache expandtab shiftwidth=4 softtabstop=4 tabstop=4 :
# adduser example
# adduser --ingroup example www-example
# mkdir /home/example/{www,log,awstats} && chown example: /home/example/{www,log,awstats}
# a2ensite example
En cas de gestion de multiples VirtualHost nous utilisons l’interface web https://gitea.evolix.org/evolix/evoadmin-web.git
Gestion des droits
Séparation complète des VirtualHost
L’utilisation d’Apache avec MPM-ITK nous permet d’utiliser des utilisateurs/groupes Unix distincts pour chaque VirtualHost. Il est donc impossible pour un utilisateur d’accéder en lecture à des fichiers d’un autre site, même si un langage dynamique comme PHP est utilisé.
# chmod 750 /home/example
Restriction en écriture pour Apache
Par défaut on souhaite donner uniquement un droit de lecture à
Apache, sauf sur certains répertoires du type uploads/,
cache/, etc. On utilise pour cela un utilisateur distinct pour
Apache (www-example
) et la gestion du code via
FTP/SSH/SFTP/SCP/RSYNC (example), le groupe étant identique. Ainsi on
donnera la permission g+w
pour les
répertoires nécessitant un droit en écriture pour Apache. Les fichiers
uploadés via Apache auront des permissions adéquats (g+rwX
)
pour être lus/modifiés/effacés via FTP/SSH/SFTP/SCP/RSYNC (l’opération
chmod
est impossible mais n’est pas nécessaire car le umask
d’Apache est forcé à 007).
# echo "umask 027" >> /etc/profile
# find /home/example -type f -user www-example -exec chmod 660 {} \;
# find /home/example -type d -user www-example -exec chmod 770 {} \;
Attention, il ne faut jamais forcer les droits récursivement sur
toute l’arborescence. Si la restriction en écriture pour Apache est
impossible à gérer, on n’utilisera pas d’utilisateur distinct en
indiquant AssignUserID example example
ce qui éliminera
100% des problèmes de droits (mais pose des problèmes de sécurité).
SSL
Apache peut gérer des connexions sécurisées via HTTPS.
# a2enmod ssl
Il faut alors générer une clé privée et un certificat auto-signé ou délivré par une autorité de certification.
Voir HowtoSSL
Exemple pour générer un certificat auto-signé :
# openssl req -newkey rsa:2048 -sha256 -nodes -keyout private.key -out demande.csr
# openssl x509 -req -days 3650 -sha256 -in demande.csr -signkey private.key -out certificate.crt
# mv private.key /etc/ssl/private/ && chown root:ssl-cert /etc/ssl/private/private.key && chmod 640 /etc/ssl/private/private.key
# mv certificate.crt /etc/ssl/certs/ && chown root:root /etc/ssl/certs/certificate.crt && chmod 644 /etc/ssl/certs/certificate.crt
La configuration d’un VirtualHost pour HTTPS pourra ainsi ressembler à :
<VirtualHost *:80 *:443>
ServerName secure.example.com
ServerAlias www.example.com example.com
SSLEngine on
SSLProtocol all -SSLv2 -SSLv3
SSLCertificateKeyFile /etc/ssl/private/private.key
SSLCertificateFile /etc/ssl/certs/certificate.crt
#SSLCertificateChainFile /etc/ssl/certs/certificates_chain.pem
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteCond %{HTTP:X-Forwarded-Proto} !=https
RewriteRule ^/(.*) https://%{SERVER_NAME}/$1 [L,R=permanent]
</VirtualHost>
La chaine de certification ou le certificat de l’autorité peut aussi
être ajouté à la fin du fichier contenant le certificat
/etc/ssl/certs/certificate.crt
.
Pour une configuration avancée des paramètres SSL, voir HowtoSSL#configuration-apache
Note : la syntaxe
<VirtualHost *:80 *:443>
n’est possible qu’à partir de Debian 8.
Codes d’erreurs :
SSL_ERROR_RX_RECORD_TOO_LONG
:
- Cela peut être causé par un problème dans les directives
. Par exemple, si certains vhost écouent sur
*:$port
et d’autre sur$IP:$port
. - Il est aussi possible que la résolution IPv6 ne se fasse pas correctement.
SSL Client
Il est aussi possible de faire du SSL client pour authentifier celui-ci. Pour cela vous devez avoir une CA en mesure d’émetre des certificats SSL pour vos clients. Vous pouvez utiliser notre outil Shellpki
Pré-requis : Le vhost doit être en SSL.
On va alors ajouter les directives suivante dans le(s) vhosts qui doivent faire de l’authentification client :
# SSL Client, CA example.net
SSLCACertificateFile /etc/ssl/certs/example.net_CA_R01.pem
#SSLCARevocationFile /etc/ssl/crl/example.net_CA_R01.pem
SSLVerifyClient require
SSLVerifyDepth 1
Explication rapide des directives :
SSLCACertificateFile
: Chemin vers le certificat faisant autoritéSSLCARevocationFile
: Chemin vers la liste de révocationSSLVerifyClient
:- require : Le client doit présenter un certificat valide
- optional : Le client peut présenter un certificat valide
- none : Le client n’as pas à présenter un certificat valide
SSLVerifyDepth
: La profondeur de vérification
Après, au travers de la configuration, on a accès aux variables donnant des informations sur le certificat client. On peut ainsi faire facilement des autorisations avec des expressions régulières pour autoriser un groupe de client selon le CN du certificat :
Require expr "%{SSL_CLIENT_S_DN_CN} =~ /.*\.france\.example\.net/"
Voir la documentation du module SSL d’Apache
Logs
https://httpd.apache.org/docs/2.4/fr/logs.html
La directive CustomLog permet de définir le journal des accès ; plusieurs formats existent : combined, common, vhost_combined, etc. Cette directive peut être utilisée plusieurs fois, il y aura plusieurs fichiers de logs différents.
Si besoin, on peut ignorer certaines requêtes HTTP ainsi :
On peut aussi le faire via des Rewrite Rules :
La directive ErrorLog permet de définir le journal d’erreurs Apache.
Logs de la taille de la requête et le temps d’exécution
Si l’on veut loguer, dans un log séparé, la taille de la requête et le temps d’exécution par Apache de cette requête, on peut créer une configuration Apache avec le LogFormat suivant, dans /etc/apache2/conf-available/access-log-time.conf :
LogFormat "%h %t \"%r\" %B %D" measure-time
Et mettre dans le vhost concerné le CustomLog suivant :
CustomLog /home/example/log/access_log_time.log measure-time
Ainsi nous aurons ces infos : IP client, date, la requête, taille de la réponse en octet, temps de réponse en microsecondes.
Configuration avancée
On peut lister la configuration courante via :
# apache2ctl -S
# apache2ctl -D DUMP_VHOSTS
AllowOverrideList
La directive AllowOverrideList
permet d’autoriser
l’utilisation de directives dans les .htaccess
de manière
plus précise que AllowOverride
. Elle prend une liste
explicite de directives. Ces directives sont ajoutés à celles autorisées
par AllowOverride
.
[...]
<Directory /home/$USER/www>
AllowOverrideList ExpiresActive ExpiresByType ExpiresDefault
[...]
</Directory>
AllowOverrideList
ne vérifie pas que la directive existe, il n’y a donc pas besoin de géré le cas où le module fournissant la directive autorisée n’est pas actif.
mod_deflate
La compression des fichiers HTML/TXT/XML/CSS/JS/RSS en GZIP se fait désormais par défaut car le module mod_deflate est activé dès l’installation.
Voici ses paramètres par défaut :
<IfModule mod_deflate.c>
<IfModule mod_filter.c>
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css
AddOutputFilterByType DEFLATE application/x-javascript application/javascript application/ecmascript
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/xml
</IfModule>
</IfModule>
On conseille d’ajouter :
AddOutputFilterByType DEFLATE image/svg+xml
AddOutputFilterByType DEFLATE text/javascript
AddOutputFilterByType DEFLATE application/json
mod_log_forensic
Le module log_forensic trace les entêtes de chaque requête. C’est particulièrement pratique pour comprendre si un proxy envoie bien les bons en-têtes… et dans quel ordre les réponses sont renvoyées par rapport aux requêtes (grâce à des identificants uniques).
# a2enmod log_forensic
# systemctl restart apache2
Il faut ensuite activer par VHost :
<VirtualHost>
[…]
ForensicLog /pat/to/log/forensic.log
[…]
</VirtualHost>
mod_proxy_http
Le module proxy permet d’utiliser Apache en tant que proxy, notamment reverse-proxy HTTP :
# a2enmod proxy_http
Exemple avec un service HTTP local :
ProxyPreserveHost On
ProxyPass /foo/ http://127.0.0.1:8080/bar
ProxyPassReverse /foo/ http://127.0.0.1:8080/bar
<Proxy *>
Require all granted
</Proxy>
Exemple avec un service HTTP distant (pratique pour une migration immédiate d’un serveur vers un autre) :
ProxyPreserveHost On
ProxyPass / http://192.0.2.17/
ProxyPassReverse / http://192.0.2.17/
<Proxy *>
Require all granted
</Proxy>
Si on fait la terminaison ssl, il faut le signaler au service en ajoutant les headers (sinon le service pourra se plaindre de communication en clair):
RequestHeader set "X-Forwarded-Proto" expr=%{REQUEST_SCHEME}
RequestHeader set "X-Forwarded-SSL" expr=%{HTTPS}
et pour un HTTPS distant, on ajoute SSLProxyEngine On
:
SSLProxyEngine On
ProxyPreserveHost On
ProxyPass / https://192.0.2.17/
ProxyPassReverse / https://192.0.2.17/
<Proxy *>
Require all granted
</Proxy>
S’il y a besoin d’appliquer des règles spécifiques tel que des Alias avant que les requêtes soient proxifiés:
Alias /dossier/ /home/$USER/dossier/
<Directory /home/$USER/dossier/>
Options +SymLinksIfOwnerMatch +Indexes
Require all granted
</Directory>
ProxyPreserveHost On
ProxyPassMatch ^/dossier !
ProxyPass / http://127.0.0.1:3504/
ProxyPassReverse / http://127.0.0.1:3504/
Il est possible d’ajouter à la fin de directive
ProxyPass
l’argument timeout=<DELAY>
(en
secondes). La valeur par défaut est celle de ProxyTimeout
,
qui elle-même a pour valeur par défaut la valeur de
Timeout
.
mod_remoteip / mod_rpaf
Le module mod_remoteip (anciennement mod_rpaf) permet d’utiliser la 1ère adresse IP située dans un entête HTTP type X-Forwarded-For pour les logs Apache et directives “Require” demod_authz_host.
Debian >= 9
Voici un exemple d’utilisation en Stretch pour un reverse-proxy avec l’adresse IP publique 192.0.2.10 et le réseau public 192.0.2.128/25 :
# a2enmod remoteip
# cat /etc/apache2/conf-available/zzz-evolinux-custom.conf
<IfModule remoteip_module>
RemoteIPHeader X-Forwarded-For
RemoteIPTrustedProxy 192.0.2.10
RemoteIPTrustedProxy 192.0.2.128/25
</IfModule>
# systemctl reload apache2
Note : sur Debian 9 (Stretch, Apache < 2.4.38), si l’IP 127.0.0.1 est logguée par Apache à la place de celle du client, ajoutez l’IP 127.0.0.1 à la directive RemoteIPInternalProxy.
Note : Le traitement effectué par remoteip “vide” le header X-Forwarded-For. Les IP intermédiaires des proxy listés avec la directive
RemoteIPInternalProxy
seront retirées (comme si on “dépile”). Pour une application en PHP. Il est donc possible que le header ne soit plus présent. L’IP du visiteur sera directement récupérable via$_SERVER['REMOTE_ADDR']
Attention au niveau des logs il peut aussi être nécessaire de
modifier le LogFormat
, en remplaçant %h
par
%a
:
# cat /etc/apache2/conf-available/zzz-evolinux-custom.conf
LogFormat "%v:%p %a %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
LogFormat "%a %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%a %l %u %t \"%r\" %>s %O" common
Remarque : Il est possible de demander à Apache de conserver l’IP du
proxy traversé, notamment pour informer l’application web, au travers
d’un header supplémentaire. On utilisera alors la directive
RemoteIPProxiesHeader X-Forwarded-By
pour placer dans
X-Forwarded-By la liste des IP des proxys
traversés?
Debian 7
Voici un exemple d’utilisation en Wheezy pour un reverse-proxy avec les adresses IP 192.0.2.10 et 192.0.2.11 :
# apt install libapache2-mod-rpaf
# cat /etc/apache2/mods-enabled/rpaf.conf
<IfModule rpaf_module>
RPAFenable On
RPAFsethostname On
#RPAFheader X-Forwarded-For
RPAFproxy_ips 127.0.0.1 192.0.2.10 192.0.2.11
</IfModule>
Note : bien mettre l’IP du reverse-proxy dans
RPAFproxy_ips
mod_xsendfile
Le module xsendfile permet de rediriger l’envoi d’un fichier vers Apache via un Header HTTP, notamment utilisé pour servir des fichiers via Apache tout en permettant de gérer le contrôle d’accès via une application web.
# apt install libapache2-mod-xsendfile
Pour autoriser son utilisation via .htaccess, on ajoute
Options
à AllowOverride
.
mod_negotiation
Le module negotiation permet de servir des documents en fonction de critère, notamment la langue acceptée par le client HTTP.
# a2enmod negotiation include alias
Vous pouvez éditer le fichier
/etc/apache2/conf-enabled/localized-error-pages.conf
qui
permet d’afficher des pages d’erreur en différentes langues, et
décommenter cette partie :
Exemple avec un service HTTP local :
<IfModule mod_negotiation.c>
<IfModule mod_include.c>
<IfModule mod_alias.c>
Alias /error/ "/usr/share/apache2/error/"
<Directory "/usr/share/apache2/error">
Options IncludesNoExec
AddOutputFilter Includes html
AddHandler type-map var
Require all granted
LanguagePriority en cs de es fr it nl sv pt-br ro
ForceLanguagePriority Prefer Fallback
</Directory>
ErrorDocument 400 /error/HTTP_BAD_REQUEST.html.var
ErrorDocument 401 /error/HTTP_UNAUTHORIZED.html.var
ErrorDocument 403 /error/HTTP_FORBIDDEN.html.var
ErrorDocument 404 /error/HTTP_NOT_FOUND.html.var
ErrorDocument 405 /error/HTTP_METHOD_NOT_ALLOWED.html.var
ErrorDocument 408 /error/HTTP_REQUEST_TIME_OUT.html.var
ErrorDocument 410 /error/HTTP_GONE.html.var
ErrorDocument 411 /error/HTTP_LENGTH_REQUIRED.html.var
ErrorDocument 412 /error/HTTP_PRECONDITION_FAILED.html.var
ErrorDocument 413 /error/HTTP_REQUEST_ENTITY_TOO_LARGE.html.var
ErrorDocument 414 /error/HTTP_REQUEST_URI_TOO_LARGE.html.var
ErrorDocument 415 /error/HTTP_UNSUPPORTED_MEDIA_TYPE.html.var
ErrorDocument 500 /error/HTTP_INTERNAL_SERVER_ERROR.html.var
ErrorDocument 501 /error/HTTP_NOT_IMPLEMENTED.html.var
ErrorDocument 502 /error/HTTP_BAD_GATEWAY.html.var
ErrorDocument 503 /error/HTTP_SERVICE_UNAVAILABLE.html.var
ErrorDocument 506 /error/HTTP_VARIANT_ALSO_VARIES.html.var
</IfModule>
</IfModule>
</IfModule>
mod_geoip
Ce module permet de pouvoir traiter différemment des visiteurs par rapport à leur pays d’origine directement depuis la configuration d’Apache. Il utilise pour cela la base GeoIP de Maxmind.
# apt install geoip-database-extra libapache2-mod-geoip
Note: geoip-database-contrib (dans les dépots contrib) va installer
un cron qui va mettre à jour les fichiers de la base GeoIP. Ces fichiers
de base se trouvent dans /usr/share/GeoIP/
Quand on va avoir besoin de GeoIP, il faut penser à l’activer dans le(s) fichier(s) de confs
<IfModule mod_geoip.c>
GeoIPEnable On
GeoIPDBFile /usr/share/GeoIP/GeoIP.dat
</IfModule>
De là, le pays d’origine du visiteur, ainsi que d’autres informations sont placées dans des variables d’environnement utilisables dans le VHOST.
Exemple : Autoriser que les visiteurs venant de France pour un accéder à un dossier précis :
<Directory /var/www/foo/bar>
Require all denied
SetEnvIf GEOIP_COUNTRY_CODE FR AllowCountry
Require env AllowCountry
</Directory>
Faire une redirection suivant le pays :
RewriteEngine on
RewriteCond %{ENV:GEOIP_COUNTRY_CODE} ^FR$
RewriteRule ^(.*)$ https://www.example.fr$1 [R,L]
La documentation complète du module est là Pour plus d’info, il y a la documentation complète du module
mod_davfs
Ce module permet de prendre en charge WebDAV.
Pour l’activer :
# a2enmod davfs
Ensuite pour activer le module, il suffit de rajouter
Dav On
dans le virtualhost voulu.
Exemple de vhost :
<VirtualHost *:443>
ServerName dav.example.com
DocumentRoot /home/webdav/
<Directory "/home/webdav/">
Dav On
AuthType Basic
AuthName DAV
AuthUserFile "/etc/apache2/webdav.htpasswd"
Require valid-user
</Directory>
ErrorLog ${APACHE_LOG_DIR}/dav_error.log
CustomLog ${APACHE_LOG_DIR}/dav_access.log combined
SSLEngine on
SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
</VirtualHost>
mod_qos
http://mod-qos.sourceforge.net/
Le module qos (Quality of Service) est un module de qualité de service pour le serveur Web Apache mettant en œuvre des mécanismes de contrôle qui peuvent fournir différents niveaux de priorité à différentes requêtes HTTP.
mod_qos peut être utilisé pour déterminer quelles requêtes doivent être traitées et lesquelles ne doivent pas l’être afin de ne pas avoir une surcharge sur les ressources du serveur Apache.
Le module collecte différents attributs tels que l’URL de demande, les en-têtes de demande et de réponse HTTP, l’adresse IP source, les codes de pays, le code de réponse HTTP, les données d’historique (basées sur la session utilisateur et l’adresse IP source), le nombre de demandes simultanées au serveur (total ou requêtes ayant des attributs similaires), le nombre de connexions TCP simultanées (totales ou à partir d’une seule IP source), etc…
Dans une configuration simple on peux utilisé les variables suivantes :
<IfModule qos_module>
QS_SrvRequestRate 120
QS_SrvMaxConn 150
QS_SrvMaxConnClose 200
QS_SrvMaxConnPerIP 20
</IfModule>
QS_SrvRequestRate
permet d’appliquer un débit de
téléchargement minimal en lecture uniquement (en octet/sec)
QS_SrvMaxConn
limites de connexion par virtual host
QS_SrvMaxConnClose
permet un support keep-alive jusqu’à ce
que le serveur atteigne n connexions QS_SrvMaxConnPerIP
autorise n connexion par adresse ip
Si l’on veux mettre en liste blanche une ip bien précise on peux le faire avec la configuration suivante (remplacer $IP par la bonne valeur) :
SetEnvIf Remote_Addr $IP QS_VipRequest=yes
mod_substitute
- Activer le module avec la commande
a2enmod substitute
, puis redémarrer Apache. - Dans le vhost où on souhaite faire la substitution :
AddOutputFilterByType SUBSTITUTE text/html
Substitute "s/REGEXP/REMPLACEMENT/i"
Le « i » à la fin de la commande de substitution permet d’ignorer la casse.
Cas d’un proxy
Si le vhost qui fait la substitution fait aussi proxy entre le client et le serveur web, la substitution ne fonctionnera pas, car le corps de la réponse est compressé. Pour faire la substitution, il faut modifier la manière dont Apache traite la réponse avant de l’envoyer au client :
SetOutputFilter INFLATE;proxy-html;DEFLATE
AddOutputFilterByType SUBSTITUTE text/html
Substitute "s/REGEXP/REMPLACEMENT/i"
mod_auth_openidc
Le module mod_auth_openidc est un module certifié “OpenID” qui implémente le OpenID Connect Relying Party functionality (RP).
Il relaie l’authentification d’un utilisateur à un OpenID Connect Provider (OP) comme keycloak et celui lui communique en retour les informations d’identification de l’utilisateur.
Le module permet donc de protéger une application en réservant l’accés aux utilisateurs authentifiés, il peut aussi communiquer à l’application ces informations d’authentification via des headers notamment.
Cela permet de ne pas avoir à utiliser un proxy oauth2 comme Oauth2-Proxy
Page du module : github
- Installation
# apt install libapache2-mod-auth-openidc
- Exemple de configuration pour gitit protégé par keycloak ( royaume
foo
)
<VirtualHost *:443>
[...]
OIDCProviderMetadataURL https://sso.exemple.com/realms/foo/.well-known/openid-configuration
OIDCClientID gitit
OIDCClientSecret 4HyjDjKYZktFvtYYJ77CCrBawLCb4UD9
# OIDCRedirectURI is a vanity URL that must point to a path protected by this module but must NOT point to any content
OIDCRedirectURI https://gitit.exemple.com/chemin/qui/nexiste/pas
OIDCCryptoPassphrase ZW7M0DnzF8SXngLUOwuwntuWFN1TjGfj
OIDCStateMaxNumberOfCookies 10 true
<Location />
AuthType openid-connect
Require valid-user
RequestHeader unset Accept-Encoding
RequestHeader set REMOTE_USER "%{OIDC_CLAIM_EMAIL}e" env=OIDC_CLAIM_EMAIL
</Location>
</VirtualHost>
OIDCClientID
etOIDCClientSecret
correspondent au nom du client et secret du client dans keycloakOIDCRedirectURI
doit pointer vers une url qui n’existe pas dans l’application, soyez créatif ! (attention l’url sera visible des clients)OIDCCryptoPassphrase
est un passphrase utilisé pour le chiffrement des cookies notamment, vous pouvez en générer une avechead /dev/urandom | tr -dc A-Za-z0-9 | head -c 32
RequestHeader
peut être utiliser pour passer des informations fournies par le OP a l’application via un header
Remarque Importante : Ces directives NE peuvent PAS être utilisées dans les fichiers
.htaccess
Un cookie de session nommé mod_auth_openidc_session
est
stocké coté utilisateur, celui-ci peut être supprimé pour se déconnecter
( log out ), il faudra ensuite rediriger vers le endpoint de
déconnection du sso en amont, par exemple avec keycloak
https://sso.exemple.com/realms/foocorp/protocol/openid-connect/logout
( ou se deconnecter du sso manuellement )
On peut invalider les session en cours avec un simple reload de apache.
Autorisations
Require
Historiquement Apache, utilisait les directives Order
,
Allow
, Deny
, et Satisfy
pour
définir les autorisations des ressources qu’il fournissait. Depuis la
version 2.4 ces directives sont dépréciées, il ne faut donc plus les
utiliser ; bien qu’elles soient toujours utilisables via le module mod_access_compat
.
Ces quatres directives sont remplacées par Require
fournies
par le module mod_authz_host
; la migration sommaire d’un module à l’autre est décrit dans la release
note 2.4.
/!\ Il ne faut jamais mélanger d’anciennes directives comme Order, Allow ou Deny avec des nouvelles comme Require, car le comportement n’est pas défini ce qui vous emenera dans du deboguage interminable. /!\
Voici quelques exemples d’utilisation de la directive
Require
:
# Autoriser l'accès à tout le monde
Require all granted
# Interdire l'accès à tout le monde
Require all denied
# Limiter l'accès à une IP
Require ip 192.0.2.21
# Autoriser l'accès "local"
Require local
# Autoriser l'accès à tout le monde mais bloquer une IP spécifique
<RequireAll>
Require all granted
Require not ip 192.0.2.21
</RequireAll>
# Limiter l'accès à des utilisateurs valides (authentification HTTP)
AuthType Basic
AuthBasicProvider file
AuthUserFile /foo/.htpasswd
Require valid-user
# Autoriser une IP OU un utilisateur valide (équivalent de l'ancien "Satisfy Any")
AuthType Basic
AuthBasicProvider file
AuthUserFile /foo/.htpasswd
Require valid-user
Require ip 192.0.2.21
# Autoriser des IP ET un utilisateur valide
AuthType Basic
AuthBasicProvider file
AuthUserFile /foo/.htpasswd
<RequireAll>
<RequireAny>
Require ip 192.0.2.21
Require ip 192.0.2.22
</RequireAny>
require valid-user
</RequireAll>
Plomberie Require (à valider)
https://httpd.apache.org/docs/current/fr/mod/mod_authz_core.html#require
Apache va prendre en compte toutes les sections
<Directory XXX>
(ou <Files XXX>
)
de la plus large à la plus précise :
- Tout d’abord la (ou les) section(s)
<Directory />
- Ensuite
<Directory /foo>
- Ensuite
<Directory /foo/bar>
- etc.
Les options explicites de la section la plus précise écraseront les options explicites précédentes.
Si il y a plusieurs sections <Directory XXX>
identiques, Apache va les évaluer dans cet ordre :
- Configuration générale
- Dans
<VirtualHost XXX>
- Dans les fichiers
.htaccess
Les options explicites dans <VirtualHost XXX>
écrasent celles de la configuration générale. Les options explicites des
fichiers .htaccess
écrasent celles dans
<VirtualHost XXX>
Enfin, si dans un même niveau de section il y a des options identiques, ce sont les dernières qui écrasent les premières.
Voici un exemple :
<Directory />
Require ip 192.0.2.21
</Directory>
<Directory /home/foo>
Require ip 192.0.2.23
</Directory>
<Directory /home/foo>
Require ip 192.0.2.24
</Directory>
<VirtualHost *>
Servername www.example.com
DocumentRoot /home/foo/www
<Directory />
Require ip 192.0.2.22
<Directory>
<Directory /home/foo>
Require ip 192.0.2.25
</Directory>
</VirtualHost>
Les évaluations se font de l’IP la plus petite à la plus grande… eu final, seule l’IP 192.0.2.25 est autorisée !
Note : les options peuvent aussi être utilisées dans une section
<Location XXX>
, elles vont écraser les options
<Directory XXX>
(ou <Files XXX>
)
mais on préfèrera éviter.
Note 2 : l’écrasement des options correspond à l’option par défaut
AuthMerging Off
mais cela peut
se changer
Authentification HTTP
HTTP Basic Authentication (mod_auth_basic)
Basic est la méthode d’authenfication HTTP la plus simple et la plus répandue. La configuration se fait via VirtualHost ou .htaccess :
AuthType Basic
AuthName "Restricted"
AuthUserFile /foo/.htpasswd
AuthGroupFile /dev/null
require valid-user
La gestion du fichier .htpasswd se gère via la commande
htpasswd
.
Pour créer l’utilisateur foo :
HTTP Digest Authentication (mod_auth_digest)
Il est possible d’utiliser la méthode d’authentification Digest plutôt que Basic en activant le module :
# a2enmod auth_digest
La configuration est quasi-identique au type Basic :
La gestion du fichier .htpasswd se gère alors via la
commande htdigest
.
Authentification via LDAP (mod_authnz_ldap)
# a2enmod authnz_ldap
Voici un exemple de configuration :
AuthBasicProvider ldap
AuthName "Restricted"
AuthType Basic
AuthLDAPURL ldaps://ldap.example.com/ou=people,dc=example,dc=com?uid?one?(filter)
Require valid-user
Pour le cas d’une connexion à un Active Directory, il faut probablement remplacer
uid
parsAMAccountName
.
Note : pour utiliser ldaps avec un certificat non reconnu
par le système, il faut ajouter la directive suivante dans la
configuration globale d’Apache
LDAPVerifyServerCert off
.
Rewrite Rules
- mod_rewrite : http://httpd.apache.org/docs/2.4/mod/mod_rewrite.html
- drapeaux utilisables : https://httpd.apache.org/docs/2.4/rewrite/flags.html
On doit mettre la valeur de la directive suivante pour activer les règles de réécriture :
RewriteEngine On
Voici quelques exemple de règles de redirections:
# rediriger la page d'accueil avec un code 301
RedirectPermanent / http://new.example.com/
# rediriger n'importe quelle requête en conservant le chemin
RedirectMatch ^/(.*)$ http://new.example.com/$1
# rediriger la page d'accueil vers un autre chemin
RedirectMatch ^/$ /sub/
# rediriger n'importe quelle requête en conservant le chemin avec des exceptions
RewriteCond %{REMOTE_ADDR} !^192\.0\.2\.129
RewriteRule ^/(.*) http://new.example.com/$1 [L,R=permanent]
# rediriger vers HTTPS sauf pour certaines requetes
RewriteCond %{HTTPS} !=on
RewriteCond %{HTTP:X-Forwarded-Proto} !=https
RewriteCond %{REQUEST_URI} !^/.well-known/
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [L,R]
# si l'on veut rediriger vers le même FQDN
RewriteRule ^ https://%{HTTP:Host}%{REQUEST_URI} [L,R]
# le drapeau NC permet de ne pas tenir compte de la casse
RewriteRule ^/FoO.tXt /sub/ [L,R,NC]
# empêcher des requêtes POST sur une URL particulière
RewriteCond %{REQUEST_METHOD} POST
RewriteRule ^/foo.txt [L,F]
# rediriger vers HTTPS dans un VirtualHost mixte HTTP/HTTPS
RewriteCond %{HTTPS} !=on
RewriteRule ^/(.*) https://%{SERVER_NAME}/$1 [L,R=permanent]
# mettre un site en maintenance (code 503) avec des exceptions
RewriteCond %{REMOTE_ADDR} !^192\.0\.2\.129
RewriteRule ^.*$ / [R=503,L]
ErrorDocument 503 "Maintenance temporaire, veuillez patienter. Merci."
#ErrorDocument 503 http://maintenance.evolix.org/
#Header Set Cache-Control "no-cache, no-store"
#Header Set Pragma "no-cache"
# Rediriger un sous-domaine *-app.domaine.tld vers *.domaine.tld :
RewriteCond %{HTTP_HOST} ^(.*)-app.domaine.tld$
RewriteRule ^/(.*)$ https://%1.domaine.tld/$1 [R]
# Afficher une page de maintenance si elle existe et le contenu du site s'affiche pour ceux ayant l'IP spécifié.
RewriteCond %{REMOTE_ADDR} !^123\.456\.X\.X
RewriteCond %{DOCUMENT_ROOT}/maintenance.html -f
RewriteCond %{SCRIPT_FILENAME} !maintenance.html
RewriteRule ^.*$ /maintenance.html [R=503,L]
ErrorDocument 503 /maintenance.html
Header Set Cache-Control "max-age=0, no-store"
# Rediriger sur une page sans ses paramètres et valeurs.
RewriteCond %{REQUEST_URI} ^/index\.php$
RewriteCond %{QUERY_STRING} ^parametre=valeur$
RewriteRule (.*) /page-destination [QSD,R=301,L]
# Substituer un caractère (tel que page.php/ en page.php? ) :
RewriteCond %{REQUEST_URI} (.*page.php)\/(key=.*)
RewriteRule (.*) /%1?%2 [QSD,R=301,L]
# Envoyer une erreur 403 en fonction de l'entête User-Agent
RewriteCond %{HTTP_USER_AGENT} "FooBot" [NC]
RewriteRule ^ - [F,L]
# On peut positionner une variable d'environnement utilisée plus tard
RewriteCond %{QUERY_STRING} jogo
RewriteCond %{HTTP_USER_AGENT} "Googlebot"
RewriteRule ^ - [F,L,E=dontlog:1]
CustomLog /home/example/log/access.log combined env=!dontlog
Pour supprimer un Query String avec une Rewrite Rule : https://www.philipphoffmann.de/blog/2012/08/16/how-to-discard-the-query-string-in-a-rewriterule-apache-mod_rewrite/
Conditions
À partir de la version Apache 2.4, on peut utiliser des conditions pour l’application des directives (l’imbrication de multiples n’est disponible que pour les versions >= 2.4.26).
<If "%{HTTP_HOST} == 'test.example.com'">
SetEnv APP_ENV "test"
</If>
<If "%{HTTP_QUERY} =~ /wp-admin*/">
...
</If>
Expressions possibles : https://httpd.apache.org/docs/2.4/expr.html
Attaques DOS (Denial Of Service) ou XSS
mod_evasive
Le module mod_evasive permet de limiter certaines attaques DoS en limitant l’accès à une ou plusieurs pages par un temps de banissement d’une IP source. Par défaut, nous ajustons la configuration de façon à ce que si une adresse IP accède plus de 5 fois à la même page en 30s, ou à plus de 30 requêtes sur tout le site en 1s, il sera banni (erreur HTTP 403) pendant 60s. Une notification sera alors envoyée à syslog et par email.
Dans la configuration de mod_evasive (dans
/etc/apache2/conf-enabled/
) :
<IfModule mod_evasive20.c>
DOSHashTableSize 3097
# Si une adresse IP accède plus de 5 fois à la même page en 30s
DOSPageCount 5
DOSPageInterval 30
# Si une adresse IP accède plus de 30 fois le site en 1s
DOSSiteCount 30
DOSSiteInterval 1
DOSBlockingPeriod 60
DOSEmailNotify security@example.com
</IfModule>
Attention, pour certains sites avec de nombreuses ressources statiques sur le même serveur HTTP, il faut souvent désactiver mod_evasive pour éviter des faux-positifs et ne l’utiliser qu’en cas d’attaques récurrentes.
On peut mettre en liste blanche une ip ou une plage d’ip, pour que le mod_evasive ignore les requêtes venant de ceux-ci :
N.B. : si on veut ignorer plusieurs IP, il faut ajouter des lignes.
N.B. : on ne peut pas utiliser DOSWhitelist
sous
<Directory>
.
On peut aussi utiliser un wildcard * peut être autorisé sur les 3 derniers octets, si nécessaire.
Pour “désactiver” mod_evasive spécifiquement pour un vhost :
Fail2Ban
Fail2Ban est indépendant d’Apache, mais peut être utilisé pour de la détection plus précise que mod_evasive : il va lire en permanence les journaux générés par Apache (ce qui peut être coûteux en ressources) et il pourra bannir des adresses IP si cela répond à certaines règles comme trop de connexions à une page d’authentification par exemple.
Voir https://wiki.evolix.org/HowtoFail2Ban#apache-nginx
ModSecurity
La modification de la configuration ModSecurity nécessite un redémarrage d’Apache2.
L’utilisation du moteur ModSecurity permet de bloquer
certaines requêtes HTTP (injections SQL, attaques XSS connues, etc.) au
niveau d’Apache. On l’installe avec le OWASP ModSecurity Core Rule Set
(CRS) qui est un ensemble de règles couvrant un large spectre d’attaques
possibles. Elles sont définit dans le dossier
/usr/share/modsecurity-crs/rules/
Faisant office de pare-feu applicatif, le contenu des demandes clients ET les réponses serveurs sont analysés à la recherche de signes d’attaques pouvant générer de faux-positifs. Il faudra par la suite ajuster les réglages en fonction du niveau de sécurité que l’on veut appliquer à l’application.
Si on retrouve dans le log le message
Access denied with code ...
c’est que ModSecurity bloque
une requête, autrement c’est que le score d’anomalie est trop faible ou
que ModSecurity est en mode “Audit”.
#### Intégration de ModSecurity dans Apache
Installer les deux paquets suivants :
# apt install libapache2-mod-security2 modsecurity-crs
Nous faisons une configuration minimale via
/etc/apache2/conf-available/modsecurity.conf
:
<IfModule mod_security2.c>
# Activation du moteur gérant les règles CRS
SecRuleEngine On
## Les demandes produit par le client
# Activation de l'analyse du contenu
SecRequestBodyAccess On
# Taille limite du contenu en octets
#SecRequestBodyLimit 134217728
# Taille maximale du contenu en mémoire et déplacé sur le disque si besoin
#SecRequestBodyInMemoryLimit 131072
# Emplacement sur le disque de données temporaires
SecTmpDir /tmp
## Les réponses produite par le serveur
# Activation de l'analyse du contenu
SecResponseBodyAccess Off
# Taille maximale du contenu en mémoire et déplacé sur le disque si besoin
#SecResponseBodyLimit 524288
# Autoriser les réponses de ces types
SecResponseBodyMimeType (null) text/html text/plain text/xml
# Changer l'entête HTTP "Server: Apache"
#SecServerSignature "Apache/2.2.0 (Fedora)"
# Emplacement des fichiers suspicieux envoyé au serveur
SecUploadDir /tmp
# Conserver les fichiers interceptés
SecUploadKeepFiles Off
# Emplacement des données persistantes (données d'adresse IP, données de sessions, etc)
SecDataDir /tmp
# Action par défaut
SecDefaultAction "log,auditlog,deny,status:406,phase:2"
# Activer le réglage fin du moteur
SecAuditEngine Off
# Définis quels codes HTTP doivent être enregistrés dans les logs
#SecAuditLogRelevantStatus "^[45]"
# Utiliser un seul fichier de log
SecAuditLogType Serial
# Emplacement du fichier de log
SecAuditLog /var/log/apache2/modsec_audit.log
# Définis les types de métadonnées qui seront conservés
SecAuditLogParts "ABIFHZ"
# Définis l'argument utilisé pour séparer les URL encodés
#SecArgumentSeparator "&"
# Définis le format du cookie
SecCookieFormat 0
# Emplacement du fichier de debug
SecDebugLog /var/log/apache2/modsec_debug.log
# On définit pas de log
SecDebugLogLevel 0
#########
# RULES
#########
# Removed because it does not play well with apache-itk
# Can be removed when modsecurity 2.9.3 hits debian
# See https://github.com/SpiderLabs/ModSecurity/issues/712
SecRuleRemoveById "910000-910999"
SecRuleRemoveById "920000"
SecRuleRemoveById "930000"
</IfModule>
Documentation de référence sur les directives Apache de ModSecurity
Nous désactivons le log d’audit par défaut, puisque l’information
enregistrée dans le log d’erreur Apache est suffisante pour connaître la
raison d’un bloquage. Par contre, il est utile de le réactiver (avec
SecAuditEngine RelevantOnly
) lorsque vous faites un audit
des règles applicables a un serveur ou lors de la rédaction de nouvelles
règles. Dans ce cas, il existe des outils
pour faciliter l’obtention de statistiques du audit log.
Ajustement des règles de ModSecurity
Les paramètres du Core Rule Set de ModSecurity peuvent être
personalisées dans /etc/modsecurity/crs/crs-setup.conf
.
C’est notamment là qu’on peut autoriser les méthodes HTTP bloquées par défaut, comme PATCH, PUT, DELETE…
Affinage des règles pour les applications web type Wordpress
Voir https://coreruleset.org/docs/concepts/false_positives_tuning/
Le ModSecurity Core Rule Set contient des règles spécifiques pour traiter les faux positifs liés à l’utilisation de certaines applications web tel que WordPress.
Il faut alors activer une règle de ce type en sélectionnant la ligne
correspondant à son applications, par exemple
tx.crs_exclusions_wordpress=1
:
#SecAction \
# "id:900130,\
# phase:1,\
# nolog,\
# pass,\
# t:none,\
# setvar:tx.crs_exclusions_cpanel=1,\
# setvar:tx.crs_exclusions_dokuwiki=1,\
# setvar:tx.crs_exclusions_drupal=1,\
# setvar:tx.crs_exclusions_nextcloud=1,\
# setvar:tx.crs_exclusions_phpbb=1,\
# setvar:tx.crs_exclusions_phpmyadmin=1,\
# setvar:tx.crs_exclusions_wordpress=1,\
# setvar:tx.crs_exclusions_xenforo=1"
SecAction \
"id:900130,\
phase:1,\
nolog,\
pass,\
t:none,\
setvar:tx.crs_exclusions_wordpress=1"
Désactiver des règles au cas par cas
En général, plutôt que de les désactiver, on préfère régler plus
finement le règles définies dans le Core Rules Set (CRS) dans
/etc/modsecurity/crs/crs-setup.conf
.
Si après cela, certaines règles posent toujours des problèmes, on peut les désactiver au cas par cas.
Pour trouver dans un log toutes les règles qui se sont déclenchées ainsi que leur fréquence :
# grep ModSecurity <LOG_PATH> | grep -E 'id "[0-9]{6}"' | sed -E 's/.*([0-9]{6}).*/\1/' | sort | uniq -c
5 911100
1 920180
287 920420
8 930130
300 949110
300 980130
On peut ensuite les désactiver au cas par cas dans un vhost ou dans
un conf.d
d’Apache :
<IfModule mod_security2.c>
SecRuleRemoveById "949110"
...
</IfModule>
Ne jamais désactiver 980130, c’est celle qui fait les actions de blocage. Si il y a un problème de faux positif, c’est une autre règle qui pose problème. Pas celle là. Si vous voulez passer en mode de détection uniquement, utiliser
SecRuleEngine DetectionOnly
.
Ajouter des règles
On peut écrire ses propres règles que l’on mettra par exemple dans un
fichier /etc/modsecurity/*.conf
.
$ cat /etc/modsecurity/cve-2018-6389.conf
SecRule REQUEST_URI "@rx (?i:/wp-admin/load-scripts.php?.*?(load%5B%5D|load\[\]|load%5B\]|load\[%5D)=([^&,]*,){13,})" "id:20186389,msg:'Potential use of CVE-2018-6389',deny"
Désactiver ModSecurity pour un vhost ou un répertoire
On évite de désactiver complètement ModSecurity.
Si l’affinage des règles et leur désactivation au cas par cas ne
suffit pas, on peut désactiver ModSecurity spécifiquement pour un vhost
ou un répertoire avec la directive SecRuleEngine Off
.
Ceci peut être utile en cas de problèmes, mais on préfère identifier
les règles problématiques dans les logs et les désactiver au cas par cas
avec SecRuleRemoveById XXXXXX
, comme nous le faisons avec
les règles 910XXX
.
<Directory "/home/monsite/www/wp-admin">
<IfModule security2_module>
SecRuleEngine Off
</IfModule>
</Directory>
Statistiques
AWStats
Voir HowtoAwstats.
Note : une configuration AWStats peut être forcée au sein
d’un VirtualHost grâce à la directive
SetEnv AWSTATS_FORCE_CONFIG example
Les stats AWStats du
VirtualHost sont alors accessibles via l’url : https://mondomaine.com/cgi-foo/awstats.pl
GoAccess
Voir HowtoGoaccess.
Matomo
Voir HowtoMatomo.
Monitoring
log2mail
Pour être alerté en cas de Segmentation fault, on ajoute la configuration suivante au logiciel log2mail :
file = /var/log/apache2/error.log
pattern = "Segmentation fault"
mailto = alert@example.com
# adduser log2mail adm
# servicectl restart log2mail
apachetop
L’indispensable apachetop permet de surveiller en direct l’activité d’Apache via un fichier d’access_log (format combined) :
$ apachetop -f access.log -T 3600 -q
On peut alors visualiser les URLs les plus sollicitées (querystrings inclus grâce à l’option -q), et switcher à l’aide de la touche d pour voir les adresses IP source ; à l’aide de la touche n pour voir la répartition en codes HTTP 2xx/3xx/4xx/5xx.
Voici l’ensemble des commandes disponibles :
d : switch l'affichage entre URLs/IPs source/Referrers
n : switch l'affichage entre le nombre de requêtes et la répartition en codes HTTP 2xx/3xx/4xx/5xx
h or ? : afficher l'aide
p : (un)freeze l'affichage
q : quitter
up/down : sélectionner une ligne (affichage d'une '*' sur la ligne)
right/left : entrer/sortir dans les détails pour la ligne sélectionnées
DÉTAILS:
s: TRI PAR:
r) requêtes R) reqs/sec b) bytes B) bytes/sec
2) 2xx 3) 3xx 4) 4xx 5) 5xx
t: AFFICHE DANS LE SOUS-MENU:
u) urls r) referrers h) adresses IP source
f: FILTRES:
a) ajout d'un filtre c) suppression des filtres s) montrer les filtres actifs
a: AJOUT D'UN FILTRE TYPE REGEX
u) appliqué à l'URL r) appliqué au Refferer h) appliqué à l'adresse IP source
server-status
L’activation du server-status est utile pour une observation manuelle ou automatique.
<IfModule mod_status.c>
ExtendedStatus On
<Location /server-status-XXXX>
SetHandler server-status
Include ipaddr_whitelist.conf
Require ip 192.0.2.43
Require ip 127.0.0.1
</Location>
</IfModule>
Sauvegarde automatique du server-status
Placer le code suivant dans un fichier tel que /usr/share/scripts/save_apache_status.sh
#!/bin/bash
set -e
DIR=/var/log/apache-status
URL=http://127.0.0.1/server-status-XXXX
TS=`date +%Y%m%d%H%M%S`
FILE="$DIR/$TS.html"
mkdir -p $DIR
curl -s $URL > $FILE
chmod 640 $FILE
find $DIR -type f -mtime +1 -delete
exit 0
Si on veux le sauvegarder dans /var/www/ pour y acceder par le serveur web, on peux utilisé un script de ce type :
#!/bin/sh
DATE=$(date +%Y-%m-%d-%H-%M)
URL=$(grep -m1 "<Location.*server-status" /etc/apache2/sites-enabled/000-default.conf|awk '{ print $2 }' |cut -d'>' -f1)
mkdir -p /var/www/apache-status
curl -Ls http://127.0.0.1${URL} > "/var/www/apache-status/${DATE}.html" && chmod 644 "/var/www/apache-status/${DATE}.html"
find "/var/www/apache-status/" -type f -mtime +4 -delete
Il sera ensuite appelé dans une tâche cron de root :
* * * * * /usr/share/scripts/save_apache_status.sh
Munin
Activer les plugins Munin pour Apache ainsi :
# cd /etc/munin/plugins
# ln -s /usr/share/munin/plugins/apache_accesses
# ln -s /usr/share/munin/plugins/apache_processes
# ln -s /usr/share/munin/plugins/apache_volume
Les plugins se basent sur server-status, configurable via
/etc/munin/plugin-conf.d/munin-node
:
[apache_*]
env.url http://127.0.0.1:%d/server-status-XXXX?auto
env.port 80
Pour tester :
# sudo -u munin munin-run apache_accesses
Une valeur du type accesses80.value 19372070
doit être
renvoyé. Si la commande indique U, C’est qu’il y a un soucis d’accès à
la page server-status. Vérifier avec
GET http://127.0.0.1:%d/server-status-XXXX?auto
.
Support du ssl
Adapter la configuration via
/etc/munin/plugin-conf.d/munin-node
:
[apache_*]
env.url https://127.0.0.1:%d/server-status-XXXX?auto
env.port 443
env.PERL_LWP_SSL_VERIFY_HOSTNAME 0
FAQ
Autorisation DirectoryIndex via .htaccess ?
Ajouter Indexes
dans AllowOverride
.
Si il s’agit de la seul directive ayant besoin d’être autorisée, il
est aussi possible d’utiliser
AllowOverrideList DirectoryIndex
.
Autorisation directives mod_expires ou mod_cache via .htaccess ?
Deux façons :
- Simple : Ajouter
Indexes
dansAllowOverride
. - Explicite et précis : Ajouter les directives dans une directive
AllowOverrideList
(même niveau queAllowOverride
). Les directives demod_expires
sontExpiresActive
,ExpiresByType
etExpiresDefault
. (mod_cache
a beaucoup plus de directives: https://httpd.apache.org/docs/current/mod/mod_cache.html)
Autorisation de Rrewrite Rules (mod_rewrite) via .htaccess ?
Ajouter FileInfo
dans AllowOverride
et
+SymLinksIfOwnerMatch
ou +FollowSymLinks
dans
Options
.
Attention, les règles de ré-écritures dans un .htaccess
sont différentes par rapport à un VirtualHost. Notamment les chemins
sont écrits en relatifs, sans /
au début, par exemple :
RewriteEngine on
RewriteRule ^.well-known/autoconfig/mail/config-v1.1.xml /home/foo/pub/config-v1.1.xml
Autorisation Options via .htaccess ?
Si l’on modifie l’option php_admin_value memory_limit
:
un reload/graceful suffit pour prendre en compte la
modification.
Exemple pour le AllowOverride :
AllowOverride Options=All,MultiViews Indexes AuthConfig Limit FileInfo
Authentification LDAP avec serveur LDAP en local
Quand on utilise l’authentification LDAP avec un serveur en local, il faut démarrer slapd avant de démarrer Apache, sinon celui-ci se plaint qu’il ne peut pas contacter le serveur LDAP, et bloque l’accès aux pages … (erreur 500).
Pour cela, dans le script d’init d’apache2 ajouter slapd dans la
directive Required-Start:
# Required-Start: $local_fs $remote_fs $network $syslog $named slapd
Supprimer les liens logiques et ré-générer les.
# insserv -r apache2
# insserv apache2
Page personnalisée lors code erreur HTTP
On peut ajouter une configuration générale à tous les vhosts afin de faire apparaître le contenu d’une page personnalisée selon le code de retour HTTP.
Pour cela on ajoute un nouveau fichier de configuration à l’intérieur de la racine apache, car cela concernera tout les vhosts.
/etc/apache2/error.conf
:
ErrorDocument XXX /YYYYYY/page.html
Alias /YYYYYY /var/www/
<Directory /var/www/>
Require all granted
DirectoryIndex page.html
</Directory>
Remplacer XXX par le code erreur HTTP souhaité et YYYYY par le nom de Location souhaité (URL) - vu que global à tous les vhosts, prendre une chaîne aléatoire.
Ensuite, il suffit simplement d’ajouter le fichier dans la configuration générale d’Apache :
/etc/apache2/apache2.conf
:
Site web cassé en https avec un reverse proxy
Il est possible que l’application (joomla par exemple) pense qu’elle est accédée en HTTP à tort parce qu’en réalité c’est un reverse proxy qui fait la terminaison ssl. On peut indiquer dans le vhost
Ainsi l’application saura qu’elle est accédée en HTTPS à condition que le reverse proxy soit bien configuré et envoie X-Forwarded-Proto.
Bloquer un User-Agent
On peut bloquer des bots faisant des requêtes non légitimes via :
Bloquer certaines URI
Par exemple, pour retourner une 404 si on interroge
/.git
:
Site avec accents cassés
Si vous avez des vieux fichiers sources (TXT, HTML), il est probable
qu’ils utilisent l’encodage ISO-8859 au lieu d’Unicode. On peut alors
forcer la reconnaissance de cet encodage (ajout de charset= dans
l’entête HTTP Content-Type) via l’option AddDefaultCharset
utilisable globalement, dans un VirtualHost, dans un Directory ou même
un .htaccess si autorisé :
AddDefaultCharset ISO-8859-15
Note : si vous avez des fichiers PHP en ISO-8859, vous devrez forcer l’option
default_charset
de PHP :php_value default_charset ISO-8859-15
php_value error_reporting
Pour régler la valeur PHP error_reporting
par vhost, il
ne faut pas utiliser les constantes PHP mais le masque binaire.
Extrait de http://php.net/manual/fr/errorfunc.constants.php : «
Note: Vous pouvez utiliser ces constantes dans le fichier
php.ini
mais pas hors de PHP, comme dans le fichier
httpd.conf
, où vous devez utiliser les valeurs des champs
de bits. »
Cela se calcule en partant de 32767 et en retirant les valeurs que l’on ne veut pas.
Exemples :
E_ALL
et~E_DEPRECATED
et~E_STRICT
:php_value error_reporting 22527
E_ALL
et~E_DEPRECATED
et~E_STRICT
et~E_NOTICE
:php_value error_reporting 22519
Ne pas logguer le query-string dans le access.log
On peut ajouter un nouveau format de log dans
/etc/apache2/apache.conf
et l’utiliser après dans les
définitions de fichier de logs :
LogFormat "%h %l %u %t \"%m %U %H\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combinednoqtring
Proxy WebSocket socket.io
# a2enmod proxy_wstunnel
# Socket.io
RewriteRule /socket.io/websocket/ - [R=200,L]
ProxyPass /socket.io/socket.io.js http://127.0.0.1:8080/socket.io/socket.io.js
ProxyPassReverse /socket.io/socket.io.js http://127.0.0.1:8080/socket.io/socket.io.js
ProxyPass /socket.io/websocket ws://127.0.0.1:8080/socket.io/websocket
ProxyPassReverse /socket.io/websocket ws://127.0.0.1:8080/socket.io/websocket
ProxyPass /socket.io/ http://127.0.0.1:8080/socket.io/
ProxyPassReverse /socket.io/ http://127.0.0.1:8080/socket.io/
RewriteCond %{REQUEST_URI} ^/socket.io [NC]
RewriteCond %{QUERY_STRING} transport=websocket [NC]
RewriteRule /(.*) ws://127.0.0.1:8080/$1 [P,L]
ProxyPass / http://127.0.0.1:8080/
ProxyPassReverse / http://127.0.0.1:8080/
IncludeOptional génère une erreur quand le fichier n’existe pas !
C’est un comportement de Apache < 2.4.30 (Jessie, Stretch). La directive ne va pas générer d’erreur de syntaxe (contrairement à Include) si et seulement si, des jokers (comme *) sont utilisés.
A partir d’Apache 2.4.30 (Buster), IncludeOptional ne va pas émettre d’erreur de syntaxe dans ce cas particulier.
C’est expliqué dans la documentation d’apache
Un contournement si on souhaite vraiment inclure qu’un seul fichier qui peut ne pas exister :
IncludeOptional /etc/apache2/ssl/XXX.con[f]
J’ai activé HTTP/2, mais ça ne fonctionne pas :(
Malheureusement, Apache en mode prefork (ou itk qui est un dérivé du prefork) n’est pas adapté au fonctionnement du protocole HTTP/2. On peut notamment voir dans les journaux le message suivant :
# tail /var/log/apache2/error.log
Thu Oct 02 10:42:00.052627 2019] [http2:warn] [pid 9682] AH10034: The mpm module (prefork.c) is not supported by mod_http2. The mpm determines how things are processed in your server. HTTP/2 has more demands in this regard and the currently selected mpm will just not do. This is an advisory warning. Your server will continue to work, but the HTTP/2 protocol will be inactive.
Dans tous les cas, PHP n’étant pas compatible avec Apache fonctionnant en mode thread, il est nécessaire d’être en mpm prefork ou itk. Donc pas de HTTP/2 possible.
Cependant il est possible de mettre HaProxy en amont HowtoHaproxy
Too many open files
Si vous avez beaucoup de VirtualHosts, vous pouvez avoir des erreurs
Too many open files
ou même une erreur bizarre si vous
utilisez du PHP :
Fatal error: date(): Timezone database is corrupt - this should *never* happen!
Il faut alors augmenter la limite max open files d’Apache qui est par défaut à 8192.
Cela se fait via le fichier /etc/apache2/envvars
:
APACHE_ULIMIT_MAX_FILES='ulimit -n 65536'
CORS Allow-Origin
Header set Access-Control-Allow-Origin "*"
NFS et Apache-ITK
Pout utiliser des montages NFS avec Apache-ITK (que nous utilisons par défaut), il faut désactiver l’option suivante :
EnableCapabilities off
Apache ajoute l’entête HTTP “Vary” inopinément
Lorsque vous évaluez une variable HTTP_USER_AGENT, certaines versions
d’Apache (notamment celle de Debian 9) peuvent avoir la mauvaise idée
d’ajouter un entête HTTP Vary: User-Agent
.
Cela peut être une évaluation dans votre configuration Apache, votre VirtualHost ou même dans vos .htaccess
Exemple de configuration qui génère le comportement problématique :
Require expr %{HTTP_USER_AGENT} !~ /Nutch/
Cela pourrait s’appliquer aussi aux “Rewrite Rule” et à d’autres variables comme HTTP_HOST, etc.
Le bug semble corrigé dans les versions récentes d’Apache : https://bz.apache.org/bugzilla/show_bug.cgi?id=58231
La documentation Apache mentionne une fonction
req_navary()
qui permettrait d’empêcher ce comportement :
https://httpd.apache.org/docs/2.4/expr.html#functions
Lien qui nous a mis sur la piste : https://www.nivas.hr/blog/2017/02/13/apache-sending-vary-host-making-things-uncacheable-varnish/
Gestion des droits avec Apache-ITK et PHP-FPM
Si l’on utilise PHP-FPM avec
Apache-ITK, on veillera à avoir un pool FPM qui utilise les mêmes
utilisateur/groupe qu’Apache-ITK. En particulier si on définit
AssignUserID foo bar
il faut s’assurer que
user = foo
et group = bar
dans le
configuration du pool PHP-FPM.
Apache plante au 1024e restart
En Debian 9, avec un Apache utilisant mod_php et imagemagick, quand
Apache est rechargé la 1024e fois apache plante en idiquant dans les
logs libgomp: could not create thread pool destructor.
.
Pour plus d’info lire cet article.
Code 413 Request Entity Too Large
Si #modsecurity
bloque la requête, ajuster la
variableSecRequestBodyLimit
.
Modifier le /tmp de Apache ( TMPDIR )
Apache n’honore pas ou mal la variable d’environnement TMPDIR, par
conséquent, si on a une petite partition pour /tmp
, on peut
se retrouver avec une partition pleine, à cause d’upload notamment.
La solution est de changer le montage /tmp
dans le
namespace mnt utilisé par apache ( via systemd ) :
$ mkdir /home/apache-tmp
$ systemctl --edit apache2
# rajouter la configuration suivante :
[Service]
PrivateTmp=false
BindPaths=/home/apache-tmp:/tmp
Il faut ensuite re-démarrer apache2.
Tips : on peut utiliser
lsns | grep apache
pour trouver le namespace mnt ( et le pid ) d’apache, puis entrer dedans avecnsenter -t <pid> -a
et tester avec untouch /tmp/toto