Comment héberger plusieurs sites Web en toute sécurité avec Nginx et Php-fpm sur Ubuntu 14.04

introduction

Il est bien connu que la pile LEMP (Linux, nginx, MySQL, PHP) offre une rapidité et une fiabilité inégalées pour l’exploitation de sites PHP. D’autres avantages de cette pile populaire, tels que la sécurité et l’isolation, le sont moins.

Dans cet article, nous allons vous montrer les avantages en termes de sécurité et d’isolation de l’exécution de sites sur LEMP avec différents utilisateurs Linux. Cela sera fait en créant différents pools php-fpm pour chaque bloc de serveur nginx (site ou hôte virtuel).

Conditions préalables

Ce guide a été testé sur Ubuntu 14.04. L’installation et la configuration décrites seraient similaires sur d’autres systèmes d’exploitation ou versions du système d’exploitation, mais les commandes et l’emplacement des fichiers de configuration peuvent varier.

Cela suppose également que vous avez déjà configuré nginx et php-fpm. Sinon, veuillez suivre les étapes 1 et 3 de l’article https://www.digitalocean.com/community/tutorials/how-to-install-linux-nginx-mysql-php-lemp-stack-on-ubuntu-14. -04 [Comment installer des piles Linux, nginx, MySQL, PHP (LEMP) sur Ubuntu 14.04].

Toutes les commandes de ce didacticiel doivent être exécutées en tant qu’utilisateur non root. Si un accès root est requis pour la commande, il sera précédé de + sudo +. Si vous ne l’avez pas déjà configuré, suivez ce tutoriel: Initial Initial Server Setup with Ubuntu 14.04 .

Vous aurez également besoin d’un nom de domaine pleinement qualifié (fqdn) qui pointe vers le droplet à tester en plus du nom par défaut + localhost +. Si vous n’en avez pas, vous pouvez utiliser + site1.example.org +. Editez le fichier + / etc / hosts + avec votre éditeur favori comme ceci + sudo vim / etc / hosts + et ajoutez cette ligne (remplacez + site1.example.org + par votre fqdn si vous l’utilisez):

/ etc / hosts

...
127.0.0.1 site1.example.org
...

Raisons pour sécuriser LEMP en plus

Sous une configuration LEMP commune, il n’y a qu’un seul pool php-fpm qui exécute tous les scripts PHP pour tous les sites sous le même utilisateur. Cela pose deux problèmes majeurs:

  • Si une application Web sur un bloc de serveur nginx, c.-à-d. sous-domaine ou site séparé, est compromis, tous les sites de cette Droplet seront également affectés. L’attaquant est capable de lire les fichiers de configuration, y compris les détails de la base de données, des autres sites ou même de modifier leurs fichiers.

  • Si vous souhaitez donner à un utilisateur un accès à un site de votre Droplet, vous lui donnerez pratiquement accès à tous les sites. Par exemple, votre développeur doit travailler sur l’environnement de transfert. Cependant, même avec des autorisations de fichiers très strictes, vous lui donnerez toujours l’accès à tous les sites, y compris votre site principal, sur le même Droplet.

Les problèmes ci-dessus sont résolus dans php-fpm en créant un pool différent qui s’exécute sous un utilisateur différent pour chaque site.

[[step-1-- configuring-php-fpm]] === Étape 1 - Configuration de php-fpm

Si vous avez couvert les conditions préalables, vous devriez déjà avoir un site Web fonctionnel sur le Droplet. Sauf si vous avez spécifié un nom de domaine personnalisé pour celui-ci, vous devriez pouvoir y accéder localement sous fqdn + localhost + ou par l’adresse IP du droplet.

Nous allons maintenant créer un deuxième site (site1.exemple.org) avec son propre pool php-fpm et son propre utilisateur Linux.

Commençons par créer l’utilisateur nécessaire. Pour une meilleure isolation, le nouvel utilisateur doit avoir son propre groupe. Commencez donc par créer le groupe d’utilisateurs + site1 +:

sudo groupadd site1

Ensuite, créez un site utilisateur1 appartenant à ce groupe:

sudo useradd -g site1 site1

Jusqu’à présent, le nouveau site utilisateur1 n’a pas de mot de passe et ne peut pas se connecter à Droplet. Si vous devez fournir à quelqu’un un accès direct aux fichiers de ce site, vous devez créer un mot de passe pour cet utilisateur à l’aide de la commande + sudo passwd site1 +. Avec la nouvelle combinaison utilisateur / mot de passe, un utilisateur peut se connecter à distance par ssh ou sftp. Pour plus d’informations et pour plus de détails sur la sécurité, consultez l’article Setup un utilisateur secondaire de SSH / SFTP avec un accès limité aux répertoires.

