[couverture de Linux Magazine 65]

Introduction à LDAP et déploiment de OpenLDAP

Article publié dans Linux Magazine 65, octobre 2004.

Copyright © 2004 - Jérôme Fenal.

[+ del.icio.us] [+ Developers Zone] [+ Bookmarks.fr] [Digg this] [+ My Yahoo!]

Chapeau de l'article

Une fois n'est pas coutume, il y aura peu de Perl ce mois-ci chez les Mongueurs. En effet, nous allons bientôt parler du module Net::LDAP, mais avant de ce faire, une bonne introduction s'impose, ce que nous ferons ce mois-ci, avec l'installation de OpenLDAP. Le mois prochain, nous verrons une utilisation avancée de OpenLDAP, avec l'écriture de schéma et la mise en haute disponibilité via la réplication. Ensuite, un des contextes dans lequel l'utilisation d'un annuaire est intéressante est la centralisation des comptes utilisateurs Unix/Linux. Et c'est là que nous verrons en détail l'utilisation de Net::LDAP.

Mais en attendant, je vous souhaite une bonne lecture à la découverte de LDAP.

Un annuaire, quesako ?

LDAP est un protocole d'accès à un annuaire X.500, normalisé à la fin des années 1990 ; X.500 étant une norme monstrueuse pour la gestion d'annuaire sur des réseaux ISO (vous connaissez les couches ISO ?, ou encore les protocoles réseaux ISO comme DECnet Phase V ou plus proche de nous ISO/DSA de Bull).

LDAP, donc, dont l'acronyme est Lightweight Directory Access Protocol, a très rapidement évolué de l'accès à un annuaire X.500 à la gestion complète d'un annuaire pour les environnements non X.500. C'est ainsi que sont sortis les annuaires de Netscape et que tout le monde a suivi. Dans le camp du libre, OpenLDAP est un projet qui a démarré en août 1998, sur la base de la mise en œuvre de LDAP réalisée par l'Université du Michigan.

Un annuaire, pour quoi faire ?

Un annuaire permet de stocker des données légèrement typées, organisées selon des classes particulières et présentées dans un arbre. L'exemple le plus commun, dont il tire son nom est l'annuaire de personnes. Mais on peut y stocker bien d'autres choses : des comptes Unix, des données personnelles (ce qu'on peut trouver dans un carnet d'adresses, mais aussi les photos des personnes, etc.), des données sur des objets plus ou moins abstraits, comme des données d'identification, des certificats (la distribution de listes de révocation de certificat peut se faire par LDAP), un parc matériel, et plus généralement tout ce qui peut être nommé et à qui on peut attacher des informations. Voyons comment procéder après une petite introduction, et comment Perl va pouvoir nous aider.

Différences entre un annuaire et un SGBD

Le principe de l'annuaire n'est pas à confondre avec une base de données relationnelle dont l'objectif est différent. Un annuaire est d'abord conçu pour recevoir beaucoup plus de requêtes en lecture qu'en écriture. Une base de données a d'autres objectifs comme un typage fort et une rapidité d'accès en lecture comme en écriture.

Un annuaire permet donc de stocker des objets, auxquels on peut attacher des attributs (qui sont typés), organisés dans un arbre. Il existe une représentation normalisée des données des objets, le format LDIF.

Un autre avantage des annuaires sur les bases de données est la facilité de mise en œuvre de la réplication. En gros, à chaque modification dans l'annuaire (qui sont minimes par rapport aux accès en lecture, rappelons-le), ces modifications sont journalisées et reportées dans les annuaires secondaires, ou esclaves.

