[Apache2] Authentification LDAP et groupes dynamiques

Voici le problème. J’ai un serveur SVN propulsé par un serveur Web Apache2.2. Mes utilisateurs sont stocké sur un annuaire LDAP. J’authentifie donc mes utilisateurs via un le plugin Mod_authnz_ldap. Tout ceci fonctionne assez simplement.

Pour des questions de sécurités j’ai aussi créé un group svn_user dans mon annuaire LDAP contenant les utilisateurs ayant le droit de s’authentifier sur mon serveur SVN. Pour ceci le plugin mod_authnz_ldap propose une clause require ldap-group qui répond à ce problème.

J’ai donc possibilité d’authentifier mes utilisateurs et de vérifier leur appartenance à un groupe.

Le problème arrive quand nous voulons gérer des autorisations différentes suivant les projets SVN et les groupes auquel appartiennent les utilisateurs.

En gros pour un projet X j’ai un group svn_x qui contient les utilisateurs Jean, Pierre et Paul, j’ai aussi un projet Y et un groupe svn_y contenant les utilisateurs Jack et Toto. Je souhaite donc géré mes autorisations en fonction de ces groupes.

J’ai tout d’abord pensé à utiliser le mod_authnz_ldap avec des variable d’environnement créé à partir d’une regex et du mod_rewrite sauf que ceci n’est pas possible car le module mod_authnz_ldap ne permet l’utilisation des variables d’environnement. Je me suis donc tourné vers l’utilisation du module mod_authnz_external qui permet de créer ses propres scripts d’authentification. C’est ce que nous allons détailler ici.

Installation du mod_authnz_external

Nous allons donc installer le module d’authentification. Un simple aptitude aurait pu faire l’affaire mais ceci ne serait pas assez drôle !

En effet le module n’est pas disponible dans les dépôts principaux de Debian. Nous devons donc utiliser les backport et si possible le plus proprement possible !!

Commençons par l’ajout des sources dans le gestionnaire de paquet apt.

# vi /etc/apt/sources.list

et ajouter la ligne suivante

deb http://backports.debian.org/debian-backports lenny-backports main

Nous allons aussi modifier le fichier /etc/apt/preferences et ajouter une règle de gestion de priorité (ceci nous permettra de bénéficier des mises à jours du paquet).

# vi /etc/apt/preferences

Et ajoutons cet règle

Package: *
Pin: release a=lenny-backports
Pin-Priority: 200

Ceci étant fait nous allons mettre à jour notre base de données apt et installer le module

# aptitude update

Prendre : 11 http://ftp.fr.debian.org lenny/main Sources [2310kB]
8630ko téléchargés en 12s (680ko/s)
Lecture des listes de paquets… Fait

# aptitude -t lenny-backports install libapache2-mod-authnz-external

l’option -t nous permet de spécifier l’utilisation du dépôt backport pour l’installation.

L’installation étant effectué nous allons activer le module et redémarrer apache.

# a2enmod authnz_external
Enabling module authnz_external.
Run ‘/etc/init.d/apache2 restart’ to activate new configuration!

# /etc/init.d/apache2 restart
Restarting web server: apache2 … waiting .

Voilà notre module est installé ! Passons à sa configuration !

Configuration du module

Nous allons maintenant configurer le module pour dire à apache d’utiliser un script d’authentification externe que nous détaillerons dans la prochaine section. Je ne détaillerai pas ici la configuration Dav svn, ceci n’étant pas le but de ce billet.

Nous allons modifier notre virtual host svn.exemple.com dans ce but. Comme rien ne vaut un exemple, en voici un.

<VirtualHost *:80>
ServerAdmin admin@exemple.com
ServerName svn.exemple.com

DAV svn
SVNParentPath   /var/svn

# Déclaration de notre script d’authentification externe
DefineExternalAuth auth_ldap pipe /etc/apache2/auth_external/auth_ldap.pl

<Location />
Options Indexes FollowSymLinks MultiViews
AllowOverride None
Order allow,deny
allow from all

# Type d’authentification
AuthType Basic
AuthName « Authentification qui va bien »
# Utilisation du module d’authentification external
AuthBasicProvider external
# Utilisation de notre script déclaré plus haut
AuthExternal auth_ldap
Require valid-user
</Location>
</VirtualHost>

Ceci n’est pas très compliqué nous spécifions que nous voulons utiliser le module external installé plus haut avec la clause AuthBasicProvider. Plus spécifiquement au module external nous déclarons un un script d’authentification avec la clause DefineExternalAuth que nous nommons auth_ldap. Les deux paramètres suivant sont tout d’abord le mode de communication entre le module et le script et le chemin du script. Nous utilisons ici le mode pipe qui nous renvoie les identifiants de l’utilisateur sur l’entrée standard du script. La dernière clause est AuthExternal qui nous permet de spécifier le script d’authentification à utiliser.