Ensuite, créez un nouveau pool php-fpm pour site1. Un pool php-fpm dans son essence même n’est qu’un processus Linux ordinaire qui s’exécute sous un certain utilisateur / groupe et écoute sur un socket Linux. Il pourrait également écouter sur une combinaison IP: port aussi, mais cela nécessiterait davantage de ressources Droplet, et ce n’est pas la méthode préférée.

Par défaut, dans Ubuntu 14.04, chaque pool php-fpm doit être configuré dans un fichier du répertoire + / etc / php5 / fpm / pool.d +. Chaque fichier avec les extensions + .conf + dans ce répertoire est automatiquement chargé dans la configuration globale php-fpm.

Donc, pour notre nouveau site, créons un nouveau fichier + / etc / php5 / fpm / pool.d / site1.conf +. Vous pouvez le faire avec votre éditeur préféré comme ceci:

sudo vim /etc/php5/fpm/pool.d/site1.conf

Ce fichier doit contenir:

/etc/php5/fpm/pool.d/site1.conf

[site1]







pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
chdir = /

Dans la configuration ci-dessus, notez ces options spécifiques:

  • + [site1] + est le nom du pool. Pour chaque pool, vous devez spécifier un nom unique.

  • + user et` + group` représentent l’utilisateur Linux et le groupe sous lequel le nouveau pool sera exécuté.

  • + listen + devrait pointer vers un emplacement unique pour chaque pool.

  • + listen.owner + et + listen.group + définissent la propriété de l’écouteur, c.-à-d. le socket du nouveau pool php-fpm. Nginx doit pouvoir lire cette socket. C’est pourquoi le socket est créé avec l’utilisateur et le groupe sous lesquels nginx s’exécute - + www-data +.

  • + php_admin_value + vous permet de définir des valeurs de configuration php personnalisées. Nous l’avons utilisé pour désactiver des fonctions pouvant exécuter des commandes Linux - + exec, passthru, shell_exec, system +.

  • + php_admin_flag + est similaire à + ​​php_admin_value +, mais il ne s’agit que d’un commutateur pour les valeurs booléennes, c’est-à-dire allumé et éteint. Nous allons désactiver la fonction PHP + allow_url_fopen + qui permet à un script PHP d’ouvrir des fichiers distants et qui pourrait être utilisé par un attaquant.

Les options + pm + sont en dehors du sujet de sécurité actuel, mais sachez qu’elles vous permettent de configurer les performances du pool.

L’option + chdir + devrait être + / +, qui est la racine du système de fichiers. Ceci ne devrait pas être changé à moins d’utiliser une autre option importante + chroot +.

L’option + chroot + n’est pas incluse volontairement dans la configuration ci-dessus. Cela vous permettrait d’exécuter un pool dans un environnement jailed, c.-à-d. verrouillé dans un répertoire. Ceci est idéal pour la sécurité car vous pouvez verrouiller le pool à la racine Web du site. Cependant, cette sécurité ultime posera de sérieux problèmes à toute application PHP décente qui repose sur des fichiers binaires système et des applications telles que Imagemagick, qui ne seront pas disponibles. Si ce sujet vous intéresse, lisez l’article https://www.digitalocean.com/community/tutorials/how-to-use-firejail-to-set-up-a-wordpress-installation-in-a- jailed-environment [Comment utiliser Firejail pour configurer une installation WordPress dans un environnement Jailed].

Une fois que vous avez terminé avec la configuration ci-dessus, redémarrez php-fpm pour que les nouveaux paramètres prennent effet avec la commande:

sudo service php5-fpm restart

Vérifiez que le nouveau pool fonctionne correctement en recherchant ses processus comme suit:

ps aux |grep site1

Si vous avez suivi les instructions exactes jusqu’ici, vous devriez voir une sortie semblable à:

  14042  0.0  0.8 133620  4208 ?        S    14:45   0:00 php-fpm: pool site1
  14043  0.0  1.1 133760  5892 ?        S    14:45   0:00 php-fpm: pool site1

En rouge, l’utilisateur sous lequel le processus ou le pool php-fpm est exécuté - site1.

De plus, nous allons désactiver la mise en cache php par défaut fournie par opcache. Cette extension de mise en cache particulière peut être excellente pour les performances, mais pas pour la sécurité, comme nous le verrons plus tard. Pour le désactiver, éditez le fichier + / etc / php5 / fpm / conf.d / 05-opcache.ini + avec les privilèges de super utilisateur et ajoutez la ligne suivante:

/etc/php5/fpm/conf.d/05-opcache.ini

opcache.enable=0

Puis redémarrez à nouveau php-fpm (+ sudo service php5-fpm restart +) pour que le paramètre soit pris en compte.

[[step-2-- configuring-nginx]] === Étape 2 - Configuration de nginx

