20 Nov 2009

Paramétrer SSH et sécuriser ses transactions FTP et SVN dans un tunnel SSH.

Category: Configuration,Linux

Résumer de l'article

Cette article décrit comment paramétrer un serveur SSH pour n'accepter que les authentifications par clés et comment utiliser cette configuration pour mettre en place un serveur SVN sécurisé par un tunnel SSH. Nous verrons au passage qu'un serveur ftp sécurisé (sftp) est automatiquement disponible et comment utiliser rsync pour synchroniser des données entre deux machines ; le tout à travers un tunnel SSH.

L'OS utilisé est GNU/Linux Debian , mais la transposition à un autre environnement unix-like ne devrait pas poser de difficulté.

Prérequis

Installation

On commence par installer subversion, si l'on veut avoir un serveur svn, et openssh-server qui fournira entre autre un serveur ssh et sftp :
apt-get install subversion openssh-server.

Création d'un utilisateur pouvant s'authentifier par clef

Clefs ssh

Pour ce faire, on crée sur le poste client (le poste de celui qui va se connecter en ssh) une pair de clés; une privée à conserver précieusement et une publique que vous pouvez donner à la terre entière.
La commande ssh-keygen -t rsa -b 1024 -f ~/.ssh/key-1_rsa crée dans le répertoire ~/.ssh deux clefs : key-1_rsa et key-1_rsa.pub.
La clef key-1_rsa est la clef privée qu'il ne faut donner à personne et que vous devez garder précieusement à partir du moment où elle est utilisée pour signer/chiffrer des messages, des fichiers, des partitions etc ou encore pour se connecter à un serveur distant.
La clé key-1_rsa.pub est la clef publique correspondante que vous pouvez distribuer à tout le monde; elle permettra aux autres personnes de, par exemple, vérifier les messages que vous aurez signé avec la clé privée correspondante.

Utilisateur sur le serveur

Il faut maintenant créer un utilisateur, ssh_user par exemple mais cela pourrait être root, sur le poste serveur (celui auquel vous voulez vous connecter) tel que son fichier ~/.ssh/authorized_keys contienne la clef public; tout possesseur de la clef privée correspondante pourra alors se connecter au serveur en tant que ssh_user --d'où l'importance de garder précieusement sa clef privée--.
Sur le poste serveur:

  1. on crée l'utilisateur ssh_user : adduser ssh_user ;
  2. on devient ssh_user : su ssh_user ;
  3. si le répertoire ~/.ssh n'existe pas, on le crée: mkdir ~/.ssh ;
  4. on crée le fichier authorized_keys : touch ~/.ssh/authorized_keys ;
  5. et on restreint les droits de lecture : chmod 600 ~/.ssh/authorized_keys

Depuis le poste client, on envoie la clef publique sur le serveur par le moyen de son choix. En ssh, par exemple, ça donne :
scp ~/.ssh/key-1_rsa.pub root@ip_du_serveur:/home/ssh_user/.ssh/
Il faut ensuite entrer la clef publique dans le fichier /home/ssh_user/.ssh/authorized_keys; on peut encore utiliser ssh depuis le poste client :
ssh root@ip_du_serveur "cat /home/ssh_user/.ssh/key-1_rsa.pub >> /home/ssh_user/.ssh/authorized_keys
...ou utiliser son éditeur de texte préféré pour copier/coller le contenu du fichier.

L'utilisateur ssh_user est maintenant prêt... il ne reste plus qu'à configurer le serveur ssh pour qu'il n'autorise que les authentifications par clés.

Configurer SSH pour n'autoriser que les authentifications par clef

Voici un exemple de ce qu'il peut se trouver dans le fichier de configuration /etc/ssh/sshd_config, il est suffisamment commenté pour pouvoir l'adapter à des besoins spécifiques:

# Le port utilisé
Port 2564

# Version de ssh utilisée
Protocol 2

# Clefs système pour le protocol version 2
HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_dsa_key

# Spécifie si sshd sépare les privilèges en créant un processus fils
##non privilégié pour prendre en charge le trafic réseau entrant.
UsePrivilegeSeparation yes

# Le but de la regénération est d'éviter le décryptage de sessions capturées
##en s'introduisant plus tard sur la machine et en volant la clef.
##Pour la version 1 seulement !
KeyRegenerationInterval 3600
# Définit le nombre de bits de la clef éphémère pour la version 1 du protocole.
ServerKeyBits 768

# Donne le code de facilité utilisé lors de l'enregistrement
##des messages du démon sshd.
SyslogFacility AUTH
LogLevel INFO

# Authentification:
LoginGraceTime 120

# Spécifie si root peut se connecter par ssh(1).
##L'argument est « yes », « without-password »,
##« forced-commands-only » ou « no ».
PermitRootLogin  without-password