Sur ce dernier point, si l'on fait un parallèle avec les DNS, le fonctionnement est un peu différent : il n'y a pas la notion de transfert de zones (on prend toute la base de données pour l'envoyer à son collègue), mais juste la notification d'un changement (avec la matière du changement). Nous verrons plus loin que la mise en œuvre de la réplication est relativement simple, mais nécessite une initialisation manuelle. Un script peut faciliter la chose.

Un annuaire, définitions et principes de fonctionnement

Format de données LDIF

LDIF : LDAP Data Interchange Format. C'est le format de fichier permettant le chargement et la mise à jour de données dans un annuaire LDAP.

Par exemple :

  # debut
  dn: dc=example,dc=com
  dc: example
  objectClass: top
  objectClass: domain
  
  dn: cn=Manager, dc=example,dc=com
  objectClass: organizationalRole
  cn: Manager
  
  # fin

Noter que le séparateur entre deux objets est une ligne vide.

Nous verrons plus loin ce que sont les classes d'objets (objectClass). Disons simplement pour le moment qu'une classe définit les différents attributs qu'un objet peut ou doit posséder.

Le DN est le distinguished name, à savoir le nom de l'objet dans l'annuaire. C'est ce nom qui permet de retrouver de façon unique un objet dans l'arbre des objets. Un objet possède donc toujours un DN, qui reprend son RDN (relative dn). Ici, le RDN de dc=example, dc=com est dc=example et cn=Manager pour le second enregistrement. À noter que le RDN doit être spécifié tant dans l'objet que dans le DN.

Notez aussi que des espaces peuvent apparaître dans un DN, il ne sont pas significatifs autour des virgules, et que si un DN est unique sur l'annuaire, il n'en est pas de même pour le RDN. Sauf chez Microsoft dans Active Directory Server. Mais aussi dans POSIX où il vaut mieux éviter d'avoir deux comptes ayant le même UID (au sens LDAP) dans l'arbre des comptes. Le tout est de savoir ce que l'on fait.

Classes d'objets

Quelques classes d'objets :

Les classes d'objets permettent donc de regrouper les objets de même type, avec un plus par rapport à une base de données : un objet peut appartenir à plusieurs classes en même temps. Ce qui permet de fusionner, autour du même nom, des données de person, de posixAccount (cette personne a un compte Unix), de sambaSamAccount (elle a aussi un compte Samba), etc.

Un point à garder en mémoire est que les classes peuvent être de plusieurs types : ABSTRACT, STRUCTURAL et AUXILIARY. Dans la pratique, on utilisera essentiellement les deux dernières. Ce typage va nous permettre de définir des héritages entre classes.

Le type ABSTRACT permet juste de définir une classe dont doivent dériver d'autres classes. Une classe de type ABSTRACT ne peut avoir aucune instance d'objet dans l'annuaire.

Le type STRUCTURAL permet de définir une classe qui dérive d'une autre, la classe racine étant la classe top (elle-même de type ABSTRACT). Le point important ici est que dans l'instanciation d'un objet (dans son écriture LDIF, par exemple), on doit spécifier l'entière hiérarchie des classes. Comme ici dans notre exemple, où le domain descend de top. Ce mécanisme est peut-être un peu contraignant (surtout si vous avez à migrer un annuaire OpenLDAP 2.0 vers un 2.1 ou un 2.2, car la version 2.0 ne vous obligeait pas à spécifier toute la hiérarchie d'objets), mais permet une chose importante : ne pas mélanger torchons et serviettes. Imaginez en effet que nous ayons à définir des utilisateurs, mais aussi des groupes. Cela ferait mauvais effet de ne serait-ce que pouvoir mélanger les genres, et d'avoir un objet qui soit à la fois un groupe et un utilisateur. Mais comme les classes afférentes aux groupes et utilisateurs sont structurelles, elles ne peuvent donc être mélangées.

Il existe le dernier type de classe, AUXILIARY, qui permet de s'affranchir de ce mécanisme d'héritage, et d'attribuer des données complémentaires (« auxiliaires » dirait-on en bon franglais) à un objet.

Pour en finir avec les classes, sachez que bon nombre de classes sont définies en standard dans un certain nombre de RFC (comme les RFC 2252 et 2256). Si l'on fait un parallèle (pas si anodin que ça) avec SNMP, on peut considérer les classes comme des MIB SNMP (cf. Linux Magazine France n°43 d'octobre 2002), que l'on peut créer soi-même, ou dont on peut utiliser les MIB standards comme la MIB-II (RFC 1213).

Les attributs et les types

Les attributs sont donc définissables pour un objet en fonction de la classe à laquelle l'objet appartient.

Le typage est relativement faible contrairement à un SGBD. Ou plutôt, il sert un autre dessein : l'interopérabilité. Les types sont en effet spécifiques aux annuaires, et permettent de stocker essentiellement des données alphanumériques, voire des données binaires. Mais il n'est pas complètement dans le rôle de l'annuaire de vérifier ces types. Sur un SGBD, si.

Il existe une cinquantaine de types de données, certains génériques, comme « DirectoryString » (chaîne de caractères UTF-8), « INTEGER » ou « Binary String », d'autres limités à des usages spécifiques, comme « DN » (Distinguished Name, le nom d'un objet dans un annuaire) ou « JPEG » (image au format JPEG).