Une fois que nous aurons configuré le pool php-fpm pour notre site, nous configurerons le bloc de serveur dans nginx. Pour cela, veuillez créer un nouveau fichier + / etc / nginx / sites-available / site1 + avec votre éditeur préféré, comme ceci:

sudo vim /etc/nginx/sites-available/site1

Ce fichier doit contenir:

/ etc / nginx / sites-available / site1

server {
   listen 80;


   index index.php index.html index.htm;



   location / {
       try_files $uri $uri/ =404;
   }

   location ~ \.php$ {
       try_files $uri =404;
       fastcgi_split_path_info ^(.+\.php)(/.+)$;

       fastcgi_index index.php;
       fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
       include fastcgi_params;
   }
}

Le code ci-dessus montre une configuration commune pour un bloc de serveur dans nginx. Notez les parties en surbrillance intéressantes:

  • La racine Web est + / usr / share / nginx / sites / site1 +.

  • Le nom du serveur utilise fqdn + site1.exemple.org +, qui est celui mentionné dans les conditions préalables de cet article.

  • + fastcgi_pass + spécifie le gestionnaire pour les fichiers php. Pour chaque site, vous devez utiliser un socket Unix différent, tel que + / var / run / php5-fpm-site1.sock +.

Créez le répertoire racine Web:

sudo mkdir /usr/share/nginx/sites
sudo mkdir /usr/share/nginx/sites/site1

Pour activer le site ci-dessus, vous devez créer un lien symbolique vers ce répertoire dans le répertoire + / etc / nginx / sites-enabled / +. Cela peut être fait avec la commande:

sudo ln -s /etc/nginx/sites-available/site1 /etc/nginx/sites-enabled/site1

Enfin, redémarrez nginx pour que le changement prenne effet comme ceci:

sudo service nginx restart

[[step-3-- testing]] === Étape 3 - Test

Pour exécuter les tests, nous utiliserons la fonction phpinfo bien connue, qui fournit des informations détaillées sur l’environnement php. Créez un nouveau fichier sous le nom + info.php + qui ne contient que la ligne + <? Php phpinfo (); ?> + `. Vous aurez d’abord besoin de ce fichier sur le site nginx par défaut et sur sa racine Web `+ / usr / share / nginx / html / +. Pour cela, vous pouvez utiliser un éditeur comme celui-ci:

sudo vim /usr/share/nginx/html/info.php

Après cela, copiez le fichier dans la racine Web de l’autre site (site1.exemple.org) comme ceci:

sudo cp /usr/share/nginx/html/info.php /usr/share/nginx/sites/site1/

Vous êtes maintenant prêt à exécuter le test le plus élémentaire pour vérifier l’utilisateur du serveur. Vous pouvez effectuer le test avec un navigateur ou à partir du terminal Droplet et de Lynx, le navigateur en ligne de commande. Si vous n’avez pas encore Lynx sur votre Droplet, installez-le avec la commande + sudo apt-get install lynx +.

Commencez par vérifier le fichier + info.php + de votre site par défaut. Il devrait être accessible sous localhost comme ceci:

lynx --dump http://localhost/info.php |grep 'SERVER\["USER"\]'

Dans la commande ci-dessus, nous filtrons la sortie avec grep uniquement pour la variable + SERVER [" USER "] + qui représente l’utilisateur du serveur. Pour le site par défaut, la sortie devrait afficher l’utilisateur par défaut + www-data comme ceci:

_SERVER["USER"]                 www-data

De même, vérifiez ensuite l’utilisateur du serveur pour site1.example.org:

lynx --dump http://site1.example.org/info.php |grep 'SERVER\["USER"\]'

Vous devriez voir cette fois dans la sortie l’utilisateur + site1 +:

_SERVER["USER"]                 site1

Si vous avez défini des paramètres php personnalisés pour un pool php-fpm, vous pouvez également vérifier les valeurs correspondantes de la manière indiquée ci-dessus en filtrant la sortie qui vous intéresse.

Jusqu’à présent, nous savons que nos deux sites fonctionnent sous des utilisateurs différents, mais voyons maintenant comment sécuriser une connexion. Pour illustrer le problème de sécurité que nous résolvons dans cet article, nous allons créer un fichier contenant des informations sensibles. Généralement, un tel fichier contient la chaîne de connexion à la base de données et inclut les détails de l’utilisateur et du mot de passe de l’utilisateur de la base de données. Si quelqu’un découvre cette information, il peut faire n’importe quoi avec le site en question.

Avec votre éditeur favori, créez un nouveau fichier dans votre site principal + / usr / share / nginx / html / config.php +. Ce fichier doit contenir:

/usr/share/nginx/html/config.php

<?php
$pass = 'secret';
?>

