Retour à la page d'accueil OpenSSH Filezilla

Installer un serveur sFTP chrooté sur une machine GNU/Linux Debian Etch ou Lenny

Ou comment échanger des fichiers entre amis, d'un bout à l'autre du web, de manière relativement sécurisée, avec un système d'exploitation libre et en produisant l'Internet que l'on consomme.

/!\ Attention : Depuis la version 4.8p1 d'OpenSSH (version GNU/Linux), tout ce que je présente dans ce tutoriel peut être remplacé par 6 lignes de configuration.

# vi /etc/ssh/sshd_config
	Subsystem sftp internal-sftp
	Match group sftponly
	        ChrootDirectory /home/%u
        	X11Forwarding no
		AllowTcpForwarding no
		ForceCommand internal-sftp
	
Pré-requis
Mise en place
Configuration et connexion
Ajout de contenu
Conclusion
Bonus 1 : Surveiller le service et être prévenu de l'arrivée d'un fichier
Bonus 2 : Contrôler le débit sortant
Pré-requis ^

Considérons pour commencer que vous disposez d'un serveur sshd installé, configuré selon vos souhaits (cette explication se base sur une configuration Debian standard du serveur, la dernière stable en date : « Etch »).

Considérons aussi que vous avez installé un rssh. (Shell de login permettant de s'assurer que l'utilisateur s'y connectant ne pourra utiliser que sftp par exemple)

Exemple :

# apt-get install rssh

Puis décommentez les lignes suivantes dans /etc/rssh.conf

#allowsftp
#chrootpath = /chroot

En choisissant /chroot comme répertoire racine du confinement chroot que l'on souhaite obtenir. Confiner les utilisateurs dans une branche de l'arborescence de votre système a l'intérêt non négligeable de les empêcher de visiter l'intégralité de votre disque dur.

Enfin, considérons pour finir que vous avez un shell root ouvert et positionné dans le répertoire devant servir de nouvelle racine à l'environnement chroot. (répertoire créé à cet effet de préférence)

Mise en place ^

Il faut maintenant peupler le chroot avec les exécutables qui devront y être disponible. Pour cela, il convient de les copier, avec les bibliothèques de fonctions dont ils dépendent, dans une arborescence identique à l'originale. L'opération serait fastidieuse sans l'aide du script suivant copy_install.sh :

#!/bin/bash
install -D $1 $2$1
for x in `ldd $1 | grep -o '/[^[:space:]]*'`; do # foreach lib
	if [ ! -e $2$x ]; then install -D $x $2$x; fi
done

Ce dernier vous permet de simplement saisir la commande suivante pour copier tout le nécessaire d'un coup :

# ./copy_install.sh /usr/bin/ssh /rep/chroot/dest/
# ./copy_install.sh /usr/bin/scp /rep/chroot/dest/
# ./copy_install.sh /usr/bin/sftp /rep/chroot/dest/

Il vous appartient donc de le copier dans un fichier texte sur votre système, nommé copy_install.sh et de lui donner les droits d'exécution pour vous en servir.

Ensuite, il vous faut ensuite créer un /etc/passwd avec au moins la ligne de chaque utilisateur devant avoir accès au chroot. Cette formalité est indispensable pour ssh. Il est sûrement préférable d'épurer ce fichier en ne laissant que les lignes nécessaires comme détaillé plus bas.

Il vous faut également un /dev/null en bonne et due forme (toujours pour ssh) :

# mkdir dev
# mknod dev/null c 1 3
# chmod 666 dev/null

Vous aurez encore besoin du fichier :

# cp /usr/lib/openssh/sftp-server usr/lib/openssh/sftp-server

Puis il vous faut placer le setuid sur le rssh_chroot_helper :

# chmod u+s /usr/lib/rssh/rssh_chroot_helper

Voilà, votre environnement est prêt :-)

Configuration et connexion ^

Maintenant, il faut préciser dans le fichier /etc/passwd du système hôte, que l'utilisateur auquel on veut donner accès (invite) utilise le shell de login rssh. Pour cela remplacez :

invite:x:1002:1002:,,,:/home/invite:/bin/bash

Par :

invite:x:1002:1002:,,,:/chroot/invite:/usr/bin/rssh

Notons que le chemin du répertoire maison de l'utilisateur est changé pour pointer désormais vers le répertoire du contenu sFTP après l'opération de chroot.

Ensuite, il faut préciser dans le /chroot/etc/passwd que l'utilisateur "invite" existe, en y inscrivant l'unique ligne :

invite:x:1002:1002:,,,::

Notons que le champs de shell de login du fichier /chroot/etc/passwd n'a pas d'importance, il est ignoré, tout comme celui du répertoire maison de l'utilisateur.

Voilà, l'utilisateur "invite" peut désormais taper :

$ sftp invite@ta_machine.fr.eu.org

Puis saisir son mot de passe pour arriver au prompt :

sftp>

On peut également utiliser un client sftp graphique comme FileZilla, ou GFTP.