Tous les types sont définis dans la RFC 2252. Nous verrons plus loin, dans l'écriture d'un schéma, que ces syntaxes (le nom des types LDAP) sont référencées par des OID (oui, les mêmes OID ASN-1 que pour SNMP, mais dans un espace de nommage différent).

Schéma

L'endroit où se définissent attributs et classes d'objets, qu'ils soient standards ou de votre fait, s'appelle un schéma.

Si vous avez un OpenLDAP à votre disposition, via les paquetages standards de votre distribution Linux, vous pouvez y jeter un coup d'œil. Ils sont dans /etc/openldap/schema/ ou dans /usr/share/openldap/schema/. Au pire, un locate schema vous dira où ils se trouvent.

Voici un exemple de définition de classe (au format OpenLDAP), tiré du schéma Samba :

  objectclass ( 1.3.6.1.4.1.7165.2.2.6 NAME 'sambaSamAccount' SUP top AUXILIARY
   DESC 'Samba 3.0 Auxilary SAM Account'
   MUST ( uid $ sambaSID )
   MAY  ( cn $ sambaLMPassword $ sambaNTPassword $ sambaPwdLastSet $
    sambaLogonTime $ sambaLogoffTime $ sambaKickoffTime $
    sambaPwdCanChange $ sambaPwdMustChange $ sambaAcctFlags $
    displayName $ sambaHomePath $ sambaHomeDrive $ sambaLogonScript $
    sambaProfilePath $ description $ sambaUserWorkstations $
    sambaPrimaryGroupSID $ sambaDomainName $ sambaMungedDial))

Les attributs vus ici viennent donc soit du schéma sambaSamAccount (ceux préfixés par « samba »), les autres de schémas autres comme posixAccount ou person.

Voici, toujours tiré du même schéma, la définition de quelques attributs :

  ##
  ## Account flags in string format ([UWDX     ])
  ##
  attributetype ( 1.3.6.1.4.1.7165.2.1.26 NAME 'sambaAcctFlags'
   DESC 'Account Flags'
   EQUALITY caseIgnoreIA5Match
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{16} SINGLE-VALUE )

  ##
  ## Password timestamps & policies
  ##
  attributetype ( 1.3.6.1.4.1.7165.2.1.27 NAME 'sambaPwdLastSet'
   DESC 'Timestamp of the last password update'
   EQUALITY integerMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )

On remarquera la définition de la syntaxe, qui se fait par le biais d'un OID. Encore une fois, la correspondance entre OID et syntaxe associée se trouve dans la RFC 2252.

Liens entre données

Contrairement aux bases de données, où l'on utilise régulièrement des identifiants uniques d'enregistrements (comme des numéros en auto-incrémentation), l'identifiant d'un objet dans LDAP est son nom.

Et ce nom est ce qu'on appelle un DN (Distinguished Name, nom unique, rappelons-le - et pas nom distingué). Il est forcément unique sur l'annuaire, car il comprend un des attributs obligatoires de la classe principale de l'objet (comme CN=Fenal ou UID=jerome), et surtout toute la liste des OU (cf. un peu plus bas) et autres organisations (O) et composants de nom de domaine (dc=com) jusqu'à la racine de l'arbre des données.

Le DN d'un objet peut donc être mis dans un attribut d'un autre objet, signifiant ainsi le lien.

Organisation des données

Les données sont organisées dans un arbre, qui part de la racine de l'annuaire, dite RootDSE, jusqu'à tout objet considéré dans l'annuaire.

On peut organiser un annuaire de différentes façons.

