33. Serveur Web

Le service HTTP est la base de tous les services à l’heure actuelle grâce à la modularité et à la flexibilité du protocole. On retrouve ainsi de l’HTTP dans les messageries instantanées, l’appel de procédure à distance, la gestion de version collaborative etc.

33.1. Configuration d'Apache

33.1.1. Généralités

Une fonctionnalité intéressante d’Apache2 est de pouvoir diviser sa configuration dans de multiples répertoires et fichiers. Apache 1.3 reste plus modeste en ne proposant par défaut qu’un fichier : /etc/apache/httpd.conf

La syntaxe du fichier est verbeuse et se rapproche de l’Anglais.

Ce fichier global, par son importance, ne pourra être modifié que par un administrateur consciencieux qui aura vérifié deux fois ses modifications avant de relancer Apache. Cela signifie qu’une seule personne (ou une équipe) peut modifier les paramètres de configuration.

Imaginons que chaque service d'un entreprise souhaite un espace sur le serveur pour la publication de leurs mémos. Soit l'on se charge de tout gérer pour eux, soit on leur délégue la tâche en leur créant leur propre espace de travail. La configuration de leur répertoire ou espace se fera via des fichiers .htaccess, ces derniers permettent de configurer plus finement les accès, options, redirections ou messages d’erreur par exemple.

33.1.2. Configuration

33.1.2.1. Syntaxe

La syntaxe d’Apache est souple et peu stricte : les directives de configuration sont seulement séparées par des sauts de ligne et il n’y a généralement pas à se soucier des guillements simples ou doubles, options obligatoires, etc.

Ceci est vrai pour tout sauf pour la configuration des règles de réécriture (rewriting rules de mod_rewrite).

33.1.2.2. Portée lexicale

Par défaut, les instructions ont une portée globale : elles s’appliquent à la totalité des sites virtuels, comptes ou répertoires. Il est possible d’avoir une meilleure granularité en créant des scopes qui utilisent une syntaxe similaire au XML/HTML :

DocumentRoot /srv/testme.fr/www/htdocs/

<Directory /srv/testme.fr/www/cgi-bin/>
	AllowOverride None
	Options ExecCGI
</Directory>

Les sélecteurs peuvent concerner les sites virtuels (virtual hosts), les répertoires, un pointeur d’adresse (la partie locale d’une URL) ou un fichier.

33.1.3. Configuration minimale

Apache requiert peu d’informations pour obtenir une configuration minimaliste : le nom complet du serveur et la racine de publication de tous les fichiers sont les pré-requis obligatoires (en plus des lignes de chargement de modules).

En général et pour prendre des bonnes habitudes avant les domaines virtuels, on créera une hiérarchie de répertoires de cette façon :

/srv/
	‘-- testme.fr
		‘-- www
			|-- cgi-bin - Contient les scripts à être executer
			|-- htdocs - Contient tous les fichiers statiques (html)
			‘-- logs - Contiendra les logs

Apache doit savoir où se trouve la racine du site, dans l’URL http://www.testme.fr/index.html, il ira chercher le fichier $racine/index.html

$racine est défini comme cela :

DocumentRoot /srv/testme.fr/www/htdocs/

Le protcole veut qu’à chaque requête HTTP, le serveur précise son nom complet, Apache renverra alors le nom donné via :

ServerName www.testme.fr

Apache peut journaliser toutes les requêtes qu’il a traitées pour vous permettre de faire des statistiques, du debugging ou simplement assouvir votre curiosité sur le parcours des visiteurs du site. Un format de log (journal) standard existe entre les différents serveurs HTTP existants et il se précise ainsi dans la configuration :

# Logguer les requêtes au format combined dans /srv/../access.log
ErrorLog	/srv/www.testme.fr/www/logs/error.log
CustomLog	/srv/www.testme.fr/www/logs/access.log	combined

Attention aux permissions ici puisqu’Apache ouvre les fichiers de configuration en tant que super-utilisateur (root) il faut donc s’assurer que le répertoire qui contient les logs n’est pas en écriture pour les utilisateurs (sinon, ils pourraient faire des liens symboliques de access.log vers des fichiers comme /etc/passwd).

33.2. Contenu dynamique

33.2.1. Common Gateway Interface (CGI)