Pour se connecter avec FileZilla, il faut créer une nouvelle connexion dans le gestionnaire de connexions, choisir qu'elle soit authentifiée (avec nom d'utilisateur et mot de passe), puis, choisir dans le menu déroulant des protocoles disponibles, qui se dégrise alors, le protocole sFTP. (Voir )

Ajout de contenu ^

Toutefois, monter un sftp chrooté sans y ajouter de contenu c'est peut être un peu décevant. Voici comment replier sur elle même votre arborescence afin de donner accès, dans le chroot, à une partie de vos fichiers :

# mount -o bind /rep/source /rep/chroot/dest

Ou mieux, dans /etc/fstab :

/rep/source /rep/chroot/dest none bind

Enfin, en cas de "Connection closed" ou de "Couldn't read packet: Connection reset by peer", n'hésitez pas à consulter le syslog pour tenter de comprendre d'où vient le problème :

# tail /var/log/syslog
Conclusion ^

Voilà, vous pouvez maintenant partager via Internet et de façon sécurisée des fichiers en provenance de votre disque dur. En donnant des droits d'écriture dans un répertoire du chroot à votre utilisateur "invite", vous pouvez même recevoir des fichiers. Enfin c'est du FTP quoi...

Toutefois, si vous avez encore des questions, ou des remarques :

Bonus 1 : surveiller le service et être prévenu de l'arrivée d'un fichier ^

Permettre à un invité de déposer un fichier dans un dossier de son disque dur d'un bout à l'autre de la planète et de manière efficace et sécurisée est une possibilité puissante, mais en l'état du système, si rien ne prévient de l'arrivée d'un nouveau fichier, on risque fort de passer à côté.

Je propose donc de mettre en place en quelques lignes de script un système de surveillance qui préviendra par courriel de l'arrivée d'un nouveau fichier. Ce système requiert la présence d'un cron en état de marche ainsi que d'un MTA (ce qui est normalement le cas par défaut dans Debian). De part son fonctionnement, le script préviendra également d'un arrêt du service, suite par exemple à une mise à jour d'rssh ayant restauré les droits initiaux du fichier /usr/lib/rssh/rssh_chroot_helper...

Sur le principe, le mécanisme de surveillance se compose d'un script se connectant comme un invité au serveur sFTP pour y vérifier le contenu du dossier depots, lancé régulièrement par cron. Pour ce faire, il compare la liste des fichiers présents au moment de la connexion avec la liste précédemment générée et si une différence apparaît il la reporte. Donc s'il n'arrive pas a générer la nouvelle liste, il compare le message d'erreur obtenu à la précédent liste de fichiers, rapportant la différence.

Le fichier de script shell est donc le suivant :

#!/bin/bash
WHAT='depots'
TOWARN='sdescarp@fdn.fr'
PREFIX='/root'
CHECKER="$PREFIX/sftplogin.exp"
NOW=`date --rfc-3339=seconds`

logger "Check $WHAT with $CHECKER for $TOWARN at $NOW"
$CHECKER > $PREFIX/$WHAT.current
unset RES;
RES="`diff -U 0 $PREFIX/$WHAT.baseline $PREFIX/$WHAT.current 2>&1`"
if [ "$RES" != "" ]; then
	echo -e "Check $WHAT /!\ changements \n$RES" | logger
	unset MAIL;
	MAIL="To: $TOWARN
Subject: Changement dans $WHAT ($NOW)
Content-type: text/plain; charset=UTF-8
Réponse du script :\n« $RES »"
	echo -e "$MAIL" | /usr/lib/sendmail -t -f $TOWARN
	mv $PREFIX/$WHAT.current $PREFIX/$WHAT.baseline
fi

Il se découpe en deux parties, d'abord la définition de 5 variables, puis 10 lignes de code générique. La première variable « WHAT » contient le nom, libre, de ce qui est surveillé. C'est juste pour donner du sens aux messages générés par le script. « TOWARN » est l'adresse de courriel à contacter quand un nouveau fichier arrive. « PREFIX » est le chemin du dossier où se trouve le script, ici je propose de ranger le script dans le dossier root. « CHECKER » ne sert pas à préparer des boissons froides mais à générer l'état de ce que l'on souhaite surveiller, ici c'est la liste des fichiers dans le dossier depots et elle est obtenue en ayant recours à un deuxième script. Enfin la variable « NOW » contient tout simplement l'heure de lancement du script, pour les archives.

Le script commence ensuite par se présenter au fichier /var/log/syslog via la commande logger en ressortant discrètement au passage le contenu des variables... Puis le fichier $WHAT.current est créé dans le dossier « PREFIX » à partir de la sortie de la commande représentée par « CHECKER ». Puis on s'assure que la variable RES est bien vide et on y place la sortie de la commande diff, comparant la liste précédemment générée (et stockée dans le fichier $WHAT.baseline). Du coup, soit la liste de fichiers n'a pas changé et on ne fait rien, soit il y a des différences et on entre dans la condition if suivante. Si l'on passe dans le if on commence par envoyer au logger les différences trouvées, puis on prépare une variable MAIL (simple chaîne de caractère formatée comme doivent l'être les courriels), et on transmet son contenu à la commande sendmail qui, comme son nom l'indique, se charge d'émettre le courriel sur Internet. Enfin, on remplace le résultat sauvegardé par le résultat courant du CHECKER.