Tout d'abord, on utilise rarement (comprendre : jamais) la racine RootDSE comme véritable racine de son propre annuaire. On commence toujours à un niveau en dessous défini selon son bon plaisir. On peut utiliser, comme dans l'exemple ci-dessus, une racine façon domaine DNS (example.com), mais on pourrait tout aussi bien imaginer une organisation (la société Example) en racine. Cela donnerait :

  dn: o=Example
  o: example
  objectClass: top
  objectClass: organization

Une discussion a eu lieu l'an dernier sur la liste LDAP sur la bonne manière de faire. Vous pouvez la trouver là : http://www.openldap.org/lists/openldap-software/200311/msg00338.html.

En gros, pourvu que vous soyez cohérent avec vous même, n'importe quelle racine pourra convenir. Il sera toujours possible de vous mettre d'accord, voire de ré-écrire et ré-importer un annuaire tiers pour l'interfacer avec le vôtre.

Autorisations d'accès aux données de l'annuaire

LDAP supporte deux méthodes d'accès aux données d'un annuaire : le mode authentifié, et le mode anonyme.

Le mode anonyme peut a minima permettre de se connecter en tant qu'utilisateur authentifié, voire, si les ACL (Listes de Contrôle d'Accès) de l'annuaire le permettent, de lire un contenu minimal dans l'annuaire.

Le mode authentifié, comme dans tout autre système gérant des données, permettra par ailleurs de spécifier des droits particuliers (lecture, écriture, authentification) à un utilisateur ou à un groupe d'utilisateurs.

Insertion de données LDIF

Pour insérer les données définies plus haut, la simple commande suivante suffit :

  $ ldapadd -c -x -h localhost -D "cn=Manager,dc=example,dc=com" -W -f base.ldif

L'option -c permet de continuer sur des erreurs (un enregistrement défini dans le fichier LDIF est déjà présent dans l'annuaire). L'option -x permet de s'affranchir de l'authentification SASL, et n'avoir qu'à spécifier le mot de passe. -D indique le DN du compte qui se connecte. -W fera que ldapadd demandera le mot de passe du Manager. Enfin, -f indiquera le nom du fichier LDIF à insérer.

  $ ldapadd -c -x -h localhost -D "cn=Manager,dc=example,dc=com" -W -f base.ldif
  Enter LDAP Password:
  adding new entry "dc=example,dc=com"

  adding new entry "cn=Manager, dc=example,dc=com"

  $ ldapadd -c -x -h localhost -D "cn=Manager,dc=example,dc=com" -W -f ou.ldif
  Enter LDAP Password:
  adding new entry "ou=People,dc=example,dc=com"

  adding new entry "ou=Paris,ou=People,dc=example,dc=com"

  adding new entry "ou=London,ou=People,dc=example,dc=com"

  adding new entry "ou=Bath,ou=People,dc=example,dc=com"

  adding new entry "ou=Mongueurs,ou=People,dc=example,dc=com"

  adding new entry "ou=Group,dc=example,dc=com"

  adding new entry "ou=Computers,dc=example,dc=com"

Le fichier ou.ldif contient les entrées suivantes :

  dn: ou=People,dc=example,dc=com
  ou: People
  objectclass: top
  objectclass: organizationalUnit
  
  dn: ou=Paris,ou=People,dc=example,dc=com
  ou: Paris
  objectclass: top
  objectclass: organizationalUnit
  
  dn: ou=London,ou=People,dc=example,dc=com
  ou: London
  objectclass: top
  objectclass: organizationalUnit
  
  dn: ou=Bath,ou=People,dc=example,dc=com
  ou: Bath
  objectclass: top
  objectclass: organizationalUnit
  
  dn: ou=Mongueurs,ou=People,dc=example,dc=com
  ou: Mongueurs
  objectclass: top
  objectclass: organizationalUnit
  
  dn: ou=Group,dc=example,dc=com
  ou: Group
  objectclass: top
  objectclass: organizationalUnit
  
  dn: ou=Computers,dc=example,dc=com
  ou: Computers
  objectclass: top
  objectclass: organizationalUnit