#Spécifie si sshd doit vérifier les modes et le propriétaire
##des fichiers de l'utilisateur et du répertoire de base
##de l'utilisateur avant d'accepter une connexion.
StrictModes yes

#Spécifie si on autorise la pure authentification RSA.
##Version 1 du protocole seulement !!
RSAAuthentication yes

# Une option mystérieuse qui date de l'éqpoque où seulement le DSA
##était reconnu par SSH 2
##Considérée comme un alias de PubkeyAuthentication ?!
DSAAuthentication yes
PubkeyAuthentication yes

# Spécifie le fichier contenant les clefs publiques à utiliser
##pour l'authentification d'un utilisateur.
AuthorizedKeysFile %h/.ssh/authorized_keys

# ne pas utiliser les fichiers ~/.rhosts et ~/.shosts
IgnoreRhosts yes

# On n'autorise une authentification par rhosts ou /etc/hosts.equiv
##suite à une authentification de machine RSA réussie.
##SSH 1 seulement !
RhostsRSAAuthentication no

# La même chose en SSH 2
HostbasedAuthentication no

# Spécifie si on autorise l'authentification par stimulation-réponse
ChallengeResponseAuthentication no

# Spécifie si l'authentification par mot de passe est autorisée.
PasswordAuthentication no

# Un pur et dur linuxien ou pas ?
X11Forwarding no
# X11DisplayOffset 10

# Spécifie si sshd doit afficher le contenu du fichier /etc/motd
##quand un utilisateur se connecte en mode interactif.
PrintMotd no

#Spécifie si sshd doit afficher la date et l'heure de la
##dernière connexion de l'utilisateur.
PrintLastLog yes

# Spécifie si le système doit envoyer des messages TCP
##de maintien de la connexion.
##Évite les utilisateurs fantômes lors de connexions interrompues
TCPKeepAlive yes

#Banner /etc/issue.net

# Autorise le client à passr des variables locales
AcceptEnv LANG LC_*

# N'autoriser que certains utilisateurs à se connecter.
AllowUsers XXX YYY ZZZ svn

# Configure un sous-système externe
##SSH 2 seulement !!
##Ici on autorise les connection sftp
Subsystem sftp /usr/lib/openssh/sftp-server

#Autorise le « Pluggable Authentication Module interface ».
##man sshd_config pour plus d'info !
UsePAM yes

# Local Variables:
# mode:shell-script
# comment-column:0
# End:
  

Attention !

Avant de tester la nouvelle configuration du serveur ssh il faut bien s'assurer que le port utilisé, ici le port 2564 (il vaut mieux ne pas utiliser le port standard pour des raisons évidentes de sécurité), soit bien ouvert. La commande netstat -lp --inet | grep ssh doit retourner quelque chose comme:

tcp        0      0 *:2564                  *:*                     LISTEN      22952/sshd
  

