TP5 : Configuration du service web public
Introduction
Après la mise en place des domaines DNS, nous allons à présent mettre en place un service web public, composé dans un premier temps d’un serveur web, puis d’une base de données. Nous les sécuriserons entre autres via des certificats TLS lors d’un prochain labo.
A la fin de ce TP, vous devez être capable de :
- configurer un ou plusieurs sites web sur un serveur (rappel du premier semestre),
- mettre en place une base de données pour un site web dynamique
Lectures préalables
Les éléments théoriques liés à ce TP sont présentés dans le chapitre 6 : Gestion et sécurisation d’un service web du support de cours.
Pré-requis pour la réalisation du TP
Avant de pouvoir réaliser ce TP, assurez-vous d’avoir :
- sécurisé votre VPS, notamment par la mise en place de l’authentification par clé SSH
- mis en place votre zone DNS, notamment via la délégation.
Mise en place de l’environnement de travail
La mise en place du service web peut s’effectuer soit sur le même VPS que le DNS, soit sur un autre VPS de votre groupe. Dans tous les cas, assurez-vous de toujours travailler au départ d’un repository Git sur lequel l’ensemble de vos configurations sont disponibles et à jour à tout moment.
Pour ce TP, nous conseillons de travailler dans un répertoire dédié dans le home directory de votre utilisateur non privilégié (ex : répertoire /home/<user>/web
)
N’oubliez pas non plus de consigner les étapes de réalisation et vos notes personnelles sur le wiki de votre repository. Gardez en tête que vous devrez ultérieurement documenter le troubleshooting d’un bug rencontré. Ayez donc bien le réflexe de noter tout incident, bug ou difficulté rencontré, ses symptômes et les solutions trouvées ou non.
1. Configuration de base d’un serveur web
1.1. Site web statique
Vous avez déjà mis en place des sites web statiques au premier semestre dans les TPs GNS3, ainsi que dans le cadre de ce cours lors de la découverte des containers Docker. Cette première étape ne devrait donc pas vous poser de problème.
Pour étoffer vos compétences, nous vous proposons cette fois de travailler sur base d’un serveur nginx
et non plus apache
. Vous verrez que les principes de configuration sont très similaires. Vous pouvez lire une introduction à ces principes par exemple dans cet article du fournisseur Digital Ocean.
- Lancer un container
nginx
sur un de vos VPS, puis, si ce n’est pas encore fait, définissez un record A dans votre zone DNS pour faire pointer le nonwww
vers l’IP du VPS servant de serveur web. Vérifiez que vous avez bien accès au site web sur base du FQDNwww.lx-y.ephec-ti.be.
.- Créez un dockerfile dans lequel vous personnaliserez la configuration de votre serveur web (
/etc/nginx/nginx.conf
) :
- en indiquant que les fichiers html sont dans un volume mappé sur /var/www/html/ (définissez un sous-répertoire pour le virtualhost www)
- en définissant une page d’accueil autre que celle par défaut, mentionnant notamment votre numéro de groupe
- Vérifiez que votre site web est accessible et conforme à vos modifications.
Voici un exemple de fichier de configuration nginx
simple pour notre premier site web :
events {
}
http {
server {
listen 80;
server_name www.lx-y.ephec-ti.be;
index index.html;
root /var/www/html/www/;
}
}
1.2. Virtual Hosting
Pour définir un second site web, deux options sont possibles :
- Utiliser deux containers, un pour chaque site. Dans ce cas, il faudra un reverse proxy pour orienter les requêtes vers l’un ou l’autre container, en fonction du champ Host de la requête.
- N’utiliser qu’un seul container, avec le principe du Virtual Hosting déjà vu au premier semestre. Dans le cadre de ce TP, nous resterons sur la seconde option, déjà connue. Nous explorerons le concept de reverse proxy ultérieurement.
La configuration du Virtual Hosting dans nginx
est similaire à ce qui a déjà été vu pour apache
: il suffit de définir un second contexte server
, dans lequel la directive server_name
permettra d’indiquer le champ host
à prendre en compte pour identifier le virtual host.
- Configurez un second Virtual Host sur votre serveur
nginx
(appelez-leblog
), en indiquant dans la configuration le répertoire où se trouvent les fichiers html de ce virtual host (que vous aurez créé au préalable).- Mettez votre zone DNS à jour pour que le nom DNS du Virtual Host pointe vers la bonne IP. Le Record CNAME est adéquat dans ce cas de figure.
- Vérifiez que les deux sites sont accessibles depuis Internet.
1.3. Logging
La collecte des logs est un élément de configuration essentiel : les logs permettent de suivre le bon déroulement des opérations d’un service, et, en cas de problème, de retracer l’historique des événements. Il est donc essentiel de soigner leur configuration !
Dans le cas des serveurs Linux classiques, les logs sont généralement placés dans le répertoire /var/log/
. Néanmoins, dans le cas de containers Docker, les choses se passent un peu différemment : pour que les logs puissent être centralisés par Docker, ils doivent être écrits sur les flux stdout
et stderr
du container. Les images “officielles” sont en générale pré-configurées pour fonctionner de cette manière. Vous l’avez normalement déjà observé lors de la configuration du DNS.
Dans le cas de nginx
, on peut le vérifier facilement :
Ouvrez un shell sur votre serveur web, puis faites un
ls
sur le répertoire/var/log/nginx
Vous devriez constater que les fichiers de logs traditionnels access.log
(qui répertorie les requêtes reçues) et error.log
(dans lequel se retrouvent les messages d’erreurs) sont bien redirigés vers stdout
et stderr
.
Si vous observez un peu les logs de votre serveur web, vous constaterez que les requêtes sont effectivement loggées, mais que le message ne permet pas d’identifier le Virtual Host concerné par la requête. Dans le cas d’une utilisation non dockerisée, la solution consiste à définir un fichier de log séparé pour chaque virtual host. Néanmoins, dans notre cas, nous allons garder tous les logs sur stdout, et rajouter une information permettant d’identifier le Virtual Host, afin qu’un parser de logs puisse ultérieurement faire le tri.
Pour définir un nouveau format de log, on utilise la directive log_format
dans le contexte global. Le nouveau format sera ensuite utilisé lors de la configuration des logs d’accès.
log_format log_per_virtualhost '[$host] $remote_addr [$time_local] $status '
'"$request" $body_bytes_sent';
Dans l’exemple ci-dessus, la variable $host
permet d’identifier le Virtual Host auquel la requête était destinée. Les autres champs permettent d’afficher l’IP du client, le timestamp de la requête, le statut HTTP de la réponse, l’en-tête de la requête, et le nombre d’octets envoyés. D’autres champs peuvent être ajoutés, permettant par exemple d’identifier le logiciel (user agent) ayant généré la requête, le taux de compression utilisé, des informations sur les redirections, etc.
Pour utiliser ce format de log personnalisé pour logger nos requêtes, il suffit de l’indiquer dans la directive access_log
:
access_log /dev/stdout log_per_virtualhost;
Modifiez le format des logs de votre serveur, puis vérifiez via la commande
docker logs -f web
que le champ host est bien indiqué dans le log des requêtes.
2. Site web dynamique
Jusqu’ici, les sites web que nous avons configurés étaient statiques : nos serveurs se contentaient de renvoyer des fichiers présents sur le système de fichiers en réponses aux requêtes reçues.
Néanmoins, les sites web sont souvent dynamiques : leur contenu est généré automatiquement en fonction de la requête reçue et du contexte correspondant (langue du client, utilisateur connecté, …).
Les sites web dynamiques fonctionnent d’une part grâce à une base de données qui permet le stockage d’information, et d’autre part, grâce à un programme générant les pages web. Ce programme peut tourner dans un processus distinct du serveur web ou non, selon les cas. C’est ce programme qui se chargera de se connecter et d’interagir avec le SGBD.
Nous allons ici mettre en place un site web dynamique basé sur PHP et MariaDB, mais il existe de nombreuses autres configurations possibles. Ne vous attardez donc pas sur les détails spécifiques à PHP ou à MariaDB, mais essayez de comprendre la logique globale de la configuration des différents éléments.
La mise en oeuvre d’un contenu pertinent pour l’application web sort du cadre de ce cours. Nous nous contenterons donc d’un petit script permettant de récupérer quelques informations de test dans une base de données. En l’occurrence, pour rester dans le cadre de notre use case, nous allons afficher une liste des produits offerts par l’entreprise WoodyToys sur une page spécifique de notre virtual host www
.
2.1. Installation de la DB
2.1.1. Premier test de MariaDB
Pour commencer, nous allons simplement lancer un container MariaDB, puis nous y connecter depuis notre VPS, via le client MariaDB.
- Démarrez un container mariaDB en tapant la commnade ci-dessous. Notez l’option
-e
qui permet de définir des variables d’environnement, en l’occurrence ici le mot de passe de l’utilisateurroot
de la DB.docker run --name mariadbtest -e MYSQL_ROOT_PASSWORD=mypass --rm -d mariadb
- Installez le client MariaDB sur votre VPS :
sudo apt install mariadb-client
- Récupérez l’adresse IP du container MariaDB, puis connectez-vous y depuis votre VPS. Le logiciel client va établir une connexion TCP au serveur MariaDB en utilisant le port
3306
(port par défaut de MySQL/MariaDB) :mysql -h <IP du container mariaDB> -u root -p
- Vous êtes à présent connecté à votre DB via l’utilisateur
root
. Vous pouvez le constater par exemple grâce à la commandeshow databases;
2.1.2. Ajouter du contenu à la base de données
Notre prototype d’application web doit afficher les produits du catalogue de WoodyToys. Il faut donc que ces produits soient enregistrés dans la base de données. Dans le cadre de nos tests, nous nous contenterons de définir un petit script qui créera une table Products
et la remplira avec quelques items.
- Connectez-vous à nouveau à votre serveur MySQL depuis votre VPS, avec la commande donnée un peu plus haut.
- Depuis la ligne de commande MariaDB, créez la base de données pour le catalogue WoodyToys :
CREATE DATABASE woodytoys;
- Retournez sur votre VPS, et créez un fichier
woodytoys.sql
. Dans ce dernier, nous allons mettre les commandes permettant l’ajout de produits dans le catalogue WoodyToys. Copiez-y les commandes SQL ci-dessous :USE woodytoys; CREATE TABLE products (id mediumint(8) unsigned NOT NULL auto_increment,product_name varchar(255) default NULL,product_price varchar(255) default NULL,PRIMARY KEY (id)) AUTO_INCREMENT=1; INSERT INTO products (product_name,product_price) VALUES ("Set de 100 cubes multicolores","50"),("Yoyo","10"),("Circuit de billes","75"),("Arc à flèches","20"),("Maison de poupées","150");
- Exécutez ensuite ces commandes SQL en envoyant le contenu du fichier sur le flux
stdin
de votre serveur MariaDB :mysql -h <IP du container MariaDB> -u root -pmypass < woodytoys.sql
- A nouveau depuis le client MariaDB, vérifiez que la base de données est à présent remplie :
MariaDB [(none)]> use woodytoys; [...] MariaDB [woodytoys]> show tables; [...] MariaDB [woodytoys]> select * from products [...]
Cela suffira pour le moment, mais notez cependant que nous avons adopté une configuration par défaut, non sécurisée : nous n’avons pas réfléchi encore aux utilisateurs de la DB et à leurs droits d’accès, ni à la manière dont les différents fichiers seront gérés / sauvegardés.
Ceci n’est donc pas une configuration sécurisée, apte à être utilisée en production !
2.2. Premier script PHP
Nous allons à présent reprendre notre serveur nginx
, et lui ajouter la possibilité d’exécuter un script PHP permettant la récupération et l’affichage des données de notre catalogue.
Contrairement à Apache avec son module mod_php
, nginx
n’est pas capable d’exécuter directement du PHP dans son processus propre. Il va donc avoir besoin d’un service PHP tournant dans un processus séparé, avec lequel il communiquera via un protocole appelé FastCGI
. Lors de la réception d’une requête nécessitant l’exécution d’un script PHP, nginx
se contentera donc de la passer au processus dédié, qui lui renverra la page dynamique générée. nginx
joue donc ici plutôt un rôle de Reverse Proxy, en se contentant de relayer la requête à un service dédié. Nous utiliserons PHP-FPM
(PHP FastCGI Process Manager) pour cela.
Puisque nous avons besoin de deux processus/services différents, nous allons rester dans la “philosophie Docker” et utiliser deux containers différents qui communiqueront via le réseau. Il nous faut donc un nouveau container dans lequel tournera PHP-FPM
.
- Créez un nouveau sous-répertoire pour les fichiers du container
php
. Ajoutez-y un nouveau fichier Dockerfile avec le contenu ci-dessous :FROM php:8.3-fpm RUN docker-php-ext-install mysqli
- Générez l’image correspondant à ce Dockerfile.
- Dans votre répertoire contenant les fichiers HTML du virtualhost
www
, ajoutezproducts.php
contenant le code PHP suivant :<?php phpinfo(); ?>
. Ce code générera simplement une page reprenant la version de PHP utilisée. Ce répertoire sera partagé entre le containernginx
et le containerphp
, afin qu’ils aient tout deux accès au script php.- Lancez le container
php
, en lui donnant accès au répertoire avec les fichiers html via un bind mount :docker run --name php --rm -d --mount type=bind,source=\[votre répertoire de travail]/html/www,target=/var/www/html/www php
- Récupérez l’adresse IP de votre container
php
.- Modifiez le Virtual Host
www
dans la configurationnginx
comme indiqué ci-dessous. Notez la directivelocation
: elle précise ce que doit fairenginx
avec les fichiers terminant par l’extension.php
, à savoir le transmettre via le protocole FastCGI au containerphp-fpm
, qui écoute sur le port 9000. Adaptez l’adresse IP de la directivefastcgi_pass
pour que cela corresponde à votre situation.server { listen 80; server_name www.vvandens.ephec-ti.be; index index.html; root /var/www/html/www/; location ~* \.php$ { fastcgi_pass [IP du container PHP]:9000; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; } }
- Lancez votre container nginx :
docker run -p80:80 --name web --rm --mount type=bind,source=\[votre répertoire de travail]/html,target=/var/www/html/ -d web
Si tout va bien, en accédant à la page www.lx-y.ephec-ti.be/products.php
, vous devriez voir une page listant votre configuration PHP. Si cela ne fonctionne pas : utilisez docker logs
comme point de départ de votre troubleshooting.
Quelle procédure de validation permet de s’assurer que le site web est bien connecté à la base de données? Quel(s) scénarii de test devrait comporter cette procédure?
2.3. Connexion entre l’application web et la base de données
Notre base de données fonctionne, ainsi que notre premier script PHP. Néanmoins, ils ne communiquent pas encore entre eux! Nous allons donc étoffer le script PHP pour qu’il se connecte à la base de données et affiche le contenu de la table contenant les produits WoodyToys.
Utilisez le script ci-dessous en l’adaptant à votre situation, en ajustant notamment l’adresse IP de la base de données (et éventuellement le mot de passe).
<html> <style> table, th, td { padding: 10px; border: 1px solid black; border-collapse: collapse; } </style> <head> <title>Catalogue WoodyToys</title> </head> <body> <h1>Catalogue WoodyToys</h1> <?php $dbname = 'woodytoys'; $dbuser = 'root'; $dbpass = 'mypass'; $dbhost = '172.17.0.5'; $connect = mysqli_connect($dbhost, $dbuser, $dbpass) or die("Unable to connect to '$dbhost'"); mysqli_select_db($connect,$dbname) or die("Could not open the database '$dbname'"); $result = mysqli_query($connect,"SELECT id, product_name, product_price FROM products"); ?> <table> <tr> <th>Numéro de produit</th> <th>Descriptif</th> <th>Prix</th> </tr> <? while ($row = mysqli_fetch_array($result)) { printf("<tr><th>%s</th> <th>%s</th> <th>%s</th></tr>", $row[0], $row[1],$row[2]); } ?> </table> </body> </html>
2.4. Docker Compose
Nos trois containers fonctionnent comme prévu, mais doivent être démarrés séparément. De plus, les configurations des uns dépendent des adresses IP des autres, qui sont hard-codées dans leurs fichiers de configuration : ce n’est assurément pas pratique.
L’outil Docker-Compose est la solution à ce double problème : il permet de démarrer l’ensemble des containers de notre service web de manière collective, et fournit en plus une résolution de noms, ce qui permet d’utiliser les noms des containers plutôt que leurs adresses IP dans les fichiers de configuration.
- Ecrivez un fichier
docker-compose.yaml
permettant de lancer vos containers, et éditez les fichiers de configuration pour retirer la dépendance aux adresses IP.- Lancez votre infrastructure Docker-Compose, et vérifiez, en utilisant votre procédure de validation, que tout fonctionne comme attendu.
2.5. Bilan fonctionnel
Nous avons à présent un serveur web possédant les fonctionnalités suivantes :
- Une page d’accueil statique sur
www.lx-y.ephec-ti.be
, - Une page de catalogue dynamique sur
www.lx-y.ephec-ti.be/products.php
, - Un second site sur
blog.lx-y.ephec-ti.be
.
Nos fonctionnalités sont en place, mais rien n’a encore été prévu pour la sécurisation : ce sera l’objet du prochain TP. En attendant, gardez en tête que vos sites ne sont pas sûrs : pensez donc bien à garder une copie de tous vos fichiers de travail hors du VPS!