URL: https://linuxfr.org/users/peb--2/journaux/ldap-un-peu-de-technique Title: LDAP, un peu de technique. Authors: PEB Date: 2013年06月21日T03:52:54+02:00 License: CC By-SA Tags: ldap, slapd, débuter et installer Score: 42 Dans mon journal d'hier, je parlais de LDAP d'un point de vue utilités. Je propose ici de se plonger un peu plus dans le côté technique. Comme référence, j'utilise l'installation et la configuration du paquet slapd sous debian, nul doute que cela marchera à d'autres endroits sur d'autres OS. Installez donc slapd, et ldap-tools. Une histoire de schéma. =========== Avant de se plonger dans une partie configuration du serveur LDAP proprement dit, nous allons revenir sur ces histoires de schéma LDAP. Chez moi, les schémas par défaut se trouvent dans /etc/ldap/schema. Ils ne sont pas « préchargés » automatiquement, la configuration prise en compte se trouve dans /etc/ldap/slapd.d, mais ils permettent de comprendre un peu ce qu'il se passe. Des attributs... ----------- Bon, ce titre fait un peu cliffhanger de derrière les fagots... Passons. Une base LDAP sert à stocker des objets, inscrits dans des nœuds, ayant parent et enfants. Les propriétés d'un objet dépendent de celui-ci. Ici, avant de nous intéresser à l'objet proprement dit, on va se pencher sur les attributs. (on peut ce référer à [cette](http://www.zytrax.com/books/ldap/ch3/) page web pour plus d'informations) Dans l'ancien format des bases LDAP, pour les schémas, les attributs se représentaient de la façon suivante : attributetype ( 1.3.6.1.1.1.1.2 NAME 'gecos' DESC 'The GECOS field; the common name' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE ) Vous croiserez cet attribut dans /etc/ldap/schema/nis.schema. - attributetype signifie qu'on déclare un attribut, tout ce qui suit se trouve entre parenthèses. - La suite de numéros bizarres est un OID, un identifiant unique, qui permet de représenter votre attribut. Une des spécificités de LDAP est de proposer de le voir comme un arbre mondial, la racine de votre base serait donc virtuellement un des enfants d'un arbre LDAP imaginaire qui serait la propriété des gourous de LDAP. L'identifiant en question permettrait de dire "cet attribut est l'attribut numéro 2 du schéma nis". - NAME 'nom' définit le nom de l'attribut, on s'y réfère ainsi par la suite. Il est préférable pour la lisibilité de se limiter aux caractères ASCII. - DESC 'description' est une description de l'attribut. Dans la mesure du possible, on se limite aux caractères ASCII. - EQUALITY compMethod définit une méthode de comparaison pour l'égalité, c'est une contrainte implicite pour dire que notre attribut est un entier, ou une chaîne de caractère ou autre chose... - SUBSTR compMethod propose la même chose que EQUALITY, mais pour un sous-ensemble de l'attribut (chercher "coucou" dans la phrase "coucou, comment vas-tu ?" par exemple). - SYNTAX PLEIN.DE.CHIFFRES définit une contrainte sur la variable. La suite de chiffre est un OID qui pointe vers un type d'attribut précis. Par exemple, si vous utilisez SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{123}, vous définissez une chaîne de caractère quasi-ASCII de longueur maximale 123 caractères. Si vous essayez de définir un attribut avec cette syntaxe et de balancer un "é" dedans, LDAP vous inondera de son amour et de sa croyance en vos capacités de leader religieux. - SINGLE-VALUE signifie que l'attribut est monovalué. Autrement dit, un objet ne peut avoir deux gecos. Il n'en a au plus qu'un. Référez-vous au lien que je vous ai fourni pour plus d'informations. Pourquoi vous parler de l'ancienne syntaxe si elle n'est justement plus d'actualité ? Parce que la nouvelle syntaxe correspond aux schémas définis directement dans la base LDAP de configuration, qu'elle diffère peu de l'ancienne, et que lorsqu'on crée sa base LDAP, et qu'on la configure pour la première fois, créer un schéma dans un fichier puis le faire incorporer dans la base via la commande slaptest est bien plus agréable et facile. La nouvelle syntaxe est une syntaxe compatible avec ldif, voici le même attribut dans cette syntaxe. olcAttributeTypes: ( 1.3.6.1.1.1.1.2 NAME 'gecos' DESC 'The GECOS field; the common name' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE ) C'est très proche de l'ancienne syntaxe, mais compatible avec ldif. Regardez nis.ldif en comparaison avec nis.schema. ...et des objets ----------- Une fois qu'on a nos attributs, on souhaite créer des objets. Toujours dans nis, regardez en bas du fichier. objectclass ( 1.3.6.1.1.1.2.0 NAME 'posixAccount' DESC 'Abstraction of an account with POSIX attributes' SUP top AUXILIARY MUST ( cn $ uid $ uidNumber $ gidNumber $ homeDirectory ) MAY ( userPassword $ loginShell $ gecos $ description ) ) Et en nouvelle syntaxe : olcObjectClasses: ( 1.3.6.1.1.1.2.0 NAME 'posixAccount' DESC 'Abstraction of an account with POSIX attributes' SUP top AUXILIARY MUST ( cn $ uid $ uidNumber $ gidNumber $ homeDirectory ) MAY ( userPassword $ loginShell $ gecos $ description ) ) - objectclass signifie qu'on déclare un objet. - L'OID est également unique, comme pour les attributs - NAME définit toujours un nom, si possible avec des caractères ASCII exclusivement - DESC décrit l'objet - SUP définit un objet parent (celui qu'on définit hérite de ses propriétés) - La déclaration AUXILIARY est plus compliquée. Ici, on peut écrire STRUCTURAL, AUXILIARY ou ABSTRACT. Lorsqu'on crée un nœud et lui donne des informations, parmi les attributs objectClass, qui vont définir quels autres attributs un objet peut avoir, il doit y avoir un (et un seul) objectClass pointant vers un élément STRUCTURAL, et autant que l'on souhaite vers des éléments AUXILIARY, ou ABSTRACT. Un élément ne peut hériter que d'un élément du même type, ou d'un ABSTRACT. Donc un élément AUXILIARY ne peut pas hériter d'un STRUCTURAL, et réciproquement. Les éléments STRUCTURAL représentent des objets concrets. Ici, posixAccount est AUXILIARY : un objet ne pourra pas être juste un posixAccount. - MUST ( attributs séparés par des $ ) indique la liste des attributs qu'un objet _doit_ posséder en au moins un exemplaire. - MAY ( attributs séparés par des $ ) indique les attributs supplémentaires qu'un objet peut posséder. Voilà pour les schémas, une fois ceci compris, imaginons qu'en plus des éléments dans nis, on ait défini un schéma personnel avec cet objectClass : olcObjectClasses: ( 1.2.3.1.2.3.1.2.3.10 NAME 'userAccount' DESC 'Implementation of a user account' SUP top STRUCTURAL MUST ( fname $ name ) MAY ( mail $ town ) ) Avec mail, town, name et fname des attributs définissant email, ville, nom et prénom. Un compte d'utilisateur pourrait alors être l'objet avec les attributs : dn: uid=peb,ou=users,dc=localdomain objectClass: userAccount objectClass: posixAccount cn: PEB fname: PE gidNumber: 100 homeDirectory: /home/peb loginShell: /bin/zsh mail: peb@yopmail.com name: B uid: peb uidNumber: 2573 userPassword: {SSHA}Sdduv35Y4x/49FeFmrroxXXlrpgff La syntaxe ici est celle de LDIF. On y reviendra, mais vous savez désormais à quoi ressemblent les objets dans une base LDAP. Configuration de slapd. =========== Ça va vous sembler extrême, mais la première chose à faire après avoir installé slapd est cette série de commandes. # /etc/init.d/slapd stop # rm -rf /etc/ldap/slapd.d/* # rm -rf /var/lib/ldap/[^D]* On va démarrer avec un environnement propre, et faire une configuration nous-même. Il est possible que la configuration initiale vous ait convenu, mais l'objectif est de comprendre ce qu'il se passe. Écriture d'un fichier slapd.conf ----------- Je vous propose ici de construire notre fichier /etc/ldap/slapd.conf, je vais partir sur un exemple simple, mais n'hésitez pas à l'aide de la documentation à rendre le votre plus complet, et adapté à vos envies. ####################################################################### # Directives globales: # Définition des schémas et objectClasses include /etc/ldap/schema/core.schema include /etc/ldap/schema/cosine.schema include /etc/ldap/schema/nis.schema include /etc/ldap/schema/inetorgperson.schema include /etc/ldap/schema/dyngroup.schema include /etc/ldap/schema/perso.schema Ici, on commence par inclure les schémas prééexistants qui peuvent servir, et notre schéma personnel. # Emplacement du pidfile pidfile /var/run/slapd/slapd.pid # Arguments passés au serveur argsfile /var/run/slapd/slapd.args # Niveau de journalisation loglevel 0 Ensuite, quelques configurations basiques, pour les besoins du logiciel. loglevel 0 signifie qu'on le loggue rien dans syslog. Regardez loglevel dans la manpage de slapd.conf pour plus d'infos. Un bon niveau de logging quand on fait du débug est 255, mais attention, c'est extrêmement verbeux, et un serveur limité en ressources se fait souvent overloader par ce niveau de logging. (testé et désapprouvé sur un domu) # Stockage des modules modulepath /usr/lib/ldap moduleload back_bdb On définit ici le répertoire des modules, et on charge back_bdb, qui fournit le type de base LDAP "Berkley DB", que j'aime bien. # Limitation du nombre de réponses sizelimit 1000000000 Limite du nombre d'objets que la base peut simultanément retourner. # Méthode pour crypter les mots de passe password-hash {SSHA} ############################################################### # Directives de configuration pour le backend bdb backend bdb Ici, on précise type de backend pour lequel les options à venir s'appliquent. Comme il n'y a pas d'autre option, je ne suis pas convaincu que ça soit utile. ####################################################################### # Configuration de la base de données principale database bdb Ici, on déclare une nouvelle base de données, de type Berkley DB, tout ce qui suivra jusqu'à une autre déclaration générale concerne cette base. Certaines directives sont spécifiques au backend, comme on peut le voir [ici](http://www.zytrax.com/books/ldap/ch6/bdb.html). # Racine suffix "dc=localdomain" Nom du nœud racine. # Répertoire de stockage de la base de données directory "/var/lib/ldap" (Backend-dependant) Il peut être plus adapté lorsqu'on souhaite créer plusieurs bases de données de les mettre dans des répertoires distincts, il faut pour cela que le backend le permette. # Options d'indexation pour la base de données index entryUUID,entryCSN eq index objectClass,uid,uidNumber eq index cn,sn eq,approx,sub index mail,town eq index name,fname eq,approx,sub (Backend-dependant) Les index ont à peu près les mêmes utilités que pour une base SQL par exemple : diminuer le temps de processing pendant les recherches. Les index sont créés à l'ajout d'un objet dans la base ldap, ou à la modification des attributs sur lesquels ils sont placés. # Sauvegarde des timestamps de modifications des objets lastmod on # LDAP a besoin de la déclaration du rootdn rootdn "cn=admin,dc=localdomain" rootpw "{SSHA}HASH" Ici, on définit l'administrateur de la base, avec pour mot de passe un hash. Pour générer un ssha à partir d'un mot de passe, utilisez la commande slappasswd, et collez sa sortie à la place de "{SSHA}HASH". # L'attribut userPassword doit pouvoir être écrit uniquement par # soi-même, l'admin, les replicas. Dovecot peut le lire, et on peut # s'authentifier avec anonymement access to attrs=userPassword by dn.regex="cn=dovecot,dc=localdomain" read by anonymous auth by self write by * none # Permettre l'accès à la base pour des trucs comme # "supportedSASLMechanisms", pour éviter des problèmes # possibles si les méthodes SASL ne sont pas connues. access to dn.base="" by * read # Pour postfix access to dn.regex="^uid=[0-9]+,ou=users,dc=localdomain$$" attrs=mail,alias,uid,mail,entry,objectClass by dn.regex="cn=postfix,dc=localdomain" read by * break # Pour dovecot access to dn.regex="^uid=[0-9]+,ou=users,dc=localdomain$$" attrs=uid,homeDirectory,uidNumber,gidNumber,userPassword,objectClass,entry by dn.regex="cn=dovecot,dc=localdomain" read by * break # Informations accessibles à tous access to dn.regex="^uid=[0-9]+,ou=users,dc=localdomain$$" attrs=uid,homeDirectory,uidNumber,gidNumber,gecos,objectClass,entry by anonymous read by * break access to dn="ou=users,dc=localdomain" by anonymous search by * break access to dn.subtree="ou=Group,dc=linkki,dc=crans,dc=org" by anonymous read by * break # L'admin et readonly peuvent tout lire # can read everything. access to * by dn.regex="cn=admin,dc=localdomain" write by sockname.regex="ldapi" read by dn.regex="cn=readonly,dc=localdomain" read by self read by * none Ici, on configure les acl. Ils ne sont pas dépendants du backend, comme c'est un des points les plus techniques de LDAP, j'en reparlerai dans un journal séparé, après avoir pu faire mon lot de tests. Vous noterez que ça parle de dovecot et postfix, l'idée est de montrer un cas où on va créer des utilisateurs administratifs de LDAP pour dovecot et postfix à des fins d'interfaçage. Voilà, la configuration est finie, votre fichier slapd.conf est prêt. Il ne reste qu'à exécuter la commande # sudo -u openldap slaptest -f /etc/ldap/slapd.conf -F /etc/ldap/slapd.d Il est possible que slaptest vous sorte une erreur, due au fait que id2entry.bdb n'existe pas, c'est normal, puisqu'on avait fait le ménage, mais la configuration est bien faite. Faites # /etc/init.d/slapd start Votre base LDAP tourne (et id2entry a été créé), mais... est vide. Peupler une base LDAP =========== On y est, on va ici apprendre à ajouter des données, donc à créer des petits fichiers au format LDIF, puis à utiliser ldapadd, voire ldapmodify. Une fois cela fait, on arrêtera ici pour cette initiation. Un journal parlera des ACL, et un autre de la modification à chaud du schéma, des configs, et la réplication. Premiers objets. ----------- Il faut créer le nœud racine, et nous allons en plus créer deux nœuds, ou=groups, et ou=users. Lorsqu'on souhaite ajouter des objets, la syntaxe ldif est très simple, il suffit de donner l'ensemble des informations concernant les divers objets, séparés par des lignes vides. Avant chaque :, on a le nom d'attribut, et après, une espace puis la valeur de l'attribut. dn: dc=localdomain objectClass: top objectClass: dcObject objectClass: organization dc: localdomain o: localdomain dn: ou=users,dc=localdomain objectClass: top objectClass: organizationalUnit ou: users dn: ou=groups,dc=localdomain objectClass: top objectClass: organizationalUnit ou: groups On enregistre ces lignes dans un fichier /tmp/begin.ldif, puis on effectue la commande $ ldapadd -H ldap://127.0.0.1 -D "cn=admin,dc=localdomain" -W -f /tmp/begin.ldif - -H permet d'indiquer l'hôte à contacter. - -D permet de préciser l'utilisateur avec lequel on effectue l'opération - -W signifie qu'on entre le mot de passe de l'utilisateur en command line - -f permet de préciser un fichier (sinon, on utilise l'entrée standard) Tapez le mot de passe de votre admin ("Plop", chez moi), et le tour est joué. Ajout d'objets plus concrets, modifications. ----------- Vous devriez, via ldapadd, et un fichier, pouvoir créer un utilisateur voisin de celui que j'avais proposé ci-dessus. dn: uid=peb,ou=users,dc=localdomain objectClass: userAccount objectClass: posixAccount cn: PEB fname: PEB gidNumber: 100 homeDirectory: /ghome/peb loginShell: /sbin/zsh mail: peb@yopmail.com name: B uid: peb uidNumber: 2573 userPassword: {SSHA}Sdduv35Y4x/49FeFmrroxXXlrpgff Malheureusement, comme vous l'avez sûrement constaté, je suis très mauvais pour copier/coller, et des erreurs de scénario se sont glissées dans l'utilisateur. Apprenons maintenant à les corriger. La syntaxe change quelque peu, puisqu'on va devoir utiliser ldapmodify : il faut préciser le dn de l'objet à modifier, puis le type de changement à lui apporter. Pour une zoologie des types de changements, se rendre [ici](http://www.zytrax.com/books/ldap/ch8/#changetype). Ensuite, il faut préciser les attributs que l'on modifie. On sépare les modifications par des lignes contenant juste un tiret d'union. dn: uid=peb,ou=users,dc=localdomain changetype: modify replace: prenom prenom: PE - delete: homeDirectory homeDirectory: /ghome/peb - add: homeDirectory homeDirectory: /home/peb - delete: loginShell - add: loginShell loginShell: /bin/zsh - add ajoute un ou plusieurs attributs de même type. - delete en efface un ou plusieurs s'ils sont spécifiés (cf homeDirectory), tous si on se contente d'annoncer un delete: loginShell, par exemple. - Pour remplacer un attribut par un autre, on peut l'effacer, et en ajouter un nouveau, ou alors utiliser replace, qui remplace tous les attributs par la même valeur. Les trois méthodes possibles pour faire des corrections ont été utilisées ci-dessus. On sauvegarde ce LDIF dans /tmp/modif.ldif, et on appelle alors la commande $ ldapmodify -H ldap://127.0.0.1 -D "cn=admin,dc=localdomain" -W -f /tmp/modif.ldif On donne le mot de passe, et le tour est joué. Pour effacer des entrées, on peut utiliser ldapdelete, ou ldapmodify avec le changetype delete. De la doc, encore de la doc. =========== - [Zytrax](http://www.zytrax.com/books/ldap/), toujours aussi complet - [OpenLdap](http://openldap.org), toujours gérants du projet openldap - Google, et vos amis. Bonne lecture. :o)