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.

  1. 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 non www vers l’IP du VPS servant de serveur web. Vérifiez que vous avez bien accès au site web sur base du FQDN www.lx-y.ephec-ti.be..
  2. 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
  3. 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 nginxest similaire à ce qui a déjà été vu pour apache : il suffit de définir un second contexte server, dans lequel la directive server_namepermettra d’indiquer le champ hostà prendre en compte pour identifier le virtual host.

  1. Configurez un second Virtual Host sur votre serveur nginx (appelez-le blog), 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).
  2. 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.
  3. 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 stdoutet 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_formatdans 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 $hostpermet 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.

  1. Démarrez un container mariaDB en tapant la commnade ci-dessous. Notez l’option -equi permet de définir des variables d’environnement, en l’occurrence ici le mot de passe de l’utilisateur rootde la DB.
    docker run --name mariadbtest -e MYSQL_ROOT_PASSWORD=mypass --rm -d mariadb
    
  2. Installez le client MariaDB sur votre VPS : sudo apt install mariadb-client
  3. 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
    
  4. Vous êtes à présent connecté à votre DB via l’utilisateur root. Vous pouvez le constater par exemple grâce à la commande show 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.

  1. Connectez-vous à nouveau à votre serveur MySQL depuis votre VPS, avec la commande donnée un peu plus haut.
  2. Depuis la ligne de commande MariaDB, créez la base de données pour le catalogue WoodyToys : CREATE DATABASE woodytoys;
  3. 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");
  1. Exécutez ensuite ces commandes SQL en envoyant le contenu du fichier sur le flux stdinde votre serveur MariaDB : mysql -h <IP du container MariaDB> -u root -pmypass < woodytoys.sql
  2. 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, nginxn’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, nginxse 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.

  1. 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
    
  2. Générez l’image correspondant à ce Dockerfile.
  3. Dans votre répertoire contenant les fichiers HTML du virtualhost www, ajoutez products.phpcontenant 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 container nginxet le container php, afin qu’ils aient tout deux accès au script php.
  4. 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
  5. Récupérez l’adresse IP de votre container php.
  6. Modifiez le Virtual Host wwwdans la configuration nginxcomme indiqué ci-dessous. Notez la directive location: elle précise ce que doit faire nginxavec les fichiers terminant par l’extension .php, à savoir le transmettre via le protocole FastCGI au container php-fpm, qui écoute sur le port 9000. Adaptez l’adresse IP de la directive fastcgi_passpour 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;
	}
}
  1. 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 logscomme 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.

  1. 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.
  2. 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!