Thomas Touhey
:
Identification par clé publique avec OpenSSH
Récemment, j’ai découvert la puissance des clés pour s’identifier sur des
services SSH divers, comprenant les shells simples (serveurs où j’aide à
l’administration), les shells git (tels que Github, ou les instances Gitlab
ou Gitolite), et les autres shells que je nomme “action shells” tels que
celui pour l’AUR de Archlinux. Mais on peut trouver ça un peu magique
quand, alors que vous vous connectez au même utilisateur que tout le monde
(généralement git
) avec votre certificat personnel, le shell distant vous
identifie. Par exemple :
Hi cakeisalie5! You've successfully authenticated, but GitHub does not provide shell access.
Mais j’ai fini par m’y habituer, et j’en suis venu à vouloir faire mon propre
shell d’action, pour pouvoir, entre autres, mettre à jour un site (en
récupérant les modifications et en re-construisant le projet Jekyll)
avec une commande locale qui ressemble à
ssh adm@domain.org update domain.org
. J’ai déjà fait
un shell interactif, mais maintenant, la subtilité est que je souhaite
identifier l’utilisateur à l’aide de la clé publique avec laquelle il se
connecte, et prendre la ligne de commande depuis sa ligne de commande
originale (dans mon exemple, update domain.org
).
La question “quel serveur SSH utiliserai-je pour ceci” a déjà une réponse,
puisqu’il y a un standard de facto pour ça : OpenSSH.
C’est un logiciel libre qui fournit tant des clients (ssh
, scp
, sftp
)
que le serveur (sshd
) et les utilitaires pour celui-ci tels que
sftp-server
ou ssh-agent
.
Si vous utilisez une distribution GNU/Linux, vous utilisez OpenSSH comme
client, et éventuellement comme serveur.
Ma première piste pour tenter d’identifier l’utilisateur était les variables
d’environnement définies par OpenSSH et transmises au shell, telles que
SSH_ORIGINAL_COMMAND
(que l’on utilisera !), mais rien dedans n’était
relatif à l’utilisateur identifié ou à la clé publique qu’il utilisait.
Après quelques heures de recherche, j’ai fini par trouver le cœur de la
bataille : le fichier authorized_keys
.
Dans une configuration d’OpenSSH basique, quand vous tentez de vous connecter
à votre compte en utilisant une clé publique, OpenSSH ouvrira votre fichier
~/.ssh/authorized_keys
et cherchera la clé publique avec laquelle vous
vous identifier. S’il vous trouve dedans, il vous connecte, et sinon, il
vous dira probablement Permission denied (publickey).
ou quelque chose
du même acabit. En réalité, ce fichier peut faire bien plus que cela :
pour chaque clé publique, il peut définir quelques propriétés avec lesquelles
l’utilisateur va se connecter, comme différentes options d’OpenSSH… ou
la commande qui va être exécutée ! Donc la technique consiste à définir la
commande avec un argument différent pour chaque clé dans ce fichier, comme
par exemple :
1
2
command="/opt/ssh-update/shell first-key" ssh-rsa <clé>
command="/opt/ssh-update/shell secnd-key" ssh-rsa <clé>
Ainsi, dans votre shell, vous aurez juste à lire votre premier argument pour
lire quel est l’utilisateur ! N’oubliez pas de mettre ceci dans la
configuration de sshd, généralement /etc/ssh/sshd_config
, afin de n’activer
que l’identification par clé (l’utilisateur serait adm
dans mon exemple) :
1
2
Match User <utilisateur>
PasswordAuthentication no
Et n’oubliez pas de recharger/redémarrer sshd, en utilisant
service sshd reload
sur Debian, ou plus généralement systemctl reload sshd
avec systemd.
Mais sachez qu’il y a une autre technique qui peut être plus pratique.
La première marche sur toutes les distributions GNU/Linux récentes,
est utilisée par Gitlab, et ne requiert “que” de ré-engendrer le fichier
~/.ssh/authorized_keys
à chaque mise à jour de clé publique.
Mais la seconde technique, celle que je m’apprête à vous présenter, dépend
d’un mécanisme qui n’est disponible et utilisable
qu’à partir d’OpenSSH 6.9, et que des distributions répandues et encore
maintenues utilisent une version plus ancienne d’OpenSSH, comme Debian Jessie
par exemple (l’oldstable
de Debian au moment où j’écris cet article),
qui utilise OpenSSH 6.8.
Cette seconde technique est utilisée par l’AUR d’Archlinux, mais tout projet
qui vise à être utilisable sur toutes les distributions GNU/Linux
répandues et maintenues devraient dépendre de la première technique.
Cette technique repose sur AuthorizedKeysCommand
. Cette option de
configuration est présente depuis OpenSSH 6.2, et vous permet de générer
un fichier authorized_keys
à chaque fois qu’un utilisateur se connecte,
avec un utilitaire de votre choix. Cependant, cette option est trop gourmande
à grande échelle, puisque cela signifie qu’il faut regénérer des milliers de
lignes à chaque fois qu’un utilisateur se connecte ; ne générer le fichier
que lorsque c’est nécessaire est une solution bien plus sage.
Cependant, en 6.9, une fonctionnalité très pratique a été implémentée :
le fait d’utiliser des tokens dans AuthorizedKeysCommand
!
Par exemple, vous pouvez définir la valeur de l’option à
/opt/ssh-update/auth "%t" "%k"
, et chaque fois qu’un utilisateur se
connecte, le type de la clé et la clé encodée en base64 est envoyée à
votre utilitaire d’identification, qui ne produit alors que les lignes
d’authorized_keys
appropriés ! Ceci signifie que vous n’avez plus besoin de
générer statiquement le fichier ~/.ssh/authorized_keys
, puisqu’un
équivalent sera engendré à chaque connexion !
Pour configurer ceci, nous n’avons qu’à mettre à jour le bloc précédent :
1
2
3
4
Match User <utilisateur>
PasswordAuthentication no
AuthorizedKeysCommand /opt/ssh-update/auth "%t" "%k"
AuthorizedKeysCommandUser <utilisateur>
Voici un exemple d’/opt/ssh-update/auth
, codé en Python :
1
2
3
4
5
6
7
#!/usr/bin/env python3
import sys
cake_key = "..."
if sys.argv[1] != "ssh-rsa" or sys.argv[2] != cake_key:
exit(1)
print('command="/opt/ssh-update/shell cake" ssh-rsa %s cake@thing'%cake_key)
Et voici un exemple d’/opt/ssh-update/shell
, tout autant codé en Python :
1
2
3
4
5
6
7
8
#!/usr/bin/env python3
import os, sys
print("Hello, %s!"%sys.argv[1])
if not 'SSH_ORIGINAL_COMMAND' in os.environ:
print("No command?")
else:
print("Your command was: %s"%os.environ['SSH_ORIGINAL_COMMAND'])
Bien entendu, vous pouvez faire beaucoup de choses avec ceci :
- faire un utilitaire qui génère statiquement
~/.ssh/authorized_keys
à partir d’une base de données (si vous utilisez la première technique) ; - récupérer le pseudonyme de l’utilisateur à partir d’une clé publique en faisant appel à une base de données (si vous utilisez la seconde technique) ;
- implémenter les commandes SSH pour git
git-receive-pack
etgit-upload-pack
; - la seule limite est votre imagination !