Les CGI permettent d’exécuter des programmes classiques au sens système : lors de l’appel de la page, si Apache se rend compte que le fichier désiré a les permissions exécutables et que la configuration le permet, alors le programme va être exécuté et sa sortie standard sera le résultat pour le client.

<Directory /srv/testme.fr/www/cgi-bin/>
	AllowOverride None
	Options ExecCGI
</Directory>

Et nous créons le fichier suivant :

$ cat /srv/testme.fr/www/cgi-bin/hello.sh
#!/bin/sh
echo Content-Type: text/html
echo
cat <<EOF
<html>
	<head>
		<title>Hello world!</title>
	</head>
	<body>
		<p>
			Hello World, ceci est mon premier CGI
		</p>
	</body>
</html>
EOF
$ chmod +x /srv/testme.fr/www/cgi-bin/hello.sh

Ici, peu importe que ce soit un script écrit en shell ou un programme écrit en C puisque c’est le système (le noyau) qui va faire le travail d’exécution, en contrepartie, cela signifie que c’est à vous de tout gérer : les données envoyées par le clientWeb, les codes d’erreur à retourner, etc.

Les CGI sont pratiques car ils fournissent un moyen rapide de générer des pages dynamiquement. Malheureusement, cette simplicité se traduit par des performances médiocres car à chaque hit (un hit est le fait d’accéder à une page d’un site), un processus va être créé (via fork()), le programme va être chargé, si c’est un script, il va falloir charger en mémoire l’interpréteuret tous les modules nécessaires, l’exécuter et servir la page.

On a donc commencé à réfléchir à mettre l’exécution du code à l’intérieur d’Apache même et c’est ainsi que PHP a conquis tous les créateurs de sites Web.

33.2.2. PHP

Le succès du PHP est dû à plusieurs qualités :

  • une syntaxe classique, proche du C,

  • assez laxiste sur beaucoup d’erreurs de débutants,

  • un langage assez pauvre (dans les premières versions),

  • des performances correctes,

  • une installation très facile sur les serveurs Apache.

Plutôt que de faire comme pour les CGI, PHP est couramment utilisé en tant que module Apache, c’est-à-dire que le code est executé par le serveur lui-même, ne nécessitant alors pas de créer des processus à chaque fois. De plus, ce mode de fonctionnement permet de proposer des fonctionnalités bas-niveau directement liées à Apache.