À ce stade, si le contenu de la variable CHECKER avait simplement été un « ls depots », l'affaire serait bouclée. On touche ici du doigt l'intérêt d'avoir des variables au début et du code générique ensuite, puisqu'en quelque changements dans les définitions de variables le script peut surveiller d'autres dossiers de votre système, comme par exemple /proc/mdstat qui rapporte la santé à un instant donné de votre système RAID. Il y a d'ailleurs moyen, en redécoupant la solution en plusieurs fichiers de ne garder qu'une seule copie du code générique, ce qui simplifie les opérations de maintenance.

Mais si l'on avait simplement listé le contenu du dossier depots en direct, sans passer par une connexion sFTP, on aurait alors pas été prévenu des éventuels arrêts de services, consécutifs aux mises à jour du système. C'est pourquoi on passe par un sftplogin.exp pour le moins énigmatique. Il s'agit d'un autre script, interprété par la commande expect. Pourquoi passer un tel artifice ? Tout simplement parce qu'on ne peut pas ouvrir une connexion ssh/sftp requérant une authentification par mot de passe sans passer par un script tiers, écrivant le mot de passe au bon moment. Un script expect ressemble donc à une pièce de théâtre, rejouant inlassablement le script d'une connexion sftp et complétant le scénario par votre mot de passe au bon moment.

#!/usr/bin/expect -f
# Expect script to supply root/admin password for remote ssh server
# and execute command.
spawn sftp invite@ta_machine.fr.eu.org
match_max 100000
# Look for password prompt
expect "*?assword:*"
# Send password aka $password
send -- "ton_mot_de_passe\n"
# send blank line (\r) to make sure we get back to gui
expect "*?sftp> "
send -- "ls -l depots\n"
expect "sftp> "
send -- "exit\n"
expect eof

Notre objectif en ouvrant une connexion sftp était était de lister le contenu du répertoire depots et c'est donc ce que fait expect aussitôt connecté, avant de quitter promptement. Il convient ici, en plus de recopier ce script dans un fichier nommé de manière à ce que le script précédent puisse le retrouver, de personnaliser les mentions : ta_machine.fr.eu.org et ton_mot_de_passe.

Pour finir, il nous faut encore indiquer à cron que nous avons du travail pour lui. Pour cela, on crée un lien dans le dossier /etc/cron.hourly/ vers notre script de surveillance situé lui dans /root/ :

# ln /root/check_depots.sh ../cron.daily/check_depots

Vous aurez remarqué au passage que le lien est nommé sans l'extension du nom de fichier « .sh », c'est un détail important pour cron.

Pour tester votre nouvelle installation, il suffit de lancer le script en l'absence de fichier $WHAT.baseline ou en ayant modifié au préalable le contenu du fichier. Si tout se passe bien, le script devrait vous prévenir par courriel qu'il n'a pas trouvée la plaisanterie à son goût, et ne rien dire au lancement suivant.

Bonus 2 : Contrôler le débit sortant ^

Étrangement, je n'ai pas trouvé de solution simple pour contrôler le débit sortant. En revanche, si une limitation, en dur, à un certain débit, peut sembler simple et efficace, c'est aussi une solution nettement sous-optimale. En effet, pouvoir choisir des priorités de débits pour différentes applications est bien plus malin, à l'image des priorités d'accès au CPU que se disputent les processus d'un système d'exploitation.

Mettons donc en place des règles de priorités de débit :-)

Dans ce domaine, à l'instar des règles d'un pare-feu, rien ne semble être livré en standard. La solution que je propose est donc un script à rajouter à son système, mais il existe des paquetages comme shaper ou wondershaper pour Debian, qui, sans offrir autant de souplesse que le script présenté ici s'avèrent déjà efficaces.

Le script que je propose est issus de l'article suivant. Autres références : ici et

Je propose donc le script suivant : qos.txt

Une fois téléchargé, il convient de le configurer. Le script propose 5 classes de priorité de débit, et il est possible d'y ranger des flux réseau triés par port ou par adresse, source ou destination. Dans notre cas les flux sont triés par ports, la classe par défaut a la priorité la plus basse, et c'est donc là qu'atterrirons les trafics non identifiés de transfert de masse. Le script n'est rien d'autre de la mise en place d'un environnement tc.

À chaque classe correspond une priorité, mais aussi un débit moyen et un débit maximum. À vous d'adapter ces valeurs en fonctions de votre connexion.

Une fois le script configuré, tapez :

# mv qos.txt /etc/network/if-up.d/qos

Pour le placer dans le répertoire où il sera automatiquement exécuté par Debian lors de l'éveil d'une interface réseau. Notons que le nom du script est changé, car les scripts dont le nom se termine par .sh ne semblent pas être exécutés...

Il faut donc aussi rendre le script exécutable :

# chmod 755 /etc/netwok/if-up.d/qos

Puis redémarrer les interfaces réseau :

# /etc/init.d/networking restart