L'arbre des données (DIT) résultant est le suivant :

                        dc=com
                           |
                      dc=example
                      /    |    \____
                     /     |         \
                    /      |          \
            ou=People     ou=Group   ou=Computers
              /  / \  \
             /   |  |  \
            /    |  |   \
           /     |  |    \
          /      |  |     \____
         /      /     \        \
        /      /       \        \
  ou=Paris  ou=London  ou=Bath  ou=Mongueurs

Le fichier contenant les comptes contient à peu près ce qui suit :

  #!/usr/bin/perl -w
  use strict;
  my %lo = (
      ymmv     => 'Paris',  grinder  => 'Paris',
      book     => 'Paris',  sniper   => 'Paris',
      nicholas => 'London', leon     => 'Bath'
  );

  my $uid=1000;
  foreach (keys %lo) {
      my $pass=crypt("yo $_", ('.','/',0..9,'A'..'Z','a'..'z')[rand 64][rand 64]);
      my $city=$lo{$_};
      print << "EOF";
  dn: uid=$_,ou=$city,ou=People, dc=example,dc=com
  objectClass: top
  objectClass: Person
  objectClass: inetOrgPerson
  objectClass: posixAccount
  objectClass: shadowAccount
  userPassword: $pass
  uid: $_
  uidNumber: $uid
  cn: $_
  loginShell: /bin/bash
  gidNumber: 513
  gecos: Charlie $_
  description: System User
  homeDirectory: /home/$_
  sn: $_

  EOF
      $uid++;
  }

Rien de bien transcendant ici, hormis la construction du sel, pour laquelle on indexe un tableau (constitué des caractères '.', '/', des chiffres de 0 à 9, ainsi des lettres de A à Z en majuscules et minuscules) avec le résultat de la fonction rand qui fournira un résultat variant de 0 à 63. Or le tableau comporte bien 2 + 10 + 26 + 26 soit 64 valeurs. Et on en prend une tranche de deux.

Ce qui donne à l'insertion :

  $ ldapadd -c -x -W -D cn=Manager,dc=example,dc=com  -H ldaps://ldap1.example.com/ -f comptes.ldif
  Enter LDAP Password:
  adding new entry "uid=grinder,ou=Paris,ou=People, dc=example,dc=com"
  adding new entry "uid=nicholas,ou=London,ou=People, dc=example,dc=com"
  adding new entry "uid=sniper,ou=Paris,ou=People, dc=example,dc=com"
  adding new entry "uid=leon,ou=Bath,ou=People, dc=example,dc=com"
  adding new entry "uid=ymmv,ou=Paris,ou=People, dc=example,dc=com"
  adding new entry "uid=book,ou=Paris,ou=People, dc=example,dc=com"

Les lignes vides ont été enlevées pour plus de concision.

Pour les groupes, pas besoin de générer automatiquement, ça peut encore se faire à la main :

  dn: cn=stout,ou=Group,dc=example,dc=com
  cn: stout
  gidNumber: 10001
  memberUid: book
  memberUid: sniper
  memberUid: grinder
  description: Buveurs de stout
  objectClass: posixGroup
  
  dn: cn=tea,ou=Group,dc=example,dc=com
  cn: tea
  gidNumber: 10002
  memberUid: nicholas
  memberUid: leon
  memberUid: ymmv
  memberUid: book
  description: Buveurs de the
  objectClass: posixGroup
  
  dn: cn=cristal,ou=Group,dc=example,dc=com
  cn: cristal
  gidNumber: 10003
  memberUid: nicholas
  memberUid: ymmv
  description: Buveurs de blonde allemande
  objectClass: posixGroup

Interrogation de l'annuaire

Un autre avantage de LDAP est la flexibilité de son mode d'interrogation.

On a parlé ci-dessus de l'arbre contenant les données (DIT), il va nous permettre de limiter la recherche.