Étant donné que c’est le module qui exécute le code, les fichiers PHP ne sont pas obligatoirement exécutables par le système d’exploitation ce qui est un avantage indéniable dans certains cas mais peut poser quelques soucis (par exemple lorsque n’importe qui peut créer des fichiers dans un répertoire du site, comme les zones d’uploads ou les répertoires temporaires.

L’installation de PHP demande donc l’installation d’un module écrit en C, qui sera chargé par Apache.

Sous Debian, il suffit d’installer le paquet libapache-mod-php4 pour récupérer ce qu’il faut.

Maintenant, il ne reste plus qu’à mettre les directives suivantes dans le fichier httpd.conf :

# On charge le module dans Apache
LoadModule php4_module /usr/lib/apache2/modules/libphp4.so
# Désormais, les fichiers possédant l’extension .php seront considérés comme des scripts PHP
AddType application/x-httpd-php .php

Si l'on mett ces lignes dans la configuration globale, tous les fichiers possédant l’extension adéquate seront interprétés par le module : contrairement aux CGI, il n’existe pas un répertoire unique pour placer ces fichiers.

Par défaut, le code PHP est réalisé sous les privilèges de l’utilisateur faisant tourner Apache ; Sous Debian, c’est www-data. Si l'on fait tourner des sites virtuels (comme le font les hébergeurs), tous les scripts PHP utiliseront le même utilisateur. Il faut donc être très attentif aux permissions de cet utilisateur (fichiers de configuration des applications avec des mots de passe à l’intérieur, etc.).

Au fur et à mesure des versions de PHP, l’interpreteur s’est vu gonflé d’options pour améliorer la sécurité de l’interpreteur lui-même mais aussi afin de brider le langage pour éviter les pièges :

  • magic_quote,

  • magic variable,

  • safe_mode,

  • limitation du temps d’execution,

  • consommation de mémoire,

  • etc.

Si cela est possible, il faut utiliser impérativement le safe_mode !

33.2.3. mod_perl, mod_python

Si l'on préfère utiliser d’autres langages de programmation, il existe des modules pour chacun d’entre eux. Ces derniers sont souvent plus aboutis et offrent des fonctionnalités inédites en PHP (permanence du code, connexion permanente à des bases de données, espace mémoire partagé entre les différents serveurs Apache, etc.

L’installation et la configuration de ces modules sont moins aisées que pour PHP du fait des plus grandes possibilités. De plus, il sera souvent nécessaire d’adapter le code de votre application pour le lier au serveur Apache (utilisation de bibliothèques par exemple).

33.2.4. Permissions

33.2.4.1. Exécution de code

Toutes les applications sont lançées sous le même utilisateur. Une solution tierce existe pour résoudre ce souci en utilisant un module supplémentaire, mod_suphp.

En résumé, mod_suphp exécute le code (PHP uniquement) sous l’identité du propriétaire du fichier. Dans le cadre d’un service d’hébergement, on aura alors à créer un utilisateur par domaine pour que chaque site soit cloisonné des autres. Mais cela ne protègera pas le site si une faille de sécurité est trouvée dans l’une de ses applications PHP.

LoadModule suphp_module /usr/lib/apache2/modules/mod_suphp.so

<IfModule mod_suphp.c>
	AddHandler x-httpd-php .php .php3 .php4 .phtml
	suPHP_Engine on
	suPHP_ConfigPath /etc/php4/cgi/php.ini
</IfModule>

Même si l'on n'est pas hébergeur, c’est une bonne pratique de créer un utilisateur par application afin de limiter les risques.

33.2.4.2. Permissions de fichier

En sécurité, c’est toujours le même principe : limiter au maximum les privilèges. En conséquence, on devrait enlever les droits d’écriture sur tous les répertoires et fichiers disponibles sur le site.

Il faut également veiller à n'autoriser la lecture des fichiers de configuration (penser aussi aux fichiers htaccess ou htpasswd) uniquement à l'utilisateur d'Apache.

En plus de limiter au mieux les permissions des fichiers, si l'on délégue l’administration de sites virtuels, on a à restreindre les possibilités de configuration d’Apache. La directive AllowOverride permet de limiter les options disponibles dans les .htaccess, par exemple pour le site d'une entreprise, il est recommandé de désactiver le listage des répertoires :

<Directory /srv/www.testme.fr/www/htdoc/>
	AllowOverride -Indexes
</Directory>

Cette mesure permet de limiter l’accès à des documents qui ont été oubliés dans un coin du site Web mais qui ne sont liés nulle part. Lorsqu’un attaquant cible votre site, il va regarder chaque répertoire pour essayer de trouver des fichiers oubliés.

La configuration ci-dessus l’en empêchera même si la solution est de réellement supprimer ces fichiers. Ceci n’est pas réellement une sécurité, si vous avez des besoins de confidentialité plus importants, l’authentification des utilisateurs sera la solution adéquate.

33.2.4.3. Authentification

Grâce aux modules Apache, on pourra limiter l’accès à des zones de votre site depuis une base SQL, LDAP ou des fichiers plats.

Cette fonctionnalité est offerte par le protocole HTTP de base, c’est donc une solution fiable, portable et accessible depuis n’importe quel clientWeb sans nécessiter l’installation d’applications.

Par exemple :

<Directory /srv/www.testme.fr/www/htdoc/stats/>
	AuthUserFile /srv/www.testme.fr/etc/passswd.stats
	AuthGroupFile /dev/null
	AuthName "Accès aux statistiques"
	AuthType Basic
	
	require valid-user
</Directory>

Ici nous limitons l’accès aux statistiques aux seules personnes qui se sont identifiées correctement en suivant la méthode d’authentification basique (qui transfère les mots de passe en clair). Le fichier de mot de passe doit être précisé avec son path complet et absolu. Il est créé à l’aide de la commande htpasswd :

[adminbdd html]$ htpasswd -c passwd.stats agilan
New password:
Re-type new password:
Adding password for user agilan
[adminbdd html]$ cat passwd.stats
agilan:nJYbGghxK.ExI

Ici, l’option -c est utilisé pour créer le fichier avec un nom d’utilisateur en paramètre.

33.3. Fonctionnalités avancées

33.3.1. Chiffrement des communications

L’utilisation de la cryptographie va nous permettre de chiffrer toutes les communications transitant par votre serveur. En plus d’assurer cette confidentialité, les clients vont alors pouvoir identifier le serveur et être sûrs qu’ils parlent bien au serveur de mise à jour par exemple plutôt qu’à un ordinateur dirigé par un pirate.

Le Secure Socket Layer (SSL) est la solution qui a été retenue après avoir été développée par Netscape. Il existe trois versions du protocoles mais il est fortement recommandé d’utiliser SSL 2.x ou SSL 3.x si possible.

Un certificat peut-être généré par une authorité compétence (qui nous l’aura facturé très cher), ou par soi-même.

Toutes les données, aussi bien la requête (et donc les variables de transaction) que le contenu de la page sont chiffrées. Cela signifie qu’un certificat est associé à une adresse IP (puisque dans le cadre de sites virtuels, le serveur ne pourra pas savoir quel certificat utiliser).

Si l'on utilise Apache 1.3, il est nécessaire d’installer mod_ssl (paquet Debian libapache-mod-ssl) alors que le module est dans la distribution de base d’Apache 2.x. La configuration est alors très simple et se résume à :

LoadModule ssl_module /usr/lib/apache2/modules/mod_ssl.so

# Écouter sur le port HTTPS (443)
# en plus du port HTTP classique (80)
Listen 443
Listen 80

<IfModule mod_ssl.c>
	SSLEngine on
	SSLCertificateFile /srv/pki/httpd/httpd.cert
	SSLCertificateKeyFile /srv/pki/httpd/httpd.key
</IfModule>

Après redémarrage d’Apache, le serveur acceptera alors les requêtes émises sur le port HTTPS (443).

Juqu’ici, nous ne gérions qu’un seul site, nous partions de l’hypothèse que toutes les requêtes que nous recevions étaient à destination de www.testme.fr. Voyons maintenant comment mettre en place des sites virtuels.

33.3.2. Domaines virtuels

Voici un exemple de requête HTTP minimale émise par un navigateur :

[adminbdd html]$ telnet www.google.fr 80
Trying 74.125.230.215...
Connected to www.google.fr.
Escape character is '^]'.
GET /index.html HTTP/1.1

HTTP/1.1 200 OK
Via: 1.1 LILSISAFWPX
Connection: Keep-Alive
Proxy-Connection: Keep-Alive
Transfer-Encoding: chunked
Expires: -1
Date: Thu, 07 Jun 2012 15:55:34 GMT
Content-Type: text/html; charset=ISO-8859-1
Server: gws
Cache-Control: private, max-age=0
X-XSS-Protection: 1; mode=block
X-Frame-Options: SAMEORIGIN

1000
<!doctype html><html itemscope itemtype="http://schema.org/WebPage"><head><meta http-equiv="cont ...

Le champ "host" du protocol HTTP précise à quel site nous souhaitons accéder et permet d’avoir plusieurs sites indépendants sur une même adresse IP. C’est pour cette raison que nous parlons de sites virtuels. Dans le jargon d’Apache, ce sont des Virtual Hosts, il en existe deux types : les name based et IP based qui correspondent à la manière de différencier deux sites, l’un utilisera des noms (avec le champ Host:) comme nous l’avons vu précédemment alors que les IP based différencient les sites par des adresses IP différentes.

33.3.2.1. Name based

Chaque virtual host dispose de sa propre configuration indépendante, ce sont les directives VirtualHost qui nous fournissent ce moyen pratique :

# Nous indiquons que nous voulons faire des virtual hosts de type name based sur toutes nos adresses IP
NameVirtualHost *:80

<VirtualHost *:80>
	ServerName www.testme.fr
	ServerAlias testme.fr *.testme.fr
	DocumentRoot /srv/www.testme.fr/www/htdocs/
	ErrorLog /srv/www.testme.fr/www/logs/error.log
	TransferLog /srv/www.testme.fr/www/logs/access.log
</VirtualHost>

Ce bloc fait appel à un virtual host de type name based puisqu’il indique qu’on peut y accéder depuis n’importe quelle adresse IP tant que c’est sur le port 80. Le nom principal du serveur est www.testme.fr mais il possède également les alias pour répondre en tant que foobar.testme.fr si un client y accède ainsi.

Pour déclarer un autre site virtuel, c’est la même chose :

<VirtualHost *:80>
	ServerName www.breakme.com
	DocumentRoot /srv/www.breakme.com/www/htdocs/
</VirtualHost>

Ce site virtuel ne sera accessible que sous le nom www.breakme.com et aucun autre. Lorsqu’Apache ne trouve pas de site virtuel associé à un nom (comme foobar.breakme.com), il se replie sur le site par défaut qui est le premier virtual host configuré. Dans notre exemple, ce sera donc le virtual host testme.fr qui récupérera la requête.

33.3.2.2. IP based

Une solution possible est de lancer plusieurs instances d’Apache en précisant quelles interfaces écouter (avec Listen 1.2.3.4:80). C’est ce que conseille les développeurs d’Apache s’il y a des besoins forts de sécurité car cela permet de faire tourner Apache sous un utilisateur différent des autres sites. Deux instances signifient également deux fois plus de maintenance car il y a maintenant plusieurs fichiers de configuration indépendants, etc.

L’autre solution est d’utiliser le mécanisme interne d’Apache en précisant pour chaque virtual host l’adresse IP visée, qui ne devra pas avoir été donnée à un NameVirtualHost bien entendu :

<VirtualHost 4.5.6.7>
	DocumentRoot /srv/www.testyourself.com/www/htdocs/
	ServerName www.testyourself.com
	ErrorLog /srv/www.testyourself.com/www/logs/error.log
	TransferLog /srv/www.testyourself.com/www/logs/access.log
</VirtualHost>

<VirtualHost 1.2.3.4>
	ServerName www.foobar.org
	DocumentRoot /srv/www.foobar.org/www/htdocs
	ErrorLog /srv/www.foobar.org/www/logs/error.log
	TransferLog /srv/www.foobar.org/www/logs/access.log
</VirtualHost>

Avec www.foobar.com résolvant en 1.2.3.4 et www.testyourself.com en 4.5.6.7. Dans la configuration, il est conseillé de mettre les adresses IP en dur (pour VirtualHost) plutôt que le nom DNS car sinon en cas de problème de résolution DNS, Apache ne sera plus capable de savoir où aiguiller les requêtes.

33.3.3. Règles de réécriture

mod_rewrite est un module qui peut servir de couteau suisse : une fois maîtrisé, vous pouvez vous en servir dans toutes les situations (maintenance, réparations rapides, migration, redirection de trafic, etc.).

mod_rewrite permet de réécrire des URL à l’aide des expressions rationnelles. L’exemple le plus concrêt est la migration vers un nouveau serveur : toutes les requêtes arrivant sur l’ancien serveur seront redirigées vers le nouveau serveur en place.

# on active la réécriture des adresses (nécessaire une fois uniquement)
RewriteEngine on

RewriteRule ^/(.+) http://newserver/$1 [R,L]

Le premier élément de RewriteRule est une expression rationnelle correspondant à ce que l’on veut matcher (c’est notre condition), si elle est vérifiée alors on réécrit l’adresse avec le deuxième paramètre. Les caractères entre crochets sont des options de redirection (R signifie redirection vers une URL complète et L indique que c’est la dernière règle).

L’écriture de ce type de règles demande un apprentissage des expressions rationnelles alors expliquons en détail la règle ˆ/(.+) :

  • ^ : Début de ligne.

  • (.+) : .+ signifie n’importe quel caractère au moins n fois (où n > 0). Les parenthèses permettent de sauvegarder ce qui a été matché, ces données sont accessibles par $1, $2, $3. . .qui correspondent respectivement à la première, deuxième et troisième paire de parenthèses.

En résumé, la règle est vraie si l’adresse demandée commence par / suivi d’au moins un caractère. Ces caractères sont sauvegardés dans la variable $1.

Ensuite, on redirige vers http://newserver/$1 avec $1 qui sera remplacé comme prévu.

mod_rewrite peut aller bien plus loin en utilisant les variables internes d’Apache, des tables de hashage, plusieurs conditions par règle ou des redirections en fonction du navigateur, etc.