Dans le fichier ci-dessus, nous définissons une variable appelée + pass + qui contient la valeur + secret. Naturellement, nous souhaitons restreindre l’accès à ce fichier. Nous allons donc définir ses autorisations sur 400, ce qui donne un accès en lecture seule au propriétaire du fichier.

Pour modifier les autorisations sur 400, exécutez la commande:

sudo chmod 400 /usr/share/nginx/html/config.php

De plus, notre site principal est géré par l’utilisateur + www-data + qui devrait pouvoir lire ce fichier. Ainsi, changez la propriété du fichier sur cet utilisateur comme ceci:

sudo chown www-data:www-data /usr/share/nginx/html/config.php

Dans notre exemple, nous allons utiliser un autre fichier appelé + / usr / share / nginx / html / readfile.php + pour lire les informations secrètes et les imprimer. Ce fichier doit contenir le code suivant:

/usr/share/nginx/html/readfile.php

<?php
include('/usr/share/nginx/html/config.php');
print($pass);
?>

Changez également le propriétaire de ce fichier en + www-data:

sudo chown www-data:www-data /usr/share/nginx/html/readfile.php

Pour confirmer que toutes les autorisations et les droits de propriété sont corrects dans la racine Web, exécutez la commande + ls -l / usr / share / nginx / html / +. Vous devriez voir une sortie similaire à:

-r-------- 1 www-data www-data  27 Jun 19 05:35 config.php
-rw-r--r-- 1 www-data www-data  68 Jun 21 16:31 readfile.php

Accédez maintenant au dernier fichier sur votre site par défaut à l’aide de la commande + lynx --dump http: // localhost / readfile.php +. Vous devriez pouvoir voir imprimé dans la sortie + secret + qui montre que le fichier contenant des informations sensibles est accessible sur le même site, ce qui correspond au comportement correct attendu.

Copiez maintenant le fichier + / usr / share / nginx / html / readfile.php + sur votre deuxième site, site1.example.org, comme ceci:

sudo cp /usr/share/nginx/html/readfile.php /usr/share/nginx/sites/site1/

Pour que les relations entre le site et les utilisateurs restent en ordre, assurez-vous que, sur chaque site, les fichiers appartiennent à l’utilisateur du site respectif. Pour ce faire, modifiez la propriété du fichier nouvellement copié sur site1 à l’aide de la commande suivante:

sudo chown site1:site1 /usr/share/nginx/sites/site1/readfile.php

Pour confirmer que vous avez défini les autorisations et la propriété correctes du fichier, veuillez répertorier le contenu de la racine Web de site1 à l’aide de la commande + ls -l / usr / share / nginx / sites / site1 / +. Tu devrais voir:

-rw-r--r-- 1 site1 site1  80 Jun 21 16:44 readfile.php

Essayez ensuite d’accéder au même fichier à partir de site1.exemple.com avec la commande + lynx --dump http: // site1.exemple.org / readfile.php +. Vous ne verrez que les espaces vides retournés. De plus, si vous recherchez des erreurs dans le journal des erreurs de nginx avec la commande grep + sudo grep error / var / log / nginx / error.log +, vous verrez:

2015/06/30 15:15:13 [error] 894#0: *242 FastCGI sent in stderr: "PHP message: PHP Warning:  include(/usr/share/nginx/html/config.php): failed to open stream: Permission denied in /usr/share/nginx/sites/site1/readfile.php on line 2

L’avertissement indique qu’un script du site site1.example.org ne peut pas lire le fichier sensible + config.php + du site principal. Ainsi, les sites qui fonctionnent sous différents utilisateurs ne peuvent pas compromettre la sécurité des autres.

Si vous revenez à la fin de la partie configuration de cet article, vous verrez que nous avons désactivé la mise en cache par défaut fournie par opcache. Si vous êtes curieux de savoir pourquoi, essayez de réactiver opcache en définissant avec les privilèges de superutilisateur + opcache.enable = 1 + dans le fichier + / etc / php5 / fpm / conf.d / 05-opcache.ini + et redémarrez php5-fpm avec la commande + sudo service php5-fpm restart +.

Étonnamment, si vous exécutez à nouveau les étapes de test dans le même ordre, vous pourrez lire le fichier confidentiel indépendamment de sa propriété et de sa permission. Ce problème dans opcache est signalé depuis longtemps, mais il n’a pas encore été résolu au moment de la rédaction de cet article.

Conclusion

Du point de vue de la sécurité, il est essentiel d’utiliser des pools php-fpm avec un utilisateur différent pour chaque site du même serveur Web Nginx. Même s’il y a une petite pénalité de performance, l’avantage d’un tel isolement pourrait empêcher de graves violations de la sécurité.

L’idée décrite dans cet article n’est pas unique, et elle est présente dans d’autres technologies d’isolement PHP similaires, telles que SuPHP. Cependant, la performance de toutes les autres alternatives est bien pire que celle de php-fpm.