La recherche dans un annuaire, en plus de la spécification d'attributs, nous permet de spécifier deux paramètres particuliers :

  1. La base de la recherche :

    Le point de départ, dans l'arbre des informations de l'annuaire (DIT), de la recherche, ce qui nous permet de limiter une recherche à une sous-branche de l'arbre ;

  2. Le scope de recherche :

    La façon dont la recherche va pouvoir descendre dans les branches de l'arbre. Pour cela, plusieurs possibilités sont offertes par LDAP, base, sub et one :

    1. scope=base :

      Permet de faire une recherche dans la base de l'annuaire, et retourne toutes les entrées trouvées. Ce type de requête n'est pas utile en temps normal (elle est utilisée par exemple pour obtenir des informations sur le schéma) ;

    2. scope=sub :

      Permet de faire une recherche à partir du nœud spécifié par la base, dans le nœud courant, mais aussi dans tous les nœuds en dessous. On pourrait le penser comme une recherche récursive dans toutes les branches du sous-arbre prenant racine à la base considérée ;

    3. scope=one :

      Permet de faire une recherche à partir du nœud spécifié par la base, uniquement sur le niveau courant. Cela évite de descendre visiter les OU s'il n'y en a pas besoin.

Les filtres

En plus des limites « spatiales » que l'on peut imposer à notre recherche, on peut aussi (fort heureusement) spécifier des données à rechercher. Cela se fait dans les filtres LDAP. Contrairement aux habitudes en matière de développement en langage de haut-niveau, la syntaxe des filtres utilise des opérateurs préfixés, ce afin d'éviter toute ambiguïté syntaxique. Après ces précisions, gardez à l'esprit que les filtres sont donc relativement simples, et le mieux est de le voir sur un exemple :

  (&((|(objectClass=person)(objectClass=posixAccount))(uid=jerome)))

Si l'on éclate ce filtre sur plusieurs lignes, cela donne :

  (
  & (
      (
      | (objectClass=person)
        (objectClass=posixAccount)
      )
      (uid=jerome)
    )
  )

Soit : trouve-moi les objets de classe person ou posixAccount dont l'attribut uid est « jerome » (attention, ici, l'attribut uid représente l'identifiant de l'utilisateur, le nom de son compte, et non l'uid au sens POSIX qui sera uidNumber).

Ces filtres peuvent donc être utilisés dans vos requêtes sur l'annuaire. Rappelons une fois encore que le but d'un annuaire est d'être interrogé. Les requêtes peuvent être faites manuellement, via la commande ldapsearch, ou par vos applications, qu'elles soient en Perl ou en C. La représentation éclatée d'un filtre présentée ci-dessus n'a pour but que de vous montrer sa construction. La forme uniligne sera la seule utilisable dans les faits.

Exemples d'interrogation

OpenLDAP : Un annuaire libre

OpenLDAP est un annuaire libre mettant en œuvre le protocole LDAP, sous une licence qui semble équivalente à la licence BSD révisée (sans clause de publicité).

Il est dérivé du serveur LDAP de l'Université du Michigan, et a largement évolué depuis.

Installation

Je ne m'étendrai pas sur les arcanes de la compilation d'OpenLDAP, car elle pourrait faire l'objet d'un article à part entière. Sachez simplement qu'il vous faudra les logiciels suivants :

Sleepycat Berkeley Database version 4

Les dernières versions (2.1 et 2.2 à l'heure où j'écris) utilisent cette base de données en support (backend). Elle a l'immense avantage d'être transactionnelle, à savoir qu'elle gère un mécanisme de journalisation des mises à jour des données, et permet grâce à lui une haute-disponibilité, contrairement à d'autres mécanismes comme le support ldbm.

À noter que cette bibliothèque est souvent intégrée à votre distribution GNU/Linux, mais pas toujours dans la version requise par OpenLDAP. Et c'est là que les ennuis commencent car il vous faudra la compiler spécifiquement pour pouvoir compiler ensuite OpenLDAP.

OpenSSL

LDAP est un des protocoles, comme HTTP, à pouvoir être encapsulé nativement dans SSL/TLS. Pour sécuriser les échanges entre clients et annuaire, il vous faudra donc compiler l'annuaire avec OpenSSL. Le nom du protocole façon URL devient ldaps au lieu de ldap pour la version non SSL.

Utilisez plutôt les paquetages disponibles (apt-get install, urpmi ou encore yum install, dans un ordre alphabétique) dans votre distribution ou utilisez ceux de Jehan Procaccia de l'INT (cf. § Liens).

Capacités de OpenLDAP 2.1

