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, 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 filter 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 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. 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.
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 8.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 7.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 rechercher, 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 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 7.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 modification 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 qu´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éfault 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.
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 ?
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}utilisateur@royaume, et non pas SASLutilisateur@royaume.