[couverture de Linux Magazine 67]

Authentification Linux avec LDAP

Article publié dans Linux Magazine 67, décembre 2004.

Copyright © 2004 - Jérôme Fenal.

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

Chapeau de l'article

Après avoir vu comment et l'intérêt d'utiliser LDAP pour y stocker des informations sur des personnes, il paraît intéressant de le mettre en pratique, après toute cette théorie.

Puisque nous avons maintenant le moyen de sécuriser les flux, ainsi que celui d'éviter un arrêt de service grâce à la réplication, nous allons voir comment faire qu'un Linux authentifie des comptes avec un annuaire LDAP. Cela impliquera LDAP, bien sûr, mais aussi et surtout les PAM.

Présentation des composants nécessaires

L'utilisation de LDAP pour la gestion des comptes Unix requiert plusieurs composants : PAM, NSS et un schéma LDAP adéquat, reconnu par pam_ldap et nss_ldap.

PAM

Les PAM (Pluggable Authentication Modules, modules d'authentification enfichables) permettent de paramétrer à l'envi les procédures et sources d'authentification, mais aussi d'offrir des services supplémentaires aux programmes qui savent les utiliser. C'est un premier pas pour la mise en place d'un SSO (Single Sign-On, authentification unique en français) sur un système Unix/Linux, ses applications, et même au-delà.

Le paramétrage des greffons se fait dans les fichiers /etc/pam.conf ou /etc/pam.d/*, suivant les distributions. Pour plus d'informations, référez-vous à la page de manuel pam(8), à [2], ou à ce qui suit.

NSS

NSS (Name Service Switch, commutateur de services de nommages), permet de fournir à Unix non des services d'authentification, mais des services de correspondances entre noms, de toutes sortes (noms de machines et noms d'utilisateurs intelligibles par l'homme, par exemple), et les identifiants de ces mêmes objets pour la machine (adresses IP et uid/gid dans notre exemple).

NSS, comme PAM, est composé de greffons sous formes de bibliothèques dynamiques, Le paramétrage est plus limité, non unifié entre eux (au contraire de PAM), et se fait principalement dans /etc/nsswitch.conf.

Schéma LDAP adéquat

Le schéma nécessaire est défini dans nis.schema. Il regroupe toutes les classes de base nécessaires à la construction d'un service NIS (ex-YP) basé sur LDAP. Nous allons principalement utiliser les classes posixAccount, posixGroup, voire pour les aventureux shadowAccount. Les classes posixAccount et shadowAccount sont de type AUXILIARY, posixGroup est elle STRUCTURAL. Nous allons donc devoir adosser les comptes à d'autres classes, le mieux étant d'utiliser la classe inetOrgPerson qui est la plus complète dans le standard.

Peuplement

Le peuplement de la base peut se faire à partir de fichiers LDIF, mais par la suite, pour une plus grande facilité, à l'aide d'un outil interactif. Dans le cas où la source de la base de compte n'est pas votre annuaire, mais un annuaire tiers, une base de données (celle de la paye, par exemple), etc., vous aurez d'autres besoins que Perl pourra couvrir, ce que nous verrons dans le prochain article.

Il est souvent pris comme norme de construire des OU à partir de la racine de votre arbre pour séparer les différentes classes d'objets : comptes, groupes, et autres. Cela permet tout autant de séparer les objets afin de permettre à l'homme de s'y retrouver, mais aussi à la machine comme nous le verrons dans la configuration du client LDAP de PAM et NSS, où les comptes utilisateurs seront dans une OU, les groupes dans une autre, etc.

De plus, dans un environnement hétérogène, il est souvent moins facile de configurer les emplacements des différentes données, comme avec Solaris, par exemple (la documentation n'abonde pas vraiment, pas de recours au source possible), où l'OU pour les comptes est People, celle pour les groupes est Group, et non Groups comme certaines distributions Linux le préconfigurent.

Nous allons donc utiliser les OU suivantes : People pour les comptes, Group pour les groupes, et nous pouvons prévoir l'OU Computers pour les comptes d'ordinateur dans un domaine NT4 géré par Samba et LDAP.

D'où le pourquoi du fichier LDIF imprimé dans ces pages et le pourquoi des OU et de leur nom spécifique.

Gestion des comptes Unix

Configuration du client LDAP du système

Nous avons donc trois endroits où modifier la configuration :

Configuration PAM

PAM sert juste à l'authentification d'un compte. On peut très bien imaginer authentifier un utilisateur déclaré dans la base système (/etc/passwd et consorts) sur un annuaire LDAP. Mais on peut aussi authentifier les utilisateurs déclarés dans LDAP, qui seront vus par le système grâce à NSS.

La configuration de PAM se fait relativement facilement si vous utilisez un système d'origine Red Hat ou Mandrake. Ceux-ci utilisent pam_stack(8) pour centraliser la configuration sur un pseudo-service : system-auth.

Le plus simple ensuite est d'utiliser l'utilitaire authconfig (Red Hat) ou drakauth (Mandrake) pour configurer l'utilisation de LDAP. Cependant, la configuration n'est pas forcément optimale, et il convient de voir en détail ce qui est fait.

Sur une Mandrake 10.0, le fichier /etc/pam.d/system-auth est à l'origine le suivant :

  #%PAM-1.0
  auth        required      /lib/security/pam_env.so
  auth        sufficient    /lib/security/pam_unix.so likeauth nullok
  auth        required      /lib/security/pam_deny.so
  account     required      /lib/security/pam_unix.so
  password    required      /lib/security/pam_cracklib.so retry=3 minlen=2  dcredit=0  ucredit=0 
  password    sufficient    /lib/security/pam_unix.so nullok use_authtok md5 shadow
  password    required      /lib/security/pam_deny.so
  session     required      /lib/security/pam_limits.so
  session     required      /lib/security/pam_unix.so

Après utilisation de drakauth, on obtient :

  #%PAM-1.0
  auth        required      /lib/security/pam_env.so
  auth        sufficient    /lib/security/pam_unix.so likeauth nullok use_first_pass
  auth        sufficient    /lib/security/pam_ldap.so
  auth        required      /lib/security/pam_deny.so
  account     sufficient    /lib/security/pam_unix.so use_first_pass
  account     sufficient    /lib/security/pam_ldap.so
  account     required      /lib/security/pam_deny.so
  password    required      /lib/security/pam_cracklib.so retry=3 minlen=2  dcredit=0  ucredit=0 
  password    sufficient    /lib/security/pam_unix.so nullok use_authtok md5 shadow
  password    sufficient    /lib/security/pam_ldap.so
  password    required      /lib/security/pam_deny.so
  session     required      /lib/security/pam_limits.so
  session     required      /lib/security/pam_unix.so

Le seul problème, c'est que ça ne marche pas... Et c'est pareil (encore qu'un peu moins pire) sur une Red Hat, de la 7 à la 9.

Le problème (chez Red Hat) est qu'en l'absence de serveur LDAP au bout, un compte local ne pourra se connecter. Et au pire (Mandrake), les options sont telles qu'aucun compte local ne pourra se connecter.

Il faut donc modifier le fichier en question afin d'avoir :

  auth    required     /lib/security/pam_env.so
  auth    sufficient   /lib/security/pam_unix.so likeauth nullok
  auth    sufficient   /lib/security/pam_ldap.so use_first_pass
  auth    required     /lib/security/pam_deny.so
  account required     /lib/security/pam_unix.so
  account sufficient   /lib/security/pam_localuser.so
  account [default=bad
          success=ok \
          user_unknown=ignore \
          service_err=ignore \
          system_err=ignore \
          authinfo_unavail=ignore] \
                       /lib/security/pam_ldap.so
  account  required    /lib/security/pam_deny.so
  password required    /lib/security/pam_cracklib.so retry=3 minlen=2 \
                                                        dcredit=0  ucredit=0
  password sufficient  /lib/security/pam_unix.so nullok use_authtok md5 shadow
  password sufficient  /lib/security/pam_ldap.so
  password required    /lib/security/pam_deny.so

  session  required    /lib/security/pam_limits.so
  session  required    /lib/security/pam_unix.so

Notez le déplacement de l'option use_first_pass, l'ajout des « machins franchement bizarres entre crochets » (cf. [1]) pour la ligne account de pam_ldap.

Mais, au moins, avec cette configuration, je peux me connecter en root sur une machine à tout moment, qu'un serveur LDAP soit lancé ou non...

Client LDAP de PAM et NSS

De plus, le fichier /etc/ldap.conf est modifié par drakauth ou authconfig pour obtenir la racine de vos données, ainsi que les paramètres nécessaires pour aller chercher les informations.

  host ldap1.example.com
  base dc=example,dc=com
  ldap_version 3
  port 389
  scope one
  pam_filter objectclass=posixaccount
  pam_login_attribute uid
  pam_member_attribute gid
  pam_password crypt
  nss_base_passwd ou=People,dc=example,dc=com?sub
  nss_base_shadow ou=People,dc=example,dc=com?sub
  nss_base_group ou=Group,dc=example,dc=com?sub

Rappelez-vous que nous avons dans notre LDIF des sous-OU, et qu'il faut donc bien spécifier sur les trois sources passwd, shadow et group de faire des requêtes LDAP avec un scope sub. Ce peut être spécifié globalement en modifiant la ligne scope one ci-dessus en scope sub.

De plus, une autre option intéressante est de faire gérer les mots de passe par OpenLDAP, si la compatibilité avec les mots de passe DES crypt classiques d'Unix n'est pas nécessaire pour d'autres applications qui utilisent l'attribut userPassword. Dans ce cas, modifier la ligne pam_password crypt en pam_password exop.

Pour plus d'informations, lisez donc ce fichier, qui doit être installé avec pam_ldap ou nss_ldap, les commentaires y sont fournis et de qualité. Point de détail ayant son importance : ce fichier est séparé en deux fichiers différents dans Debian, /etc/libnss-ldap.conf et /etc/pam_ldap.conf.

La configuration ci-dessus est relativement simple, fonctionne, mais a un inconvénient : elle n'est pas sécurisée. Il faut donc dire à notre client LDAP PAM de joindre le serveur LDAP via SSL/TLS. Pour ce faire, ajoutez les lignes suivantes dans /etc/ldap.conf :

  ssl start_tls
  tls_cacertfile /etc/ssl/MyCAcert.pem
  tls_checkpeer yes

Si vous installez un client ailleurs que sur un des serveurs LDAP, n'oubliez pas de recopier le certificat de l'autorité de certification au bon endroit (/etc/ssl/MyCAcert.pem par exemple).

Configuration NSS

La configuration est relativement simple. Il suffit de spécifier ldap en source supplémentaire pour les données dans le fichier /etc/nsswitch.conf. Les sources nécessaires à ce que l'on veut faire sont essentiellement passwd, shadow et group. Mais l'on pourra aussi ajouter, si on désire remplacer un domaine NIS, les sources pour la correspondance nom de machine / adresse IP (carte hosts), pour services, protocols, rpc, netmasks, etc., pardon, etc.

Le contenu LDAP de ces autres sources de données est détaillé un peu plus loin dans cet article.

Vérification du bon fonctionnement

Pour vérifier que tout fonctionne bien, abusez des commandes id et getent.

N'hésitez pas non plus à les utiliser avec strace ou truss (systèmes BSD et Solaris), de façon à savoir ce qui peut bien se passer. Pour info, c'est ainsi que j'ai pu vérifier que les flux LDAP étaient bien chiffrés en jouant sur l'option ssl start_tls de /etc/ldap.conf :

  # strace -o ~/idok.log id book
  uid=1003(book) gid=513 groupes=513,10001(stout),10002(tea)
  # perl -pi -e 's/^(tls_cacertfile)/#$1/' /etc/ldap.conf
  # strace -o ~/idfail.log id book
  id: book: usager inexistant.

L'examen et la comparaison des fichiers vous aidera à déterminer la source du problème. Ne pas négliger non plus ce qui arrive dans syslog, fichier /var/log/messages.

PS : n'oubliez de remettre l'option modifiée dans /etc/ldap.conf :

  # perl -pi -e 's/^#(tls_cacertfile.*)/$1/' /etc/ldap.conf

Quelques trucs pouvant aider

Impossible de se connecter en tant que root

Vous avez fait une erreur dans votre configuration PAM ou dans le fichier /etc/ldap.conf des PAM et/ou NSS ? Pas de panique, vous pouvez peut-être vous en sortir sans devoir redémarrer en ajoutant à votre annuaire l'objet suivant :

  dn: uid=root,ou=People,dc=example,dc=com
  cn: root
  objectClass: top
  objectClass: account
  objectClass: posixAccount
  uid: root
  uidNumber: 0
  gidNumber: 0
  userPassword:: eMNoen1gVuZerulrbsFdre9PVAJRSIROSL3sS4LESS09
  gecos: Charlie &
  homeDirectory: /root
  loginShell: /bin/bash

N'oubliez pas de changer le mot de passe avant l'insertion dans l'annuaire.

Cela ne fonctionne bien évidemment que s'il n'a pas été spécifié d'uid minimal définissable par LDAP. Sinon, un reboot en single-user voire un CD de secours s'impose...

Dans ces cas-là, toujous conserver un shell sous root dans un coin (sur un autre bureau KDE par exemple ou sur un terminal console) pour pouvoir modifier la configuration entre deux tests qui font échouer un su - root ou un ssh root@localhost.

Utilisation de sous-OU dans ou=People ou ou=Group

Vous avez une organisation de vos comptes, groupes, et autres informations gérées par votre annuaire, mais certains comptes ne sont pas vus par Linux ? Vous avez peut-être oublié de modifier le filtre par défaut de :

  nss_base_passwd ou=People,dc=example,dc=com

à

  nss_base_passwd ou=People,dc=example,dc=com?sub

dans /etc/ldap.conf. Ça va mieux en le répétant, car comme sur le sujet de l'emplacement des directives pour la réplication LDAP, je me suis fait piéger plus d'une fois.

Utilisation de LDAP pour les autres bases de données systèmes

Quand vous regardez dans le fichier /etc/nsswitch.conf, d'autres bases de données systèmes existent telles que services, automount, hosts, aliases, etc. De la même façon que pour les comptes utilisateurs, il est aussi possible d'utiliser un annuaire LDAP pour gérer de façon centralisée ces informations. Et cela a un intérêt certain : remplacer NIS (anciennement YP, à ne pas totalement confondre avec son grand frère NIS+) par LDAP. Quel intérêt ? Passer d'un protocole essentiellement tourné vers le monde Unix (YP a été conçu, comme NIS+, par Sun), à un protocole normalisé, et plus seulement standard. Cela permet ensuite de s'interfacer avec d'autres familles de systèmes d'exploitation, même si certains acteurs du marché dérivent quelque peu à leur propre sauce des protocoles normalisés. De plus, l'utilisation de LDAP sur SSL/TLS est beaucoup plus sécurisée (NIS ne chiffre pas son trafic) et aussi plus facilement filtrable à travers un pare-feu que NIS qui est basé sur les RPC (qui peuvent ouvrir des ports de façon dynamique).

Rapidement, nous allons voir comment se nomment les objets (ce qui peut avoir son importance pour par exemple les données de /etc/services), et ce que l'on doit mettre dans les objets, et où doit-on les mettre pour éviter de mélanger torchons et serviettes.

hosts

Dans Solaris, où nous avons toujours le même problème pour spécifier où chercher telle classe d'objets, les entrées hosts sont situées dans une OU ou=Hosts, dc=example, dc=com.

La classe d'objet est ipHost, et en voici un exemple :

  dn: cn=monpc.example.com,ou=Hosts,dc=example,dc=com
  objectclass: top
  objectclass: device
  objectclass: ipHost
  cn: monpc.example.com
  ipHostNumber: 192.168.1.1

Une autre manière de nommer l'objet est d'inclure en plus du CN l'attribut ipHostNumber dans le DN. Cela permet d'avoir plusieurs entrées par nom de machine, de façon à séparer

  dn: cn=monpc+ipHostNumber=192.168.1.1,ou=Hosts,dc=example,dc=com
  objectclass: top
  objectclass: device
  objectclass: ipHost
  cn: monpc
  ipHostNumber: 192.168.1.1
  description: Mon PC à moi

Ces mêmes objets qui seront utilisés par le biais du sous-système NSS peut aussi être présenté par un serveur DNS, via quelques modifications à Bind. Une autre possibilité est d'extraire les informations de LDAP pour créer les cartes DNS à fournir à Bind. Une dernière possibilité est de dire à OpenLDAP d'aller chercher les informations de ce type directement sur un serveur DNS, avec l'inconvénient de ne plus stocker l'information dans LDAP.

Je serais pour ma part plutôt partisan de garder à chaque outil la gestion de sa propre base de données, et donc d'avoir la source de ce type de données dans LDAP, avec des extractions régulières pour alimenter un serveur DNS. Une solution pour ce faire est disponible par le biais du projet ldap2dns[4].

services

De la même façon que pour le nommage des machines, la base services utilise la combinaison nom de service et protocole IP. L'OU est nommée ou=Services.

  dn: cn=name+ipServiceProtocol=udp,ou=Services,dc=example,dc=com
  cn: name
  cn: nameserver
  ipserviceprotocol: udp
  ipserviceport: 42
  objectclass: top
  objectclass: ipService

protocols

La carte protocols fait la correspondance entre le nom d'un protocole de la couche IP tel que UDP, TCP ou ICMP et son numéro.

  dn: cn=udp,ou=Protocols,dc=example,dc=com
  cn: udp
  ipprotocolnumber: 17
  description: user datagram protocol
  objectclass: top
  objectclass: ipProtocol

rpc

La carte rpc fait la même chose pour les RPC :

  dn: cn=portmapper,ou=Rpc,dc=example,dc=com
  cn: portmapper
  cn: portmap
  cn: sunrpc
  cn: rpcbind
  objectclass: top
  objectclass: oncRpc
  oncrpcnumber: 100000

networks (pour tout le monde) et netmasks (pour Solaris)

Les cartes networks et netmasks sont un peu particulières. Elles ne sont pas utilisées par Linux, mais le sont par Solaris. En particulier netmasks qui est le seul moyen d'utiliser des adresses IP CIDR (i.e. avec des masques autres que 8, 16 ou 24 bits, respectivement les classes A, B, et C de l'espace d'adressage IP).

  dn: ipNetworkNumber=192.168.1.0,ou=Networks,dc=example,dc=com
  cn: monreseau-192.168.1.0
  description: Mon réseau à la maison
  ipnetmasknumber: 255.255.255.128
  ipnetworknumber: 192.168.1.0
  objectclass: ipNetwork
  objectclass: top

netgroup

Les cartes netgroup permettent de restreindre l'accès de certains utilisateurs NIS/YP à certaines machines. La notation habituelle est reprise telle quelle avec LDAP :

  cn=monnetgroup,ou=Netgroup,dc=example,dc=com
  objectClass=nisNetgroup
  objectClass=top
  cn=monnetgroup
  nisNetgroupTriple=(ymmv..example.com,-,)
  nisNetgroupTriple=(ymmv.,-,)
  memberNisNetgroup=manchots

automount

Cette base de données regroupe une ou plusieurs cartes. A minima, nous aurons auto.master, qui peut préciser directement des points de montages, ou faire appel à d'autres cartes pour des montages indirects, comme auto.home.

Pour imiter la répartition en fichier auto.{master,home,misc,etc.}, des sous-OU seront créées pour organiser le tout. Voyons deux exemples simples de fichiers auto.master et auto.home :

  # auto.master
  /home auto.home

La transcription commence par la création de l'OU autofs facultative :

  dn: ou=autofs,dc=example,dc=com
  objectClass: top
  objectClass: organizationalUnit
  ou: autofs

Cette OU est facultative car elle n'est là que pour ajouter un peu d'organisation. Ensuite, nous créons la carte auto.master, dont l'identifiant est OU, mais n'est par pour autant une unité organisationnelle. En effet, dans la classe d'objet automountMap, le seul attribut autorisé est OU, mais la classe descend directement de top.

  dn: ou=auto.master,ou=autofs,dc=example,dc=com
  objectClass: top
  objectClass: automountMap
  ou: auto.master

Nous créons ensuite le lien entre une racine de points de montage pour les comptes utilisateurs (/home) et la carte associée :

  dn: cn=/home,ou=auto.master,ou=autofs,dc=example,dc=com
  objectClass: top
  objectClass: automount
  cn: /home
  automountinformation: ldap:ou=auto.home,ou=autofs,dc=example,dc=com

Tout ce qui est à droite de la clé (le premier mot de chaque ligne est la clé de la ligne, voyez la commande ypcat -k passwd.byname) dans la carte NIS doit se retrouver dans l'attribut automountInformation. Dans le cas d'une carte indirecte, on spécifie le type de source (ldap:, qui ressemble à un protocole d'URL mais ne l'est point). Ensuite, de façon facultative, le serveur où trouver la carte, puis le nom distingué (DN) de la carte. En l'absence de spécification du nom du serveur, le module NSS utilisera les serveurs qui lui sont spécifiés par le fichier de configuration LDAP qu'il utilise, à savoir /etc/ldap.conf. C'est par le biais de ce mécanisme, qui supporte déjà de voir plusieurs serveurs LDAP différents, que se fera la redondance des serveurs. Cela permet de conserver les avantages de redondance qu'avait NIS (grâce à la spécification de plusieurs serveurs NIS distincts ou à une configuration broadcast).

Mais si vous spécifiez un serveur particulier, oubliez la redondance, sauf à avoir un mécanisme de type cluster haute-disponibilité (je précise, car un cluster n'est pas une ferme de calcul) ou distribution de charge avec plusieurs serveurs derrière une adresse IP unique.

La seconde carte ne pose pas de souci :

  dn: ou=auto.home,ou=autofs,dc=example,dc=com
  objectClass: top
  objectClass: automountMap
  ou: auto.home

De même que le joker pour attraper tous les comptes et les monter sur /home/compte.

  * -fstype=nfs,hard,intr,nodev,nosuid 192.168.1.1:/export/home/&
  book 192.168.1.2:/export/home/book

qui devient :

  dn: cn=/,ou=auto.home,ou=autofs,dc=example,dc=com
  objectClass: top
  objectClass: automountMap
  cn: /
  automountinformation: -fstype=nfs,hard,intr,nodev,nosuid 192.168.1.1:/export/home/&
  
  dn: cn=book,ou=auto.home,ou=autofs,dc=example,dc=com
  objectClass: top
  objectClass: automountMap
  cn: book
  automountinformation: 192.168.1.2:/export/home/book

Notez que l'étoile (joker au sens LDAP) de la carte NIS devient un slash dans le LDIF. On retrouve de même que pour la spécification de la carte indirecte tout ce qu'on peut trouver sur une ligne de fichier d'automontage spécifiant un montage NFS : options de montage et source du montage.

Quelques exemples de cartes sont aussi fournies avec autofs, n'hésitez pas à y jeter un œil (dans /usr/share/doc/autofs-4* par exemple, fichiers ldap-automount-auto.{{in,}direct,master}).

Autres bases

D'autres bases plus ou moins spécifiques à certains systèmes d'exploitation ou certaines fonctionnalités peuvent aussi être intégrées à LDAP. Citons pour exemple :

Pour ces deux bases de données, consultez la documentation fournie par Sun pour avoir la façon d'écrire les informations dans l'annuaire.

Conclusion

Nous en avons terminé avec les utilisations pratiques de LDAP en environnement système Linux. J'espère vous avoir fait découvrir bon nombre de facettes des annuaires, sachant que ceux-ci peuvent être utiles à d'autres applications. Citons par exemple, toujours dans le domaine de l'authentification, la protection de l'accès à un serveur web ou à des applications, la validation des adresses courriels pour un MTA comme sendmail ou postfix, le stockage de données pour Samba de façon à émuler des contrôleurs de domaine, etc. On peut aussi imaginer de construire des schémas particuliers permettant le recensement des configurations matérielles et logicielles de votre parc.

Le prochain et dernier article de la présente série vous parlera (enfin !) de la façon d'utiliser un annuaire avec Perl, au travers de Net::LDAP.

Liens

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 (SunOS 3) ou Unix-like depuis un peu plus longtemps, membre de Paris.pm.

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

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