Article publié dans Linux Magazine 67, décembre 2004.
Copyright © 2004 - Jérôme Fenal.
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.
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
.
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 (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.
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.
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.
Nous avons donc trois endroits où modifier la configuration :
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...
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).
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.
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
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
.
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.
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}).
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 :
ethers
:
Spécifique à Solaris, cette carte permet d'associer une adresse IP à une adresse MAC, à travers le mécanisme RARP (Reverse-ARP), protocole toujours utilisé par les machines Sun pour obtenir une adresse IP au boot.
bootparams
:
Toujours spécifique à Solaris (ou tout du moins pour faire booter des machines Sun). Permet de spécifier les sources TFTP pour le noyau ainsi que, entre autres, la source NFS à monter comme racine.
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.
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
.
http://meltin.net/people/martin/publications/polythenepam.pdf
http://www.kernel.org/pub/linux/libs/pam/Linux-PAM-html/pam-4.html
http://www.int-evry.fr/mci/user/procacci/ldap/Ldap_int010.html#htoc87
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.
Copyright © Les Mongueurs de Perl, 2001-2011
pour le site.
Les auteurs conservent le copyright de leurs articles.