J’essaie lorsque c’est possible d’éviter la sur-multiplication des mots de passe à gérer. Dans un environnement principalement Microsoft, Active Directory permet de résoudre assez bien ce problème, c’est cool. Un annuaire pour les unir tous (et dans les ténèbres les lier).
Qu’en est-il des distributions Linux ? Je gère un petit nombre de serveurs sous Linux, pour lesquels l’intérêt de les intégrer dans un domaine Active Directory est proche du néant (sans parler du foutoir à mettre en place côté Linux). En effet, il n’y a pas de GPO, l’authentification par mot de passe est désactivé sur ces serveurs, et seul quelques personnes doivent pouvoir y avoir accès et de manière limitée.
Je me suis donc orienté vers une solution où la base utilisateurs des serveurs Linux reste locale (via /etc/passwd et /etc/shadow donc) mais en cherchant à centraliser la clé SSH de ces utilisateurs avec Active Directory.
David Gwynne (voir source) s’est déjà penché sur ce problème et a exhibé un paramètre contenu dans OpenSSH permettant de contrôler l’étape d’authentification via clé publique de manière très flexible, ce paramètre acceptant un script en entrée.
Le principe est alors très simple :
- un utilisateur tente de se connecter au serveur linux en SSH
- le serveur SSH récupère le nom d’utilisateur et exécute le script indiqué par le paramètre AuthorizedKeysCommand
- par mesure de sécurité, OpenSSH propose d’exécuter ce script sous une autre identité, ce qui est fortement préférable vu qu’OpenSSH est root
- ce script va se connecter à un serveur LDAP à l’aide d’identifiants dédié
- il va récupérer l’objet utilisateur correspondant au nom d’utilisateur, et s’il existe retourner sa ou ses clés publique SSH
L’intérêt consiste à éviter de devoir manipuler le fichier /home/{user}/.ssh/authorized_keys2 sur chaque serveur pour chaque utilisateur, la création d’un accès se faisant alors en deux temps :
- création du compte sur le serveur linux avec la commande useradd
- si ce n’est pas déjà fait, ajout de sa clé dans Active Directory
Mise en place de la solution
- Dans le fichier /etc/ssh/sshd_config, on ajoute/modifie les lignes suivantes:
AuthorizedKeysCommand /etc/ssh/authorized_keys.pl
AuthorizedKeysCommandUser _sshkeys
# si ce n'est pas déjà fait, ci-dessous les paramètres à modifier pour désactiver l'identification par mot de passe
PasswordAuthentication no
ChallengeResponseAuthentication no
2. Contenu du fichier /etc/ssh/authorized_keys.pl. Il faudra renseigner le nom du domaine, le nom d’utilisateur et mot de passe d’un compte pour se connecter à l’annuaire.
#!/usr/bin/perl -w
use strict;
use Net::DNS;
use Net::LDAP;
use Net::LDAP::Constant qw(LDAP_SUCCESS LDAP_INVALID_CREDENTIALS);
use Net::LDAP::Util qw(escape_filter_value);
sub ad_bind($$$)
{
my $domain = shift;
my $user = shift;
my $pass = shift;
my $res = Net::DNS::Resolver->new;
my $query;
my $answer;
my $ldap_error;
$query = $res->query("_ldap._tcp." . $domain, 'SRV');
if (!$query) {
die "unable to query SRV records for $domain";
}
my @answers = $query->answer;
foreach $answer (@answers) {
# next if ($answer->port != 389)
my $ldap = new Net::LDAP($answer->target, timeout => 2);
$ldap_error = $@;
next unless ($ldap);
my $mesg = $ldap->bind(sprintf("%s@%s", $user, uc($domain)),
password => $pass);
return ($ldap) if ($mesg->code == LDAP_SUCCESS);
if ($mesg->code == LDAP_INVALID_CREDENTIALS) {
return (undef, $mesg->error);
}
$ldap_error = $mesg->error;
}
return (undef, $ldap_error);
}
if (scalar @ARGV != 1) {
die "username not specified";
}
my $username = $ARGV[0];
my $ad_filter = sprintf('(&(sAMAccountName=%s)(objectClass=user))', escape_filter_value($username));
my %d = (
'kveer.fr' => {
'user' => 'sshkeys',
'pass' => 'ton_mot_de_passe',
'base' => 'DC=kveer,DC=fr',
'filter' => $ad_filter
}
);
foreach my $domain (keys %d) {
my $param = $d{$domain};
my ($ds, $err) = ad_bind($domain, $param->{'user'}, $param->{'pass'});
next unless ($ds);
my $mesg = $ds->search(
base => $param->{'base'},
filter => $param->{'filter'},
attrs => ['altSecurityIdentities']
);
next unless ($mesg->code == LDAP_SUCCESS);
for (my $i = 0; $i < $mesg->count; $i++) {
my $entry = $mesg->entry($i);
my @ids = $entry->get_value('altSecurityIdentities');
foreach my $id (@ids) {
my ($type, $value) = split(/:/, $id, 2);
print "$value\n" if ($type eq 'SSHKey');
}
}
}
3. puis enfin quelques commandes pour terminer la configuration (à adapter en fonction de la distribution)
#!/bin/bash
USERNAME=_sshkeys
SCRIPT_FILE=/etc/ssh/authorized_keys.pl
useradd -s /sbin/nologin -d /var/empty/ $USERNAME
chmod 750 "$SCRIPT_FILE"
chown root:"$USERNAME" "$SCRIPT_FILE"
Ajouter la clé SSH
Pour chaque utilisateur devant avoir un accès nunux, il suffit de déclarer leur clé publique au sein d’Active Directory, dans l’attribut altSecurityIdentities. L’avantage de cet attribut est qu’il existe déjà, et fonctionne à l’aide d’un préfixe sur les valeurs, de sorte à ce qu’il puisse être utilisé par un acteur différent (ici OpenSSH) sans impacter l’usage initial qui en est fait de cet attribut. Ci-dessous à travers l’outil de gestion graphiques de l’annuaire dsa.msc.
Remarques
- Le script n’est pas parfait et peut être améliorer. Il ne vérifie pas par exemple si le compte en question est désactivé ou non.
- Le script utilise l’attribut altSecurityIdentities. On aurait très bien pu créer un attribut dédié, mais cette solution a l’avantage d’éviter de modifier manuellement le schéma AD.
Source