Pour ouvrir le port 2564, sous iptable par exemple, il faut avoir (chez moi c'est dans le fichier /etc/network/if-pre-up.d/iptables-star, mais ça peut être ailleurs ;-) ):

# SSH In
iptables -t filter -A INPUT -p tcp --dport 2564 -j ACCEPT
# SSH Out
iptables -t filter -A OUTPUT -p tcp --dport 2564 -j ACCEPT
  

Vérifier aussi que l'utilisateur ssh que vous avez créé est bien mentionné dans les AllowUsers du fichier /etc/ssh/sshd_config.

On peut maintenant tester la nouvelle configuration du serveur ssh:

* on redémarre le service ssh du poste serveur :
/etc/init.d/ssh restart
* on se connecte depuis le poste client :
ssh -p 2564 -i ~/.ssh/key-1_rsa ssh_user@ip_du_serveur

Ça devrait marcher...
À la création de la clef, si l'on n'entre pas de passphrase, aucun mot de passe n'est demandé pour se connecter au serveur; cela peut être utile pour lancer des taches planifiées sur le serveur depuis un poste client...

Serveur ftp sécurisé : sftp (Secure File Transfer Protocol)

C'est un peu déroutant, mais le sftp ressemble au ftp, s'utilise comme le ftp mais n'est pas du ftp ! En fait c'est un protocole basé sur scp, qui est encore différent du ftps et aussi différent du ftp over ssh; pour plus d'information, on peut lire en anglais cet article.
L'important pour nous c'est qu'il s'utilise comme du ftp (même les pauvres utilisateurs de Windows n'y voient que du feu), mais que tout ce qui transite dans les tuyaux est crypté (chiffré en français, svp)... Personnellement je n'utilise que du sftp et, pour les clients que j'héberge du ftps qui est aussi sécurisé mais ne donne pas accès au shell.
La bonne nouvelle dans cette histoire est, qu'avec la configuration que j'ai donné, le serveur sftp est déjà opérationnel !!
Pour s'en convaincre, il suffit de lancer la commande suivante depuis le poste client :
sftp -oPort=2564 -oIdentityFile=~/.ssh/key-1_rsa ssh_usert@ip_du_serveur

Synchroniser des données distants à travers un tunnel SSH

Rien de plus simple avec le programme rsync du paquet éponyme :
/usr/bin/rsync -auv --numeric-ids --perms -e "/usr/bin/ssh -p 2564 -i ~/.ssh/key-1_rsa" SOURCE/ ssh_user@IP_MARCHINE_DISTANTE:DESTINATION

Cette commande va synchroniser le répertoire distant DESTINATION (pas de « / » à la fin) de la machine distante IP_MARCHINE_DISTANTE avec le répertoire local SOURCE/ (ne pas oublier le « / ») en respectant les droits de tous les fichiers/répertoires. S'il l'on veut une véritable synchronisation, avec destruction des fichiers inexistant en local sur la machine distante, il faut ajouter l'option --delete.
Avec une passphrase vide et les droits root on peut planifier une sauvegarde rudimentaire.

SVN via SSH

Pour avoir du SVN sécurisé dans un tunnel SSH c'est un peu plus délicat mais l'essentiel est déjà en place...

Création d'un « utilisateur SVN via ssh » un peu spécial

On commence par créer un utilisateur spécial, le seul utilisateur à pourvoir se connecter en svn; on verra que l'on peut quand même différencier les utilisateurs en fonction de clef d'authentification utilisée...
Il suffit donc, dans un premier temps, de procéder exactement comme précédemment:

  • Créer l'utilisateur svn
  • Créer le fichier vierge /home/svn/.ssh/authorized_keys avec les bons droits ;
  • Créer autant de clefs qu'il y a d'utilisateurs svn (on peut laisser ici la passphrase vide pour les utilisateurs SVN, cela permet de se connecter sans mot de passe et n'affecte pas trop la sécurité).
    Par la suite on supposera de notre clef privée se nomme key-1_svn_rsa.
  • Copier le contenu de toutes les clefs dans le fichier /home/svn/.ssh/authorized_keys ;

Finalement on a dans le fichier /home/svn/.ssh/authorized_keys quelque chose comme

ssh-rsa ABCZ...ADDE= svn@machine
ssh-rsa UTFH...UDSA= svn@machine
etc...
  

Afin de personnaliser les clefs et de restreindre l'accès ssh des utilisateurs de ces clefs aux seules actions svn on peut modifier ainsi le contenu du fichier /home/svn/.ssh/authorized_keys:

command="/usr/bin/svnserve -t -r MON_DOSSIER_SVN/repos" ssh-rsa ABCZ...ADDE= USER1
command="/usr/bin/svnserve -t -r MON_DOSSIER_SVN/repos" ssh-rsa UTFH...UDSA= USER2
etc...
  

L'assignation command="/usr/bin/svnserve -t -r MON_DOSSIER_SVN/repos" permet ici de spécifier un shell particulier à l'utilisateur de la clef correspondante, ainsi que de fixer le répertoire SVN racine, nous en reparlerons (MON_DOSSIER_SVN est évidemment à modifier suivant votre convenance).
Ainsi MON_DOSSIER_SVN/repos sera la racine du serveur SVN des utilisateurs USER1 et USER2 ; tout chemin donné lors d'une connexion SVN sera relatif à ce chemin.
Mais on peut mieux faire....

command="/usr/bin/svnserve -t -r MON_DOSSIER_SVN/repos --tunnel-user=USER1" ssh-rsa ABCZ...ADDE= USER1
command="/usr/bin/svnserve -t -r MON_DOSSIER_SVN/repos --tunnel-user=USER2" ssh-rsa UTFH...UDSA= USER2
etc...
  

L'option --tunnel-user=USER1 permet de spécifier l'identifiant de l'utilisateur de la clef correspondante. Ainsi, un utilisateur se connectant en tant que svn avec la première clef sera en identifié comme étant USER1 ; si l'authentification se fait avec la deuxième clé, ce sera USER2 etc.
Et ce n'est pas tout !
Par mesure de sécurité, on peut encore restreindre les marges de manœuvre des utilisateurs SVN en ajoutant des options SSH :

command="/usr/bin/svnserve -t -r MON_DOSSIER_SVN/repos --tunnel-user=USER1",no-port-forwarding,no-pty,no-agent-forwarding,no-X11-forwarding ssh-rsa ABCZ...ADDE= USER1
command="/usr/bin/svnserve -t -r MON_DOSSIER_SVN/repos --tunnel-user=USER2",no-port-forwarding,no-pty,no-agent-forwarding,no-X11-forwarding ssh-rsa UTFH...UDSA= USER2
etc...
  

Création d'un premier projet sous contrôle SVN via SSH

Sans vraiment trop d'effort, le serveur SVN sécurisé est opérationnel... il ne reste plus qu'à créer notre premier projet. Pour rappel, on travaille sur le poste serveur.

On peut découper le dépôt en deux parties : une partie publique que tout le monde pourra voir et télécharger, par exemple avec WebSVN, et une partie privée qui ne sera accessible qu'aux personnes autorisées. Cela simplifiera les configurations ultérieures.
mkdir -p MON_DOSSIER_SVN/repos/public
mkdir -p MON_DOSSIER_SVN/repos/private
On change le propriétaire et le groupe du dépôt:
chown -R svn\: MON_DOSSIER_SVN/repos
On donne les droits convenables, avec un umask de 2 (SGID positionné à 2000 pour que tous les fichiers endossement l'identité du groupe svn):
chmod -R 2770 MON_DOSSIER_SVN/repos
On peut alors créer un premier projet mon_premier_projet, publique par exemple:

mkdir "MON_DOSSIER_SVN/repos/public/mon_premier_projet"
mkdir -p /tmp/repo/branches
mkdir /tmp/repo/tags
mkdir /tmp/repo/trunk
svnadmin create --fs-type fsfs "MON_DOSSIER_SVN/repos/public/mon_premier_projet"
svn import /tmp/repo file://"MON_DOSSIER_SVN/repos/public/mon_premier_projet" -m "initial import"
rm -rf /tmp/repo
  

Pour s'éviter d'avoir à retaper ces commandes à chaque création de projet on peut créer un petit script shell, disons add-svn-project, qui, en plus de créer le projet donné en paramètre (add-svn-project mon_premier_projet), nous demande si le projet est publique ou privé...

#!/bin/bash

## À configurer:
SVN_ROOT_DIR="MON_DOSSIER_SVN/repos/" # Doit se terminer pas un /
## Fin configuration

[ $# -eq 0 ] && {
    echo "Usage : $0 nom_du_projet"
    exit 1
}

REP='indet'

test_rep() {
    ( [ "$REP" = "private" ] || [ "$REP" = "public" ] ) && echo true
}

PCR='X'
while [ ! `test_rep $PCR` ]
do
    printf "Private or publiC project ? [P/c] ";read PCR
    # [ "$PCR"=='' ] && PCR='P'
    case "$PCR" in
        c|C)
            echo -e '\nPublic Site'
            REP='public'
            ;;
        p|P|'')
            echo -e '\nPrivate Site'
            REP='private'
            ;;
    esac
    if [ ! `test_rep` ]
    then
        echo -e '\nP=Private ; C=publiC'
    fi
done


proj_dir="${SVN_ROOT_DIR}${REP}/${1}"

mkdir "$proj_dir"
mkdir -p /tmp/repo/branches
mkdir /tmp/repo/tags
mkdir /tmp/repo/trunk
svnadmin create "$proj_dir" --fs-type fsfs
svn import /tmp/repo file://"$proj_dir" -m "initial import"
rm -rf /tmp/repo

chown -R svn:www-data "$proj_dir"
chmod -R 2770 "$proj_dir"
  

Première connexion et premier commit

On peut maintenant télécharger sur un poste client, disposant de la clef privée adéquat, le projet mon_premier_projet sous contrôle SVN. Ceci se fera au travers d'un tunnel SSH, sur le port 2564, avec authentification par clefs uniquement. La commande devrait ressembler à ceci :
SVN_SSH="ssh -p 2564 -i ~/.ssh/key-1_svn_rsa" svn co svn+ssh://svn@MON_DOMAINE.fr/mon_premier_projet ./mon_premier_projet

Pour tester un premier commit :
cd mon_premier_projet && touch trunk/essai.txt
svn add trunk/essai.txt
et pour le commit il ne faut pas oublier de préciser les options de connexion SSH dans la variable SVN_SSH :
SVN_SSH="ssh -p 2564 -i ~/.ssh/key-1_svn_rsa" svn commit -m 'Description du commit...'

Pour ne pas avoir à retaper à chaque fois les options SSH on peut créer un alias par la commande :
alias ssvn='SVN_SSH="ssh -p 2564 -i ~/.ssh/key-1_svn_rsa" svn'

Le commit précédent devient alors tout simplement :
ssvn commit -m 'Description du commit...'

Pour que l'alias soit permanent, il suffit de mettre la commande de création d'alias dans son fichier ~/.bashrc.

Installation et configuration de WebSVN

Rien de plus à dire que cet excellant article. ;-)

Étiquettes : , ,