OpenLDAP 2.1 est la première version à peu près propre de OpenLDAP (entendons nous bien, ce n'est pas péjoratif, le développement d'un tel logiciel est conséquent). Ce terme propre s'applique essentiellement au maintien des contraintes d'intégrité entre types de classes d'objets (pour les classes dites STRUCTURAL, voir plus loin).

OpenLDAP permet d'utiliser plusieurs sources de données, en plus des classiques bases internes, à présenter via une interface LDAP :

Les processus

Le serveur LDAP est slapd.

Le co-serveur de réplication est slurpd.

Configuration par défaut

Dans ce qui suit, nous allons détailler quelque peu la configuration par défaut de slapd sur la Mandrake 10 de mon portable personnel, où je mettrai d'abord le contenu du fichier, histoire de vous laisser un peu deviner, puis quelques explications sur les lignes qui précédent. Ça peut vous sembler bizarre, mais c'est comme ça.

  include /usr/share/openldap/schema/core.schema
  include /usr/share/openldap/schema/cosine.schema
  include /usr/share/openldap/schema/corba.schema
  include /usr/share/openldap/schema/inetorgperson.schema
  include /usr/share/openldap/schema/java.schema
  include /usr/share/openldap/schema/krb5-kdc.schema
  include /usr/share/openldap/schema/kerberosobject.schema
  include /usr/share/openldap/schema/misc.schema
  include /usr/share/openldap/schema/nis.schema
  include /usr/share/openldap/schema/openldap.schema
  include /usr/share/openldap/schema/autofs.schema
  include /usr/share/openldap/schema/samba.schema
  include /usr/share/openldap/schema/kolab.schema
  include /etc/openldap/schema/local.schema

Ce sont les inclusions des schémas LDAP utilisables sur ce serveur. Il importe de définir ici ceux que vous voulez employer. Nous garderons ceux par défaut. Les unixiens reconnaîtront différentes sources de données distribuées Unix comme nis.schema et autofs.schema, les schémas standards LDAP étant dans core.schema, cosine.schema et inetorgperson.schema. Les autres (dont samba.schema et kerberosobject.schema) sont des schémas supplémentaires.

  include         /etc/openldap/slapd.access.conf

Ceci est juste une inclusion de définitions d'ACL complémentaires sur les objets de l'annuaire.

  pidfile         /var/run/ldap/slapd.pid
  argsfile        /var/run/ldap/slapd.args

De quoi savoir où le serveur slapd doit stocker le numéro du processus pour les scripts de démarrage et d'arrêt sachent faire leur travail, ainsi que les arguments qui ont été passés à slapd pour le lancer.

  modulepath      /usr/lib/openldap

L'emplacement des modules (bibliothèques dynamiques) utilisables par OpenLDAP, essentiellement les différents supports (backend).

  TLSCertificateFile      /etc/ssl/openldap/ldap.pem
  TLSCertificateKeyFile   /etc/ssl/openldap/ldap.pem
  TLSCACertificateFile    /etc/ssl/openldap/ldap.pem

Là où sont stockés les certificats SSL pour le protocole ldaps.

À partir du mot-clé database qui suit, la configuration ne vaut que pour la base considérée (ici, dc=example,dc=com). Cela permet de faire gérer plusieurs annuaires, pourvu qu'ils aient des racines différentes, en ajoutant une section database supplémentaire.

  database        bdb

Début de spécification d'une base de données, ainsi que son type. Toutes les directives qui suivent se rapportent uniquement à la base de données qui débute ainsi, jusqu'à la prochaine directive database ou la fin du fichier.

  suffix          "dc=example,dc=com"

Le positionnement dans l'arbre des informations qui seront gérées par cette base.

  rootdn          "cn=Manager,dc=example,dc=com"

Le nom du super-utilisateur pour cette base. Pour bien faire, il faut aussi l'insérer dans l'annuaire.

  rootpw          secret

