1. Principe de base de l'isolation d'environnements « chrootés »
Dans les systèmes d'exploitation UNIX, chroot est une commande servant à changer le répertoire racine d'un processus de la machine hôte. La commande permet alors d'isoler l'exécution d'un programme dans un environnement limité, sans accès au système complet. Par analogie, on peut voir chroot comme un coffre-fort à l'intérieur d'une banque ; quelqu'un enfermé dans ce coffre-fort ne peut accéder au reste de la banque (sauf s'il s'appelle Harry Houdini, mais nous nous égarons...).
2. Utilisation du chroot pré-intégré à OpenSSH
Depuis sa version 4.9, OpenSSH offre directement un chroot sur SFTP (SSH File Transfert Protocol). Cette version d'OpenSSH est disponible dans les dépôts depuis Ubuntu 9.10 et Debian 5. Les différentes commandes décrites ci-après sont saisies côté serveur distant.
Étape 1 : Préparation de l'environnement
1) Installation d'OpenSSH
Commençons par installer OpenSSH (si ce n'est déjà fait).
% sudo apt-get update ; sudo apt-get install openssh
Nous pouvons vérifier la version d'OpenSSH par la commande suivante :
% ssh -V
OpenSSH_6.0p1 Debian-4+deb7u1, OpenSSL 1.0.1e 11 Feb 2013
2) Choix et préparation de l'environnement utilisateur
Nous devons définir un dossier de base qui accueillera les homes (environnements isolés) de chaque utilisateur. Pour des raisons de facilité, nous placerons les différents environnements dans /home. Rien ne vous empêche de définir un autre dossier de base, que vous pouvez créer n'importe où :
% sudo mkdir -p /VOTRE_CHEMIN
Nous allons également créer un groupe qui rassemblera les utilisateurs SFTP et que nous appellerons (par exemple) sftpusers :
% sudo groupadd sftpusers
Étape 2 : Configuration
Les étapes préliminaires terminées, nous pouvons attaquer la configuration.
1) Configuration d'OpenSSH
La configuration du serveur OpenSSH se fait par le biais du fichier de configuration /etc/ssh/sshd_config. Nous allons commencer par remplacer le mode de gestion SFTP par défaut par le mode interne à SSH :
# Subsystem sftp /usr/lib/openssh/sftp-server
Subsystem sftp internal-sftp
Juste après cette section, nous allons définir les utilisateurs pour lesquels va s'appliquer ce mode de gestion :
Match Group sftpusers
ChrootDirectory /home/%u
ForceCommand internal-sftp
AllowTCPForwarding no
X11Forwarding no
Vous pourriez de la même façon définir un pattern par utilisateur plutôt que par groupe, avec la syntaxe Match User NOM_DU_USER. Bien entendu, il faudra remplacer /home par tout autre dossier de base que vous auriez choisi au point précédent.
2) Création de compte utilisateur
Il ne reste plus qu'à créer des utilisateurs :
% sudo useradd -m -g sftpusers NOM_UTILISATEUR
% sudo passwd NOM_UTILISATEUR
Entrez le nouveau mot de passe UNIX : MDP_UILISATEUR
NOM_UTILISATEUR et MDP_UTILISATEUR doivent évidemment être remplacés par le nom et mot de passe de votre utilisateur. À noter qu'il faut ajouter l'argument -d /VOTRE_CHEMIN si vous n'utilisez pas /home comme dossier de base.
3) Validation de fonctionnement et configurations spécifiques
À ce niveau, vous pouvez déjà essayer depuis un poste client :
% sftp NOM_UTILISATEUR@SERVEUR_DISTANT
NOM_UTILISATEUR@SERVEUR_DISTANT's password:
Connection closed
Vous aurez alors un message d'erreur du type : « Connection closed, Error code 255, ... ». Il s'agit d'une sécurité incluse dans OpenSSH. En faisant un petit tour dans la manpage de sshd_config, nous noterons la description suivante :
ChrootDirectory
Specifies the pathname of a directory to chroot(2) to after authentication. All components of the pathname must be root-owned directories that are not writable by any other user or group. After the chroot, sshd(8) changes the working directory to the user's home directory.
La solution consiste à changer le propriétaire et les droits du dossier utilisateur :
% sudo chown root:root /home/NOM_UTILISATEUR
% sudo chmod o-w /home/NOM_UTILISATEUR
Réessayons de nous connecter depuis le client :
% sftp NOM_UTILISATEUR@SERVEUR_DISTANT
NOM_UTILISATEUR@SERVEUR_DISTANT's password:
Connected to SERVEUR_DISTANT.
sftp>
Parfait, ça fonctionne. Mais est-ce suffisant ? Essayons de vérifier que nous disposons bien d'un accès en écriture à l'environnement :
sftp> mkdir test
Couldn't create directory: Permission denied
Comme indiqué précédemment, le premier niveau du dossier « chrooté » doit appartenir à root et ne doit pas être accessible en écriture aux autres utilisateurs. Donc, au niveau du principe de fonctionnement, il faut ajouter un sous-dossier qui, cette fois, appartiendra à l'utilisateur. Pour notre exemple, soit un dossier www :
% sudo mkdir /home/NOM_UTILISATEUR/www
% sudo chown NOM_UTILISATEUR /home/NOM_UTILISATEUR/www
Il ne nous reste plus qu'à valider que tout fonctionne correctement. Depuis le poste client :
% sftp NOM_UTILISATEUR@SERVEUR_DISTANT
NOM_UTILISATEUR@SERVEUR_DISTANT's password:
Connected to SERVEUR_DISTANT.
sftp> cd www
sftp> mkdir test
sftp> ls
test
sftp>
Maintenant, qu'en est-il de la connexion SSH normale ? Tentons de nous connecter en SSH simple :
% ssh NOM_UTILISATEUR@SERVEUR_DISTANT
NOM_UTILISATEUR@SERVEUR_DISTANT's password:
This service allows sftp connections only.
Connection to SERVEUR_DISTANT closed.
Nous venons donc de mettre en place un environnement isolé pour un utilisateur ! Vous pouvez répéter cette procédure pour chaque nouvel utilisateur.
3. Utilisation d'un chroot externe à OpenSSH
La simplicité de la solution précédente peut être intéressante s'il ne s'agit que de transferts de fichiers et d'opérations simples (renommage, déplacement…), mais nous pouvons être rapidement limités par les commandes disponibles. La solution alors, est de ne pas utiliser la commande chroot intégrée à OpenSSH, mais d'utiliser le chroot système.
Nous supposons que les différentes commandes décrites ci-après se font côté serveur distant. Par ailleurs, cette solution est indépendante de la solution précédente.
Étape 1 : Préparation de l'environnement
On commence par installer OpenSSH, comme dans la première partie. De même, on définit le répertoire qui accueillera les environnements isolés, puis un groupe d'utilisateurs (chrootusers ici).
Étape 2 : Configuration
1) Configuration de OpenSSH
Dans le fichier /etc/ssh/sshd_config, on ajoute une section pour définir les utilisateurs auxquels va s'appliquer ce mode de gestion :
Match Group chrootusers
ChrootDirectory /home/%u
AllowTCPForwarding no
X11Forwarding no
À nouveau, il est possible de définir un pattern par utilisateur avec Match User NOM_DU_USER.
2) Création de compte utilisateur
% sudo useradd -m -g chrootusers NOM_UTILISATEUR
% sudo passwd NOM_UTILISATEUR
Entrez le nouveau mot de passe UNIX : MDP_UILISATEUR
Ici encore, n'oubliez pas d'ajouter l'argument -d /VOTRE_CHEMIN si vous n'utilisez pas /home comme dossier de base.
3) Génération de l'environnement restreint
Durant cette étape, nous allons générer une cage (jail) pour l'utilisateur. Cette dernière inclura les programmes et commandes que notre utilisateur aura le droit d'utiliser. Afin de simplifier les différentes opérations nécessaires à la génération de l'environnement restreint, nous allons commencer par créer un script de génération, que nous appellerons genJail.bash. Devant la complexité de ce script, nous allons le détailler partie par partie.
Pour commencer, déclarons nos variables d'environnement :
Dans APPS, nous déclarerons le chemin complet vers toutes les commandes que nous souhaitons accorder à l'utilisateur, séparées par un espace.
Nous allons à présent créer la structure de base du dossier utilisateur :
Ici, nous avons créé l'arborescence de base et généré les pseudo devices null et zero, utilisables directement par l'utilisateur dans sa cage et utilisés par nombre de commandes Linux. De nombreuses commandes ne fonctionnent pas (ou mal) si elles n'arrivent pas à récupérer les informations utilisateur (par exemple, la commande scp). Pour contourner ce problème, il faut générer un fichier etc/passwd contenant au minimum les informations sur l'utilisateur et sur root. Nous allons donc ajouter ceci dans notre script :
Ensuite, il faut copier dans la cage les dépendances des applications déclarées au début du script :
Malheureusement, ça ne suffit toujours pas. Comme expliqué précédemment, certaines applications nécessitent de récupérer les informations utilisateur. Juste créer un fichier passwd n'est pas suffisant, car en l'état, l'environnement de la cage ignore qu'il doit utiliser le fichier passwd. Pour ce faire, d'autres bibliothèques et fichiers de configuration sont nécessaires, mais n'ont pas été remontés par la commande ldd.
Sans rentrer dans les détails sur la méthode d'identification de ces fichiers, il faut comparer la liste des dépendances générée dans $TMPFILE3 avec les logs générés par la commande strace, en surveillance des applications demandant les UID et GID.
Au final, nous pouvons terminer notre script avec la récupération des fichiers manquants (certains à un emplacement fixe, d'autres à un emplacement variable en fonction de la distribution) les plus couramment utilisés (à noter qu'une identification basée sur autre chose que le fichier passwd local demandera sans doute d'autres librairies et fichiers de configuration) :
4) Ajout d'applications à l'environnement restreint : makejail
Nous avons un environnement restreint sur SSH fonctionnel. Après coup, nous pourrions avoir besoin d'autoriser l'une ou l'autre application supplémentaire. Comme nous avons pu le constater, la mécanique à mettre en œuvre n'est pas des plus simples. Pour faciliter l'ajout de nouvelles applications, nous allons cette fois utiliser un programme disponible dans les dépôts : makejail. Pour l'installer, nous utiliserons la commande suivante :
% sudo apt-get update
% sudo apt-get install makejail
Ce programme fonctionne sur la base de scripts Python. Il rassemble à la fois la recherche de dépendances dynamiques (ldd) et de dépendances indirectes (strace).
Soit un fichier de configuration addCommand.pydans lequel nous allons définir les paramètres d'exécution demakejail :
Il suffit alors de lancer makejail avec notre fichier de configuration pour ajouter la(les) commande(s) à l'environnement restreint de l'utilisateur :
% sudo makejail addCommand.py
Le programme makejail lance des itérations successives de strace pour identifier tous les fichiers de dépendances indirectes. C'est à la fois sa force et sa faiblesse. Pour un grand nombre de commandes à ajouter à l'environnement restreint, son temps d'exécution devient vraiment énorme (principale raison pour laquelle il n'a pas été retenu pour la génération de l'environnement restreint initial).
Nous privilégierons donc son utilisation pour des environnements légers, ou pour des commandes ayant un grand nombre de dépendances (par exemple : Ruby, Python, …). Enfin, nous n'avons qu'effleuré l'utilisation du paquet makejail. Pour plus d'informations, je vous invite à visiter le site de son auteur sur http://www.floc.net/makejail/.
Nous avons vu que l'environnement restreint devait garder une trace des UID et GUID de l'utilisateur courant et de root pour certaines commandes (ex : scp). Si vous utilisez makejailplutôt que le scriptgenJail.bashdétaillé ci-avant, les informations de UID/GUID devront être ajoutées manuellementdans les fichiers /etc/passwd et /etc/group de l'environnement restreint.
Conclusion
Avec chacune de ces méthodes, nous arrivons au résultat final souhaité qui était d'avoir un dossier web accessible avec une session restreinte. La première méthode, plus simple à mettre en place, est plus limitée que la seconde. Nous avons également vu comment ajouter simplement des commandes à un environnement restreint existant, à l'aide du paquet makejail. Enfin, vous pourriez obtenir des solutions comparables par l'utilisation de conteneurs (LXC, …).
Il ne reste plus qu'à configurer votre serveur web pour utiliser ces dossiers pour le rendu de vos pages web. Si votre serveur web s'exécute avec un utilisateur particulier (par ex : www-data), ajoutez à votre procédure la création d'un groupe contenant votre utilisateur ET l'utilisateur du serveur web. Ensuite, définissez ce groupe comme groupe de base du dossier www de votre utilisateur.