Lerm-IT

Blog traitant de technologies informatiques. Logiciel libre, AdminSys, DevOps et GNU/Linux !

25 Jan 2011

[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és 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érer mes autorisations en fonction de ces groupes.

J’ai tout d’abord pensé à utiliser le mod_authnz_ldap avec des variables 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. Une simple commande 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 cette 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ée 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 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 larges 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 😉 !