Le mot de passe de ce monsieur. Ici, la chaîne est en clair (lisez : ne le faites jamais, même en protégeant le fichier /etc/openldap/slapd.conf), mais peut aussi être spécifiée avec un hash DES (ie. comme dans /etc/shadow : {crypt}ijFYNcSNctBYg, à peine plus acceptable que le texte pur ; l'idéal étant le SSHA, ou tout autre algorithme de chiffrement fort).

Le générateur de hash de mot de passe pour LDAP est slappasswd :

  $ /usr/sbin/slappasswd -h '{SSHA}' -s secret -v
  {SSHA}H1mqncWN0AUgHu15O5+ECKjulFRPn8LF
  $ /usr/sbin/slappasswd -h '{MD5}' -s secret -v
  {MD5}Xr4ilOzQ4PCOq3aQ0qbuaQ==

Les types de chiffrements sont fort bien expliqués dans la page de manuel slappasswd(8C). Rapidement, {MD5} et {SHA} sont ce qu'on pense qu'ils sont, {SMD5} et {SSHA} sont exactement les mêmes, mais avec une graine (seed), de façon à augmenter la complexité du décryptage par force brute. Pour mémoire, l'encodage DES de /etc/passwd fonctionne sur le même principe, les deux premiers caractères de la chaîne chiffrée correspondent à cette graine. Oh, j'oubliais, l'option -s permet de passer le mot de passe sur la ligne de commande, ce que vous éviterez (vous n'avez pas envie qu'on voie votre mot de passe avec la commande ps(1) ou dans l'historique des commandes).

Retour au fichier de configuration :

  directory       /var/lib/ldap

Le répertoire où seront stockées les données.

  index   objectClass,uid,uidNumber,gidNumber     eq
  index   cn,mail,surname,givenname               eq,subinitial

Les index à créer sur les attributs pour accélérer les recherches.

  loglevel 256

La définition de la verbosité de slapd, pour qu'il ne signifie que les connexions, les opérations et les résultats. La page de manuel de slapd.conf(5), section loglevel vous donnera les valeurs à additionner (ou inclusif plus exactement) pour avoir d'autres informations.

  access to attr=userPassword
    by self write
    by anonymous auth
    by dn="uid=root,ou=People,dc=example,dc=com" write
    by * none

  access to *
    by dn="uid=root,ou=People,dc=example,dc=com" write
    by * read

Les quelques ACL de base pour la mise à jour des mots de passe et la gestion des comptes POSIX.

À propos des ACL

La place manque quelque peu pour parler plus avant des ACL. Sachez cependant que les définitions en sont linéaires (on matche et on sort ou on continue jusqu'à la fin), donc que l'ordre de spécification de ces ACL est important.

La documentation, quoique succinte, est bien faite, et vous permettra de voir les différentes façon de faire. N'oubliez pas de tester vos ACL avant toute mise en production, surtout dans le cas d'un serveur d'authentification. Il ne s'agit pas de rendre publics des mots de passe, surtout après la tenue de la conférence Crypto 2004, ou encore la liste des employés ou adhérents de votre organisation pour un bon vieux spam...

Quelques clients LDAP graphiques

Les quelques clients proposés ici sont dignes d'intérêt, que ce soit pour ce qu'ils sont en mesure de proposer actuellement, ou pour les promesses qu'ils seront à même de satisfaire dans quelque temps.

La prochaine fois...

... nous parlerons des utilisations avancées de OpenLDAP, en particulier la mise en œuvre de SSL/TLS, la réplication et l'écriture d'un schéma.

Liens

Les sites web

Les listes de diffusions

Je vous donne ici le nom de la liste, ainsi que sa localisation dans l'excellent moteur d'archivage et de présentation en NNTP de lidies, j'ai nommé Gmane.org, ainsi que le site Web du gestionnaire de liste.

Jérôme Fenal

<jfenal@free.fr> et <jerome.fenal@logicacmg.com>.

Jérôme Fenal est utilisateur de GNU/Linux depuis 1994, de divers Unix ou Unix-like (Domain/OS) depuis un peu plus longtemps.

Membre depuis peu des Mongueurs de Perl parisiens, après des années (depuis la création ?) à m'être fait tanner par Philippe « BooK » Bruhat pour y venir.

Merci aux Mongueurs Marseillais, Lyonnais et Parisiens qui ont assuré la relecture de cet article.

Cet article a été écrit sous Linux, avec [g]vim, en pod (Plain Old Documentation).

[IE7, par Dean Edwards] [Validation du HTML] [Validation du CSS]