Les possibilités de configuration sont bien plus large que cela mais nous ne les détaillerons pas ici. Voyons maintenant notre script d’authentification qui reste tout de même le coeur de notre solution.

Création du script d’authentification

Sans plus attendre voici notre script d’authentification. L’explication suivra.

#!/usr/bin/perl -Tw
# Romain THERRAT <romain42@gmail.com>, 2010-01-25
# include LDAP perl module
use Net::LDAP;

# LDAP Connection configuration
my $ldap_host = « ldap.exemple.com »;
my $ldap_port = 389;
my $ldap_version = 3;
# Bind credentials for LDAP connection
my $ldap_bind_user_base = « ou=users,dc=exemple,dc=com »;
my $ldap_bind_user_attribute = « cn »;
# LDAP informations on LDAP groups containing’s users
my $ldap_svn_group_base = « ou=svn,ou=groups,dc=exemple,dc=com »;
my $ldap_svn_group_attribute = « memberUid »;

# Get the name of this program
$prog= join ‘ ‘,$0,@ARGV;
$logprefix='[‘ . scalar localtime() . ‘] ‘ . $prog;

# Get the user name
$user= <STDIN>;
chomp $user;

# Get the password name
$pass= <STDIN>;
chomp $pass;

# Get project name regarding URI
my $uri= $ENV{URI};
($project) = ($uri =~ //([^/]*)/ );

# Connect to LDAP directory
my $ldap = Net::LDAP->new($ldap_host, port => $ldap_port, version => $ldap_version)
or die « $logprefix: Unable to connect ($@) »;

# Bind to user
$mesg = $ldap->bind( « $ldap_bind_user_attribute=$user,$ldap_bind_user_base », password => $pass);
if($mesg->is_error) {
print STDERR « $logprefix: User $user: authentication failure ( $mesg->error ) »;
exit 1;
}

# Search SVN LDAP group
$mesg = $ldap->search(
base   => « $ldap_svn_group_base »,
scope  => ‘sub’,
filter => « (cn=$project) »
);
$mesg->code && die $mesg->error;

# Only one group is allowed
if($mesg->count != 1) {
print STDERR « $logprefix: Multiple group retreive »;
exit 1;
}
$group = $mesg->entry(0);

# check if user is member of group
if ( grep { $_ eq $user} $group->get_value( $ldap_svn_group_attribute )) {
exit 0;
} else {
exit 1;
}

Tout d’abord je tiens à préciser que ce script n’est qu’une présentation et qu’il n’est absolument pas optimisé. En effet le module Authnz_exernal réalise de nombreux appelle au script d’authentification aussi il est nécessaire pour une utilisation en production de, au minimum, mettre en place un système de cache.

Résumons ce script. Nous créons tout d’abord quelque variables que nous utiliserons dans le script. Nous décomposons ensuite l’URI demandé par le client, celle ci se trouve dans une variable d’environnement nommé de façon assez originale : URI. Dans notre cas le premier dossier de l’URI est celui du projet et aussi celui du groupe. Nous authentifions notre utilisateur sur l’annuaire LDAP pour enfin vérifier son appartenance au groupe correspondant au projet. Attention il est aussi nécessaire d’installer la librairie Net:LDAP de perl qui se trouve dans le paquet libnet-ldap-perl sous Debian.

En ce qui concerne les valeurs de retour un zéro signifie que l’authentification s’est bien déroulé, 1 l’inverse.

Voici qui clos ce billet qui je l’espère vous aura inspiré et vous aidera dans vos configurations 😉 !

Si vous avez apprécié cet article, pensé à laissé un commentaire ou vous abonner au flux RSS feed.

2 thoughts on “[Apache2] Authentification LDAP et groupes dynamiques

  1. Bonjour

    Pour compléter ce que vous avez dit sur les nombreuses requetes :

    j’ai mis en production pour ma société ce mod_auth_external (pour la même problématique de groupes imbriqués). Pour info je suis passé par PHP.

    Nous rencontrons d’énormes lenteurs… (déjà que c’était pas hyper rapide avant avec le mod_ldap, là c’est catastrophique)…

    Un systeme de cache est plus que le minimum en effet…
    J’ai choisi d’utiliser SQLite pour faire un cache de session (les sessions php ne fonctionnant pas) sauf que c’est toujours très lent.

    Après analyse, lorsque par exemple on demande à récupérer les logs d’un dépôt, plusieurs milliers de requêtes sont faites par le client SVN, et à chaque fois apache revérifie l’authentification… (donc 2 appels, un pour le mot de passe, et un autre pour le groupe).
    il faut optimiser au maximum le script, le moindre millième de seconde gagné est important… (j’ai réussi à descende à 2ms (login+group)… à chaque requête)

  2. Désolé pour le double commentaire, mais j’ai fini par trouver une option de mod_svn très intéressante car elle réduit considérablement le nombre de requêtes réalisées, il suffit d’ajouter dans chaque location de type DAV SVN l’instruction suivante :
    SVNPathAuthz Off

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *