Retrouvez cet article dans : Linux Magazine 112
Le mois dernier, nous avons fait le tour d’un certain nombre d’opérations, de manipulations et de pratiques avec OpenLDAP. Comme précisé en fin d’article, LDAP est un vaste domaine et un livre de recettes concernant le sujet pourrait grossir sans fin jusqu’à dépasser la taille de plusieurs magazines. La preuve, il reste encore des choses à dire ce mois-ci pour compléter ce qui a déjà été détaillé.
|
1 |
Mise en place de vues utilisateur |
Beaucoup de clients LDAP sont très restrictifs sur la façon dont ils peuvent interroger un annuaire. En particulier, ceux des systèmes restreints embarqués sur des équipements réseau, sur des imprimantes, etc... Un Call Manager Cisco, par exemple, permet de synchroniser sa propre base d´utilisateur sur un annuaire LDAP, mais avec des contraintes assez pénibles. Comment donc gérer l´intégration de ces matériels, sans pour autant réorganiser la structure de l´annuaire pour se plier à leurs contraintes ?
Une solution consiste à déporter le travail de manipulation des données vers le serveur, et à répondre différemment en fonction du client qui l´interroge : c´est le principe des vues, bien connu des utilisateurs de SGBD. Les recettes suivantes montrent comment réaliser ce genre de chose avec OpenLDAP, pour filtrer des entrées d´une part, pour échanger des attributs d´autre part.
|
1.1 |
Filtrage d´entrées |
Le problème le plus courant est celui du client ne permettant pas de filtrer des entrées indésirables. Le Call Manager Cisco, par exemple, importe avidement tous les utilisateurs situés sous sa base de recherche, sans permettre de filtrer par exemple ceux qui sont dépourvus d´un numéro de téléphone. Il faut donc identifier ce client lorsqu´il interroge l´annuaire, puis effectuer ce filtrage à sa place.
Puisqu´il s´agit d´exclure des entrées de la réponse, les ACL sont parfaitement adaptées. Il suffit en effet d´interdire à ce client l´accès à ces entrées, en appliquant le filtre nécessaire pour les sélectionner, pour toute demande émanant de l´adresse IP ou de l´identifiant de ce client :
|
# filtrage des entrées visibles par le call manager aux seuls utilisateurs # pourvus d’un numéro de téléphone access to dn.subtree="ou=users,dc=domain,dc=tld" filter=(!(telephoneNumber=*)) by dn="cn=callmanager,ou=roles,dc=domain,dc=tld" none by * break # filtrage des entrées visibles par le photocopieur aux seuls membres # des ressources humaines ou des affaires financières access to dn.subtree="ou=users,dc=domain,dc=tld" filter=(!(|(ou=srh)(ou=saf))) by anonymous peername.ip=192.168.0.1 none by * break |
Il est alors facile de vérifier la différence de résultats entre les deux requêtes, en fonction de l´identifiant utilisé :
|
[guillaume@oberkampf ~]$ ldapsearch -x -LLL objectClass=person telephoneNumber dn: uid=foo,ou=users,dc=domain,dc=tld telephoneNumber: 1234567890 dn: uid=bar,ou=users,dc=domain,dc=tld [guillaume@oberkampf ~]$ ldapsearch -x -LLL -D cn=callmanager,ou=roles,dc=domain,dc=tld -w s3cr3t objectClass=person telephoneNumber dn: uid=foo,ou=users,dc=domain,dc=tld telephoneNumber: 1234567890 |
Attention lors de la mise en place de telles ACL à l´impact potentiel sur les autres accès. L´utilisation d´ACL strictement nominatives, comme détaillée dans la recette 2.3 (voir GLMF n°111), est fortement conseillée pour limiter les risques.
Pour plus d´information, consultez :
● la page de manuel slapd.access(5) ;
● la page contrôle d´accès du Guide d´administration d´OpenLDAP.
|
1.2 |
Échange d´attributs |
Un problème plus complexe se pose lorsque le client ne permet pas de choisir les attributs utilisés. Le Call Manager Cisco, toujours lui, impose l´utilisation de l´attribut telephoneNumber pour stocker le numéro de téléphone interne, alors que d´autres applications accèdent également à cet attribut, et s´attendent à y voir figuré le numéro externe. Il ne s´agit donc plus de filtrer cet attribut, mais d´utiliser en fait deux attributs différents, et à présenter l´un ou l´autre sous un même nom en fonction du client.
Les ACL ne suffisent plus. Il faut mettre en place un échange (mapping) d´attributs, au sein d´un contexte virtuel, à l´aide du module de stockage relay. L´utilisation de ce contexte virtuel suffira à identifier le client.
L´exemple suivant montre la mise en place d´un contexte virtuel ou=telephony,dc=domain,dc=tld, répliquant la branche utilisateur ou=users,dc=domain,dc=tld, et dans lequel l´attribut telephoneNumber original est remplacé par l´attribut homePhone. Le fait que ce contexte virtuel appartienne au suffixe dc=domain,dc=tld impose de déclarer la base virtuelle avant la base réelle dans la configuration de slapd :
|
# chargement du module moduleload back_relay.la # mise en place d’un contexte virtuel, dupliquant la branche utilisateur database relay suffix ou=telephony,dc=domain,dc=tld overlay rwm rwm-suffixmassage ou=users,dc=domain,dc=tld # substitution de l’attribut telephoneNumber rwm-map attribute telephoneNumber homePhone rwm-map attribute telephoneNumber |
Cette fois-ci, c´est le contexte de recherche utilisé qui fait la différence de résultats entre les deux requêtes :
|
[guillaume@oberkampf ~]$ ldapsearch -x -LLL -b ou=users,dc=domain,dc=tld objectClass=person telephoneNumber,homePhone dn: uid=foo,ou=users,dc=domain,dc=tld telephoneNumber: 1234567890 homePhone: 7890 [guillaume@oberkampf ~]$ ldapsearch -x -LLL -b ou=telephony,dc=domain,dc=tld objectClass=person telephoneNumber,homePhone dn: uid=foo,ou=telephony,dc=domain,dc=tld telephoneNumber: 7890 |
Cette stratégie permet également de mettre en œuvre le même filtrage des entrées à partir d´ACL que la précédente, en simplifiant l´identification du client, puisque le contexte dédié le fait implicitement. De plus, les ACL exotiques sont ainsi isolées des ACL de la base principale, réduisant les possibilités d´interactions non désirées :
|
access to dn.subtree="ou=telephony,dc=domain,dc=tld" filter=(!(telephoneNumber=*)) by * none |
Attention également à masquer les attributs sensibles, c´est-à-dire notamment les mots de passe, dans cette nouvelle branche, sous peine d´ouvrir une faille de sécurité remarquable... Le plus simple est de ne garder dans le contexte virtuel que les attributs nécessaires, et d´ignorer les autres :
|
rwm-map attribute telephoneNumber homePhone rwm-map attribute telephoneNumber rwm-map attribute * |
Néanmoins, un bug dans les versions 2.4.11 et antérieures empêche ceci de fonctionner correctement. Il est plus simple dans ce cas d´utiliser des ACL, en utilisant la syntaxe décrite dans la recette 2.2 (voir numéro précédent) pour ne pas avoir à énumérer un par un les attributs à protéger :
|
access to dn.subtree="ou=telephony,dc=domain,dc=tld" attrs=@krb5KDCEntry,@krb5Principal,@sambaSamAccount,@posixAccount by * none |
Pour la petite histoire, si cette stratégie fonctionne parfaitement en ligne de commande, un problème de gestion du contrôle pagedResult dans le contexte virtuel avec la version actuelle d´OpenLDAP l´empêche de fonctionner avec le Call Manager Cisco qui sert d´exemple dans cette recette. Le problème est connu, et en attente de résolution (ITS #5724).
Pour plus d´information, consultez :
● la page de manuel slapd-relay(5) ;
● la page de manuel slapo-rwm(5) ;
● la page stockage du Guide d´administration d´OpenLDAP ;
● la page greffons du Guide d´administration d´OpenLDAP.
|
2 |
Cohérence des données |
Les systèmes de gestion de bases de données relationnels proposent de façon standardisée de nombreux moyens d´assurer la cohérence des données qu´ils stockent. OpenLDAP propose également certains mécanismes assurant les mêmes fonctionnalités.
|
2.1 |
Intégrité référentielle |
Dès lors qu´on stocke des références vers des données, il faut s´assurer que lorsque ces données changent, les références concernées changent également. C´est le problème de l´intégrité référentielle. Par exemple, les groupes contiennent des références vers les utilisateurs (dans le schéma RFC2307 bis), et il faut s´assurer que quand ces utilisateurs disparaissent, ou changent d´identifiant, les groupes soient également mis à jour.
OpenLDAP propose un contrôle de l´intégrité référentielle avec le greffon refint. Celui-ci assure qu´en cas de changement de DN ou d´effacement d´objet, tous les attributs pointant vers l´objet en question sont automatiquement mis à jour. La contrainte initiale peut se configurer ainsi :
|
# chargement des modules moduleload refint.so ... overlay refint refint_attributes member |
Les commandes suivantes montrent l´effet de l´effacement de l´utilisateur bar sur le groupe users, à savoir que cet utilisateur est effacé de la liste des membres de ce groupe :
|
[guillaume@oberkampf ~]$ ldapsearch -x -LLL cn=users dn: cn=users,ou=groups,dc=domain,dc=tld objectClass: posixGroup objectClass: groupOfNames cn: users member: uid=bar,ou=users,dc=domain,dc=tld member: uid=foo,ou=users,dc=domain,dc=tld gidNumber: 5001 [guillaume@oberkampf ~]$ ldapdelete -x -D cn=admin,ou=roles,dc=domain,dc=tld -w secret uid=bar,ou=users,dc=domain,dc=tld [guillaume@oberkampf ~]$ ldapsearch -x -LLL cn=users dn: cn=users,ou=groups,dc=domain,dc=tld objectClass: posixGroup objectClass: groupOfNames cn: users member: uid=foo,ou=users,dc=domain,dc=tld gidNumber: 5001 |
Et les commandes suivantes montrent l´effet du renommage de l´utilisateur bar en baz sur ce même groupe, à savoir que la liste des membres du groupe est mise à jour :
|
[guillaume@oberkampf ~]$ ldapmodrdn -x -D cn=admin,ou=roles,dc=domain,dc=tld -w secret uid=foo,ou=users,dc=domain,dc=tld uid=baz [guillaume@oberkampf ~]$ ldapsearch -x -LLL cn=users dn: cn=users,ou=groups,dc=domain,dc=tld objectClass: posixGroup objectClass: groupOfNames cn: users gidNumber: 5001 member: uid=baz,ou=users,dc=domain,dc=tld |
Pour plus d´information, consultez :
● la page de manuel slapo-refint(5) ;
● la page greffon du Guide d´administration d´OpenLDAP.
|
2.2 |
Unicité des attributs |
Un autre problème de validité classique concerne l´unicité de certaines données. Par exemple, chaque utilisateur doit posséder un identifiant alphanumérique (login) et numérique (uid) distinct. Dans un annuaire LDAP, l´attribut utilisé pour former le RDN d´un objet est déjà couvert par une telle contrainte. L´attribut uid de la classe posixAccount servant de RDN, il ne peut ainsi y avoir deux utilisateurs de même login dans une même branche LDAP. Si les utilisateurs sont répartis dans plusieurs branches, ceci n´est pas suffisant pour assurer le respect de la contrainte initiale. Et de toute façon, ceci ne couvre pas l´attribut uidNumber.
OpenLDAP propose une véritable contrainte d´unicité par le biais du greffon unique. Celui-ci permet d´assurer qu´au sein d´une portée donnée, certains attributs aient tous une valeur unique. La contrainte initiale peut donc se configurer ainsi :
|
# chargement des modules moduleload unique.la ... overlay unique unique_base ou=users,dc=domain,dc=tld unique_attributes uid uidNumber |
Et toute tentative de modification d´attribut violant cette contrainte se traduit immédiatement par une erreur, comme le montrent les commandes suivantes :
|
[guillaume@oberkampf ~]$ ldapmodify -x -D cn=admin,ou=roles,dc=domain,dc=tld -w password <<EOF > dn: uid=bar,ou=users,dc=domain,dc=tld > changetype: modify > replace: uidNumber > gidNumber: 5000 ldap_add: Constraint violation (19) additional info: some attributes not unique |
Pour plus d´information, consultez :
● la page de manuel slapo-unique(5) ;
● la page greffon du Guide d´administration d´OpenLDAP.
|
3 |
Audit |
Le serveur slapd gère des fichiers journaux (logs), au même titre que n´importe quel serveur. Néanmoins, ceux-ci sont principalement dédiés à la surveillance du fonctionnement du serveur lui-même, de façon à résoudre les éventuels problèmes. Le seul filtrage possible concerne les différents sous-systèmes (requêtes, connexions, filtres, etc...). De plus, à moins d´utiliser une version de syslog avancée, telle syslog-ng ou rsyslog, avec stockage dans une base de donnée relationnelle, ces journaux sont stockés dans des fichiers texte plats, ce qui rend leur exploitation peu aisée. Bref, ils sont peu adaptés à la production d´information synthétique de haut niveau.
|
3.1 |
Accès aux données |
Le greffon accesslog permet d´enregistrer sous forme d´entrées dans une base LDAP l´ensemble des opérations effectuées sur les données d´une autre base. Elles peuvent ainsi être consultées par des requêtes LDAP ordinaire.
La configuration passe par la définition de la base dédiée avant celle à surveiller, puis à définir différents paramètres d´enregistrement. En particulier, la directive logops spécifie les différents types d´opérations à enregistrer, soit individuellement (ajout, effacement, comparaison, ...), soit par groupe (lecture, écriture, gestion de session). L´exemple de configuration ci-dessous enregistre toutes les modifications effectuées à la base dc=domain,dc=tld dans la base cn=log :
|
# chargement des modules moduleload accesslog.la ... # base d’audit database bdb suffix "cn=log" rootdn "cn=root,cn=log" directory /var/lib/ldap/log ... # base principale database bdb suffix "dc=domain,dc=tld" directory /var/lib/ldap/main ... # enregistrement des modifications réussies # conservation pendant une semaine overlay accesslog logdb cn=log logops writes logsuccess TRUE logpurge 07+00:00 01+00:00 |
Voici l´entrée correspondant au changement du mot de passe d´un utilisateur uid=user,ou=users,dc=domain,dc=tld, effectuée par cn=pam,ou=roles,dc=domain,dc=tld, c´est-à-dire l´utilisateur système root à travers PAM en utilisant la configuration présentée à la recette 1.4 (voir n°111). Les valeurs données sont les nouvelles valeurs, les anciennes n´étant enregistrées que si la directive logold est utilisée également.
|
# 20080310195005.000004Z, log dn: reqStart=20080310195005.000004Z,cn=log objectClass: auditModify reqStart: 20080310195005.000004Z reqEnd: 20080310195005.000005Z reqType: modify reqSession: 2 reqAuthzID: cn=pam,ou=roles,dc=domain,dc=tld reqDN: uid=user,ou=users,dc=domain,dc=tld reqResult: 0 reqMod: userPassword:= {MD5}9x2+UmKKP4OnerSUgXUlxg== reqMod: pwdChangedTime:= 20080310195005Z reqMod: pwdFailureTime:- reqMod: entryCSN:= 20080310195005Z#000000#00#000000 reqMod: modifiersName:= cn=pam,ou=roles,dc=domain,dc=tld reqMod: modifyTimestamp:= 20080310195005Z |
Des informations confidentielles pouvant se trouver dans cette base, comme le montre l´exemple précédent, il convient de restreindre son accès par des ACL, au même titre que la base principale. Il est intéressant également de noter que l´utilisation de ce greffon permet de faire de la synchronisation différentielle avec syncrepl, et donc de ne transmettre que les modifications entre le serveur maître et le serveur esclave.
Pour plus d´information, consultez :
● la page de manuel slapo-accesslog(5) ;
● la page greffon du Guide d´administration d´OpenLDAP.
|
3.2 |
Statistiques d´utilisation |
Le module de stockage back_monitor permet, lui, d´activer les statistiques d´utilisation, là encore sous forme d´objets dans une base LDAP. La configuration est minimale, puisque la base de stockage de ces informations est obligatoirement cn=monitor, et qu´il n´y a aucune directive :
|
# chargement des modules moduleload back_monitor.la ... database monitor |
Voici l´entrée correspondant aux opérations d´ouverture de session (bind), indiquant notamment le nombre d´opérations effectuées (attribut monitorOpInitiated), depuis la date de mise en place du greffon (attribut createTimestamp). Tous les attributs en question sont des attributs opérationnels, il faut prendre garde à les demander explicitement dans la requête.
|
# Bind, Operations, Monitor dn: cn=Bind,cn=Operations,cn=Monitor structuralObjectClass: monitorOperation monitorOpInitiated: 7690 monitorOpCompleted: 7690 creatorsName: modifiersName: createTimestamp: 20080310194959Z modifyTimestamp: 20080310194959Z entryDN: cn=Bind,cn=Operations,cn=Monitor subschemaSubentry: cn=Subschema hasSubordinates: FALSE |
Là encore, des ACL peuvent donc être nécessaires, bien que la confidentialité de ces informations soit moins évidente que dans le cas précédent, et s´apparente à celles de toutes les données générales de fonctionnement du système.
Pour plus d´information, consultez :
● la page de manuel slapd-monitor(5) ;
● la page stockage du Guide d´administration d´OpenLDAP.
|
3.3 |
Métrologie |
Le greffon monitor, présenté à la recette précédente, fournit de nombreuses statistiques, mais dont l´exploration manuelle présente peu d´intérêt. D´autres sources de données sont également possibles, comme les outils de statistique de la base BDB utilisée pour stocker l´annuaire. Reste à trouver comment présenter ces données sous une forme accessible.
Munin est un outil de métrologie, basé comme de nombreux autres sur rrdtools, présentant de grandes facilités d´auto-configuration. Il est basé sur l´emploi de greffons spécifiques à un type de surveillance, et, dans le cas d´OpenLDAP, il en existe deux, récupérables chez un contributeur anonyme. Le premier de ce plugin utilise les statistiques fournies par le greffon monitor, le deuxième utilise l´outil db_stat (paquetage db42-utils sur une Mandriva 2008.0, db46-utils sur les versions suivantes). Les figures 1 à 3 sont plus parlantes qu´un long discours.
|
|
|
Figure 1 : Recherches d´index réussies, par jour |
|
|
|
Figure 2 : Nombre d´opérations effectuées, par jour |
|
|
|
Figure 3 : Nombre de connexions, par jour |
Hobbit est une autre solution de métrologie similaire. Un greffon écrit par Buchan Milne, bb-openldap, permet de récupérer les informations de slapd. En plus des informations de métrologies à proprement parler, il vérifie également la synchronisation des serveurs secondaires, comme présenté dans la recette 4.2.
|
4 |
Réplication |
La réplication est le mécanisme permettant de mettre en place un ou plusieurs serveurs secondaires, de façon à améliorer la robustesse du service. L´ancien système slurpd est obsolète. Il a même complètement disparu de la branche 2.4, et il a été aujourd´hui remplacé par syncrepl. Les recettes suivantes montrent sa mise en place, présentent quelques façons de surveiller son fonctionnement, puis comment faire en sorte de propager les changements effectués sur un serveur secondaire vers le serveur principal de façon transparente.
|
4.1 |
Mise en place |
La mise en place proprement dite ne pose guère de problèmes particuliers. Il s´agit de charger le greffon sur le serveur secondaire (le consommateur), de le configurer proprement, de s´assurer que l´identité utilisée sur le serveur principal (le fournisseur) permet la lecture de tout ce qui doit être répliqué. Le serveur secondaire va alors se mettre à jour de façon automatique. Cependant, quelques précisions peuvent se révéler utiles.
D´abord, il existe deux types de synchronisation : intermittente ou persistante. Dans le premier cas, (refreshOnly), le consommateur envoie au fournisseur une requête de synchronisation à intervalle régulier. Dans le second cas (refreshAndPersist), cette requête persiste au sein du fournisseur, et tout changement sur celui-ci provoque automatiquement leur transmission au consommateur. Dans l´exemple suivant, c´est le mode persistant qui est utilisé, comme déterminé par le paramètre type :
|
syncrepl rid=123 provider=ldaps://ldap.domain.com type=refreshAndPersist searchbase="dc=domain,dc=tld" scope=sub schemachecking=off bindmethod=simple binddn="cn=syncrepl,ou=roles,dc=domain,dc=tld credentials=s3cr3t |
Ceci ne doit pas être confondu avec la synchronisation entière ou différentielle (delta syncrepl). La première, la plus simple à mettre en œuvre transmet au consommateur l´intégralité d´une entrée lorsque celle-ci est modifiée. Ce qui dans le cas des annuaires importants peut générer un trafic important. À l´opposé, la seconde ne transmet que les modifications effectives, ce qui est censé être plus économe en termes de bande passante. Elle nécessite par contre la mise en place de journaux d´audit, comme détaillé dans la recette 3.1. Il suffit alors de rajouter les paramètres logbase, logfilter et syncdata à l´exemple précédent :
|
syncrepl rid=123 provider=ldaps://ldap.domain.com type=refreshAndPersist logbase="cn=log" logfilter="(&(objectClass=auditWriteObject)(reqResult=0))" syncdata=accesslog searchbase="dc=domain,dc=tld" scope=sub schemachecking=off bindmethod=simple binddn="cn=syncrepl,ou=roles,dc=domain,dc=tld" credentials=s3cr3t |
Au niveau des permissions, sur le consommateur, c´est le rootdn qui est utilisé. Il n´y a donc besoin d´aucune ACL particulière. Par contre, sur le fournisseur, il est important de vérifier que l´utilisateur utilisé possède toutes les autorisations nécessaires, et n´est pas bridé par de quelconques limites. Ceci à la fois pour la base à répliquer d´une part, et sur la base de logs également dans le cas de la réplication différentielle. Une solution radicale est de passer par une ACL dédiée, à placer en tête de liste :
|
# syncrepl: accès en lecture global access to dn.subtree="dc=domain,dc=tld" by dn="cn=syncrepl,ou=roles,dc=domain,dc=tld" read by * break
limits dn.exact="cn=syncrepl,ou=roles,dc=domain,dc=tld" size.soft=unlimited size.hard=unlimited size.unchecked=unlimited time.soft=unlimited time.hard=unlimited |
Enfin, le paramètre rid est un identifiant de réplication interne au consommateur. En d´autres termes, il n´y a pas besoin de s´assurer de son identité entre différents consommateurs.
Pour plus d´information, consultez :
● la page de manuel slapd.conf(5) ;
● la page réplication du Guide d´administration d´OpenLDAP.
|
4.2 |
Vérification |
À la mise en place de la réplication, il est facile de s´assurer de son fonctionnement. D´abord, le répertoire hébergeant la base se remplit de nouveaux fichiers. Ensuite, il suffit d´interroger le serveur secondaire pour s´assurer qu´il contient bien les entrées répliquées. Mais, comment s´assurer que cette réplication persiste par la suite, et que les différents serveurs restent synchronisés ?
Nagios est un outil de supervision bien connu. Sur le site Nagios Exchange, on trouve un greffon de surveillance de l´état de synchronisation de deux serveurs OpenLDAP, check_syncrepl. Il est écrit en Python, et nécessite le paquetage python-ldap. Il est également disponible dans le paquetage nagios-check_syncrepl sous Mandriva. Une fois installé, il suffit de le lancer en lui donnant la base de recherche, et les URI du fournisseur et du consommateur. Si les deux serveurs sont synchronisés, le résultat ressemble à ceci :
|
[guillaume@oberkampf ~]$ /usr/lib/nagios/plugins/check_syncrepl.py ldap://ldap1.domain.tld ldap://ldap2.domain.tld -b dc=domain,dc=com 2008-09-25 00:32:47,305 - check_syncrepl.py - INFO - Provider is: ldap1.domain.tld 2008-09-25 00:32:47,321 - check_syncrepl.py - INFO - Checking if consumer ldap2.domain.tld is in SYNCH with provider 2008-09-25 00:32:47,327 - check_syncrepl.py - INFO - Provider and consumer exactly in SYNCH |
Si ce n´est pas le cas, par contre, le résultat ressemble à cela :
|
[guillaume@oberkampf ~]$ /usr/lib/nagios/plugins/check_syncrepl.py ldap://ldap1.domain.tld ldap://ldap2.domain.tld -b dc=domain,dc=com 2008-09-25 00:32:47,305 - check_syncrepl.py - INFO - Provider is: ldap1.domain.tld 2008-09-25 00:32:47,305 - check_syncrepl.py - INFO - Provider is: ldap1.domain.tld 2008-09-25 00:32:47,321 - check_syncrepl.py - INFO - Checking if consumer ldap2.domain.tld is in SYNCH with provider 2008-09-25 00:32:47,327 - check_syncrepl.py - INFO - Consumer NOT in SYNCH 2008-09-25 00:32:47,327 - check_syncrepl.py - INFO - Delta is 84 days, 0:50:21 |
En général, il suffit de relancer le consommateur, éventuellement après avoir vidé sa base, pour résoudre la plupart des problèmes.
Attention, pour la mise en œuvre par Nagios, il faut rediriger (option -l) dans un répertoire adéquat, ou, mieux, supprimer (option -q) l´enregistrement du résultat, et rajouter l´option -n. J´ai également rencontré des soucis avec des serveurs donnant une précision temporelle à la microseconde pour le marqueur de synchronisation, entraînant un crash. Un patch est disponible sur la page d´origine du greffon.
Le greffon bb-openldap pour Hobbit, déjà présenté dans la recette 3.3, réalise également cette surveillance.
|
4.3 |
Redirection transparente des modifications |
Un serveur esclave est par définition en lecture seulement, tous les changements se faisant exclusivement sur le maître. Or, dans certaines architectures, ce dernier n´est pas accessible aux utilisateurs. Comment faire dans ce cas pour leur permettre néanmoins d´effectuer les modifications les concernant, comme les changements de mot de passe ?
La solution présentée ici fait intervenir plusieurs fonctionnalités avancées de LDAP : les références LDAP (referrals), la délégation (proxy), et le greffon chain. Elle est donc assez complexe, mais parfaitement fonctionnelle.
Le début est trivial, puisqu´une simple directive updateref dans la configuration de l´esclave est suffisante :
|
updateref ldap://ldap1.domain.tld |
Toute demande de wmodification se voit ainsi retournée une référence vers le serveur maître, comme ici :
|
[guillaume@oberkampf ~]$ ldappasswd -x -D uid=bar,ou=users,dc=domain,dc=tld -w password -s newpassword -H ldap2.domain.tld Result: Referral (10) Referral: ldaps://ldap1.domain.tld |
Certains clients LDAP peuvent être configurés pour suivre automatiquement cette référence, avec tous les problèmes de sécurité qui s´ensuivent. Mais, de toute façon, le but poursuivi ici est de rendre la chose transparente pour l´utilisateur. Le greffon chain permet justement d´effectuer ce suivi automatiquement, au sein du serveur. Sur le serveur esclave, il faut donc rajouter la configuration suivante :
|
overlay chain chain-uri "ldap://ldap1.domain.tld" chain-idassert-bind bindmethod="simple" binddn="cn=chain,ou=roles,dc=domain,dc=tld" credentials="s3cr3t" mode="self" chain-idassert-authzFrom "*" chain-tls start chain-return-error TRUE |
La directive chain-uri met en place le suivi pour toutes les références correspondant à sa valeur. La directive chain-idassert-bind définit l´identifiant, le mot de passe et la méthode utilisée pour s´authentifier sur le serveur maître : en effet, le serveur esclave utilise un identifiant particulier, à la place de l´identifiant de la requête initiale. Si cette authentification réussie, par contre, c´est bien l´identifiant de cette requête initiale qui sera utilisée pour vérifier ses autorisations. Cette dissociation d´identité entre authentification et autorisation est rendue nécessaire par le fait que le mot de passe de l´utilisateur initial n´est plus disponible à cette étape, et qu´il n´est pas possible de rejouer son authentification sur le serveur maître. La directive chain-idassert-authzFrom autorise tous les identifiants à utiliser ce mandataire. Enfin, une connexion chiffrée est utilisée, et, en cas d´échec, l´erreur est renvoyée au client à la place de la référence.
Enfin, il faut mettre en place le mandataire sur le maître. D´abord, il faut autoriser cette fonctionnalité, en définissant une politique de délégation :
|
# proxy authorization policy authz-policy to |
Cette directive, telle que définie ici, permet de s´authentifier auprès du serveur avec un identifiant donné, puis à utiliser un autre identifiant compatible avec la valeur de l´attribut authzTo de cette entrée pour la détermination des autorisations. Nous avons besoin que l´identité cn=chain,ou=roles,dc=domain,dc=tld, utilisée par l´esclave, puisse ainsi prendre le rôle de n´importe quelle autre identité. Il faut donc définir cette entrée de la façon suivante :
|
dn: cn=chain,ou=roles,dc=domain,dc=tld objectClass: organizationalRole objectClass: simpleSecurityObject cn: chain description: slave server proxy user authzTo: dn:* |
Une fois tout ceci en place, la tentative de modification précédente devrait réussir :
|
[guillaume@oberkampf ~]$ ldappasswd -x -D uid=bar,ou=users,dc=domain,dc=tld -w password -s newpassword -H ldap2.domain.tld [guillaume@oberkampf ~]$ |
Attention, l´attribut authzTo est un attribut opérationnel présent pour toute entrée. Autrement dit, un utilisateur capable de modifier son propre attribut est capable d´usurper l´identité de n´importe quel autre utilisateur... D´une manière générale, l´accès aux attributs de l´entrée d´un utilisateur par lui-même doit être strictement contrôlé, et n´autoriser la modification que dans les cas pertinents :
|
# attributes access to dn.subtree="ou=users,dc=domain,dc=tld" attrs=userPassword by self ssf=56 write by self peername.ip=127.0.0.1 write by anonymous ssf=56 auth by anonymous peername.ip=127.0.0.1 auth by * none access to dn.subtree="ou=users,dc=domain,dc=tld" attrs=loginShell,gecos,displayName,description, telephoneNumber,mobile,mail,labeledURI,postalAddress,cn,sn, givenName,roomNumber,preferredLanguage by self write by * read access to dn.subtree="ou=users,dc=domain,dc=tld" by * read |
D´autres politiques de délégation, moins sensibles, peuvent également être adoptées. Plutôt que d´autoriser le mandataire à prendre n´importe quel identifiant, il peut être restreint à la branche utilisateur :
|
dn: cn=chain,ou=roles,dc=domain,dc=tld objectClass: organizationalRole objectClass: simpleSecurityObject cn: chain description: slave server proxy user authzTo: dn.children:ou=users,dc=domain,dc=tld |
Ou encore, la politique peut être définie dans l´autre sens. Plutôt que de définir pour qui le mandataire peut se faire passer, on peut définir par qui un identifiant peut se faire représenter, en utilisant cette fois-ci l´attribut authzFrom, et en changeant la configuration de slapd :
|
# proxy authorization policy authz-policy from |
Ces utilisateurs peuvent ainsi se faire représenter :
|
dn: uid=foo,ou=users,dc=domain,dc=tld authzFrom: dn: cn=chain,ou=roles,dc=domain,dc=tld dn: uid=bar,ou=users,dc=domain,dc=tld authzFrom: dn: cn=chain,ou=roles,dc=domain,dc=tld |
Cette politique offre un meilleur contrôle, mais nécessite plus de modifications dans les données.
Pour plus d´information, consultez :
● la page de manuel slapd.conf ;
● la page de manuel slapo-chain(5) ;
● la page de manuel slapd-ldap(5) ;
● la page greffon du Guide d´administration d´OpenLDAP ;
● la page stockage du Guide d´administration d´OpenLDAP.
|
5 |
Kerberos |
Le modèle d´authentification par défaut d´OpenLDAP est simple à mettre en œuvre et à utiliser. Associé à une connexion chiffrée par SSL ou TLS, il permet également un niveau de sécurité satisfaisant. Mais, il existe un autre modèle, plus robuste mais également plus complexe à mettre en œuvre, basé sur SASL. Ce dernier est en fait un modèle générique, permettant d´utiliser différents mécanismes de façon transparente pour l´application. Parmi ceux-ci, on peut citer l´utilisation de certificats X.509 ou encore Kerberos. Ce dernier est un mécanisme d´authentification fort, qui nécessite la mise en place d´une infrastructure dédiée, et notamment un serveur de distributions de clés, le KDC (Key Distribution Center). Pour chaque application à laquelle il désire accéder, l´utilisateur obtient un ticket d´authentification auprès de ce KDC, et le présente à l´application. Si celle-ci arrive à le déchiffrer, elle a la garantie que l´utilisateur est bien celui qu´il prétend être.
Les recettes qui suivent montrent l´intégration d´un serveur OpenLDAP à cette infrastructure Kerberos, que l´on suppose déjà mise en place.
|
5.1 |
Authentification |
La mise en œuvre de Kerberos au sein d´OpenLDAP est relativement simple. Il suffit de créer un principal de la forme ldap/host.domain.tld, en utilisant le nom canonique du serveur LDAP, c´est-à-dire celui retourné par une interrogation DNS sur son adresse IP, et pas un quelconque alias DNS. En effet, le client effectue une normalisation du nom de l´hôte avant de demander un ticket au KDC. Puis, il faut extraire la clé de ce principal dans le fichier keytab du serveur LDAP, par exemple de la façon suivante :
|
[guillaume@oberkampf ~]$ kadmin -p admin/admin@DOMAIN.TLD ext_keytab ldap/host.domain.tld |
Il faut ensuite s´assurer que le fichier keytab est bien lisible par le processus slapd, puis installer SASL et son greffon GSSAPI, et enfin relancer le serveur. Si tout est correct, le serveur devrait alors déclarer GSSAPI parmi les mécanismes d´authentification disponibles lorsque l´on interroge la racine DSE :
|
[guillaume@oberkampf ~]$ ldapsearch -x -b "" -s base supportedSASLMechanisms -LL version: 1 dn: supportedSASLMechanisms: GSSAPI |
Il ne reste plus qu´à installer également SASL sur le client, à obtenir un ticket Kerberos, puis à tester avec un client :
|
[guillaume@oberkampf ~]$ ldapsearch -Y GSSAPI -s base -LL SASL/GSSAPI authentication started SASL username: user@DOMAIN.TLD SASL SSF: 56 SASL data security layer installed. version: 1 dn: dc=domain,dc=tld dc: domain objectClass: top objectClass: domain |
Pour plus d´information, consultez :
● la page SASL du Guide d´administration d´OpenLDAP.
|
5.2 |
Autorisation |
Une fois Kerberos en place, l´utilisateur peut s´authentifier en présentant un ticket au serveur LDAP, à la place du couple identifiant/mot de passe. Néanmoins, il est alors affecté d´un identifiant distinct de son identifiant habituel, comme le montre la commande ldapwhoami, ce qui le prive des permissions attribuées à celui-ci :
|
[guillaume@oberkampf ~]$ ldapwhoami -Y GSSAPI SASL/GSSAPI authentication started SASL username: user@DOMAIN.TLD SASL SSF: 56 SASL data security layer installed. dn:uid=user,cn=gssapi,cn=auth |
Pour effectuer la traduction de cet identifiant SASL en identifiant canonique, il faut utiliser la directive authz-regexp dans la configuration de slapd :
|
authz-regexp "uid=([^,]*),cn=gssapi,cn=auth" "uid=$1,ou=users,dc=domain,dc=tld" |
Une fois ceci effectué, tout devrait rentrer dans l´ordre :
|
[guillaume@oberkampf ~]$ ldapwhoami -Y GSSAPI SASL/GSSAPI authentication started SASL username: user@DOMAIN.TLD SASL SSF: 56 SASL data security layer installed. dn:uid=user,ou=users,dc=domain,dc=tld |
Pour plus d´information, consultez :
● la page de manuel slapd.conf.
|
6 |
Conclusion |
La conclusion du mois précédent s’applique toujours. Ce supplément/complément inespéré n’avait pu être intégré faute de place et il nous semblait toutefois important de ne pas faire l’impasse sur toutes ces bonnes choses.
OpenLDAP est déjà riche en fonctionnalités et en usages. Mais, il faut également savoir que le projet lui-même ne cesse d’évoluer. Qui sait ce qu’il sera possible de faire d’ici quelques mois, d’ici un an ou plus ? Les informations données ici sont bien entendu utilisables en tant que telles, mais c’est aussi à vous de pousser plus loin et de les compléter avec vos recettes " maison ". De quoi finir une longue série de soirées d’hiver en beauté, non ?
|
Auteur : Guillaume Rousse |
|
|
|
Remerciements |
|
Pour finir, je tiens à remercier Buchan Milne et Andreas Hasenack, qui sont à l´origine d´une grande partie des informations présentées ici, ainsi que Mickaël Scherer pour sa relecture attentive. |
|
Erratum sur la partie 3.5 de l’article précédent |
|
La valeur de l’attribut userPassword quand elle est déléguée à SASL doit être de la forme {SASL}utilisa |




Ingénieur système à l´INRIA
