Cette page est une introduction technique au fonctionnement d'une base LDAP. Elle ne répond pas aux questions « comment câbler ? », ou « comment gère-t-on LDAP au Cr@ns ? ». Pour cela, et pour les liens entre LDAP et crans, on pourra se référer aux pages suivantes :
Gnay ?
Gnay ??
LDAP (Light Directory Access Protocol) est un protocole d'accès à des annuaires distants, permettant d'y lire des données, ou de les modifier. Avec le temps, c'est devenu une norme, proposant en plus du protocole d'accès un ensemble de structures de mise en place d'annuaires.
OpenLDAP est un projet libre proposant l'implémentation du protocole sur certains systèmes Unix, dont Debian. C'est actuellement via une base de données LDAP que le Crans gère les données concernant ses adhérents, leurs machines, ou les clubs. La version d'openldap présente sous squeeze a beaucoup modifié la façon de procéder pour l'installation, et surtout la maintenance d'un tel annuaire. Cette page vous propose un howto pour y voir un peu plus clair.
Elle ne se substitue pas à la doc, ni à un cerveau.
OpenLdap ? Où ça ?
On va faire une install sous squeeze, pour wheezy, il faudra attendre son passage à stable.
Packaging
Le paquet Debian proposé pour OpenLDAP est slapd. On peut également installer ldap-utils, et shelldap (pour l'exploration).
# apt-get install slapd ldap-utils shelldap
A priori, un mot de passe pour l'administrateur vous sera demandé, mettez « plop », ou tout autre mot de passe de votre choix.
Théorie
Avant de commencer à faire joujou avec le paquet, un peu de théorie sur ce qu'il se passe dans une base LDAP.
Arbre
LDAP propose une façon de ranger des objets avec une hiérarchie et une structure d'arbre. Chaque objet est un nœud, et chaque nœud peut avoir un parent, et plusieurs enfants. Le nœud a également des attributs propres, il y a donc le contenu du nœud, et ses ascendants/descendants, qui sont deux choses très différentes.
Par exemple, un adhérent du Crans est un nœud fils du nœud data du Crans. Le nœud data a très peu de propriétés, c'est juste une façon de hiérarchiser le contenu de la base LDAP, en revanche, chaque nœud adhérent a beaucoup de propriétés, et également des enfants (les machines de l'adhérent), qui ont elles leurs propres attributs.
Cette hiérarchie permet de classer les objets plus facilement par exemple qu'avec postgresql, où une jointure serait nécessaire pour trouver les machines de l'adhérent toto.
Un autre avantage, c'est que certains attributs peuvent être exprimés plusieurs fois pour un objet. Par exemple, l'adhérent becue a plusieurs attributs historique.
On peut bien évidemment indexer certains attributs (les recherches dans une base se faisant suivant les indexes), et faire pas mal d'autres choses, mais nous verrons cela plus tard.
Identifier un nœud
Distinguished Name
Pour se déplacer dans un arbre, il faut pouvoir identifier un nœud formellement, sans quoi, comment savoir où on se trouve. Tous les nœuds d'une base LDAP possèdent pour cela un attribut dont le nom est « dn » (distinguished name), celui-ci est unique, et propre à chaque nœud, il consiste en le nom du nœud présent, suivi du nom des nœuds parents.
Par exemple, au Crans, le nœud "aid=3775" est fils du nœud "ou=data", lui-même fils du nœud "dc=crans,dc=org". Le dn de ce nœud est donc "aid=3775,ou=data,dc=crans,dc=org".
Racine : dc=crans,dc=org
Le nœud "dc=crans,dc=org" est un peu particulier, en effet, il est tout en haut de l'arbre. On l'appelle nœud racine, ou encore nœud suffixe. Il est le seul existant au début (à nous de créer une structure à notre base ldap !).
dc est un nom courant dans LDAP, il signifie « domain component »
ou signifie « organizational unit »
cn signifie « common name »
Etc...
Parler le LDAP
Il existe plusieurs façons de parler à une base LDAP, nous n'en verrons qu'une ici, c'est celle qui me semble la plus adaptée si on veut éviter de faire des bêtises. Il s'agit d'employer le format LDIF. Il permet de visualiser et/ou modifier textuellement des entrées LDAP.
Contexte
Soit un objet dn="aid=1337,ou=data,dc=crans,dc=org". Celui-ci représente un adhérent du Crans, il a pour attributs des éléments de la liste suivante :
aid (id administratif)
nom
prenom
chbre
mail
telephone
- login (login crans, pour ceux ayant un compte) (*)
- uid (id sur les machines unix) (*)
- historique
- canonicalAlias (*)
- paiement
- userPassword
- etudes
- droits
- controle
Etc...
Les attributs avec une astérisque sont nécessaires pour un compte Crans (donc facultatifs, car rien n'oblige à avoir un compte Crans), ceux avec un ! sont indispensables et uniques.
Prenons un exemple.
dn: aid=3775,ou=data,dc=crans,dc=org objectClass: adherent objectClass: cransAccount objectClass: posixAccount objectClass: shadowAccount aid: 3775 blacklist: 1291156740$1291231238$ipv6_ra$toto: plop.crans.org canonicalAlias: Pierre-Elliott.Becue carteEtudiant: 2010 carteEtudiant: 2011 carteEtudiant: 2012 charteMA: TRUE chbre: O4242g cn: Pierre-elliott Becue controle: pc derniereConnexion: 1351222601 droits: Imprimeur droits: Moderateur droits: Nounou etudes: ENS etudes: 3 etudes: A1 gecos: Pierre-elliott Becue,,, gidNumber: 100 historique: 04/11/2011 17:38, respbats : debit 0.18 Euros [impression: /var/im pression/fichiers/becue/TD7.pdf] historique: 04/11/2011 17:39, respbats : debit 0.14 Euros [impression: /var/im pression/fichiers/becue/TD9Bis.pdf] historique: 04/11/2011 17:39, respbats : debit 0.17 Euros [impression: /var/im pression/fichiers/becue/TDAF8.pdf] historique: 08/11/2011 16:28, respbats : debit 0.03 Euros [impression: /var/im pression/fichiers/becue/kh6.pdf] historique: 08/11/2011 17:05, respbats : debit 0.38 Euros [impression: /var/im pression/fichiers/becue/kh1.pdf] homeDirectory: /home/becue loginShell: /bin/zsh mail: becue mailAlias: peb nom: Becue paiement: 2010 paiement: 2011 paiement: 2012 prenom: Pierre-Elliott solde: 0.0 tel: 0XXXXXXXXX uid: becue uidNumber: 2573 userPassword: {SSHA}sB+dHA1zBf/w4KZU3l5mFh+rvxHJo8D1
Ça, c'est moi. (Enfin, j'ai viré pas mal de trucs, pour des questions de respect de vie privée).
C'est beau, c'est lisible (sauf le userPassword, les boules, hein ?), mais dans la base LDAP, ça ne ressemble pas à cela. Ceci est une portion LDIF de la base LDAP.
Par exemple, si on voulait créer l'objet aid=3775 dans une base LDAP ne le contenant pas encore, il suffirait d'écrire tout ce bazar dans un fichier PEB.ldif, et de le balancer à LDAP, via la commande idione. Il fait le reste tout seul.
Pour modifier quelqu'un, la syntaxe est du même ordre d'idée, sauf qu'on ne précise que ce qu'on modifie, ainsi que l'action
dn: aid=3775,ou=data,dc=crans,dc=org changetype: modify (ou delete, ou autre chose) delete: mailAlias
dn: aid=3775,ou=data,dc=crans,dc=org changetype: modify replace: telephone telephone: 0642424242
On ne peut supprimer un attribut indispensable sans supprimer l'objet tout entier. Quand on supprime un attribut multiple, toutes ses occurrences sont détruites. De manière générale, le remplacement efface toutes les occurrences de l'attribut et fournit un ou des remplacements.
Généralement, toute altération de la base ldap ou presque passe par du LDIF.
Si un LDIF est mal fait, LDAP plante généralement en retournant une erreur assez compréhensible. En cas de gros souci avec une erreur, google est ton ami.
Choisir un schéma
Il est de bon ton de choisir la tête du schéma que l'on veut pour notre base. Généralement, on crée un schéma perso, basé sur des schémas standards. Il y a pas mal de fichiers de schéma dans /etc/ldap/schema, que l'on peut appeler dans un fichier de config via la commande include.
Voici un exemple de schéma, avec celui du Crans.
# # Définitions des attributs et objets pour le Crans # # Géré par BCfg2 (plugin Cfg) # # L'OID 1.3.6.1.4.1.25368 a été attribuée au Crans par l'IANA # * OID attributs : 1.3.6.1.4.1.25368.2.* # * OID classes : 1.3.6.1.4.1.25368.3.* # # Les attributs attributetype ( 1.3.6.1.4.1.25368.2.1 NAME 'aid' DESC 'Adhérent Identifier' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) attributetype ( 1.3.6.1.4.1.25368.2.2 NAME 'nom' DESC 'Nom adhérent' SUP name ) attributetype ( 1.3.6.1.4.1.25368.2.3 NAME 'prenom' DESC 'Prénom adhérent' SUP name ) attributetype ( 1.3.6.1.4.1.25368.2.4 NAME 'tel' DESC 'Numéro de téléphone adhérent' EQUALITY telephoneNumberMatch SUBSTR telephoneNumberSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.50 ) attributetype ( 1.3.6.1.4.1.25368.2.5 NAME 'paiement' DESC 'Année de paiement adhérent' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 ) attributetype ( 1.3.6.1.4.1.25368.2.6 NAME 'carteEtudiant' DESC 'Année de carte étudiant fournie' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 ) attributetype ( 1.3.6.1.4.1.25368.2.7 NAME 'mailAlias' DESC 'Alias mail' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} ) attributetype ( 1.3.6.1.4.1.25368.2.8 NAME 'canonicalAlias' DESC 'Alias mail canonique' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} ) attributetype ( 1.3.6.1.4.1.25368.2.9 NAME 'etudes' DESC 'Études adhérent' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1024} ) attributetype ( 1.3.6.1.4.1.25368.2.10 NAME 'chbre' DESC 'Chambre adhérent' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.44{16} SINGLE-VALUE ) [...] # Les classes objectclass ( 1.3.6.1.4.1.25368.3.1 NAME 'proprio' SUP top ABSTRACT DESC 'Propriétaire (classe abstraite)' MUST ( nom $ chbre $ historique ) MAY ( paiement $ info $ blacklist $ controle ) ) objectclass ( 1.3.6.1.4.1.25368.3.2 NAME 'adherent' SUP proprio DESC 'Adhérent' MUST ( aid $ prenom $ tel $ mail ) MAY ( carteEtudiant $ etudes $ postalAddress $ mailInvalide $ charteMA $ adherentPayant $ typeAdhesion $ carteRFID ) ) objectclass ( 1.3.6.1.4.1.25368.3.3 NAME 'club' SUP proprio DESC 'Club' MUST ( cid $ responsable ) MAY ( imprimeurClub $ accesRFID ) ) objectclass ( 1.3.6.1.4.1.25368.3.10 NAME 'machine' SUP top ABSTRACT DESC 'Machine (classe abstraite)' MUST ( mid $ macAddress $ host $ ipHostNumber $ historique ) MAY ( info $ blacklist $ hostAlias $ exempt $ portTCPin $ portTCPout $ portUDPin $ portUDPout $ dnsIpv6 $ machineAlias ) ) objectclass ( 1.3.6.1.4.1.25368.3.11 NAME 'machineCrans' SUP machine DESC 'Machine appartenant au Crans' MAY ( prise $ nombrePrises ) ) objectclass ( 1.3.6.1.4.1.25368.3.12 NAME 'borneWifi' SUP machine DESC 'Borne wifi' MUST ( canal $ puissance $ hotspot ) MAY ( prise $ positionBorne $ nvram ) ) objectclass ( 1.3.6.1.4.1.25368.3.13 NAME 'machineWifi' SUP machine DESC 'Machine wifi' MUST ( ipsec ) ) objectclass ( 1.3.6.1.4.1.25368.3.14 NAME 'machineFixe' SUP machine DESC 'Machine adhérent fixe' ) objectclass ( 1.3.6.1.4.1.25368.3.20 NAME 'cransAccount' SUP top AUXILIARY DESC 'Compte Crans' MAY ( mailAlias $ canonicalAlias $ droits $ solde $ contourneGreylist $ rewriteMailHeaders $ derniereConnexion $ homepageAlias $ compteWiki $ mailExt $ GPGFingerPrint ) ) [...]
Bon, la config réelle est bien plus longue, l'idée est ici de montrer un peu à quoi ressemble ce schéma, qui ne se balance pas directement dans la base LDAP (ceci n'est PAS un ldif, ça ne marcherait juste pas).
Ce fichier est par exemple présent dans /etc/ldap/schema, et est chargé au premier lancement de la base LDAP (après, il ne sert plus à rien, il est dumpé dans /etc/ldap/slapd.d/. Il y a une version /etc/ldap/schema/crans.ldif, formatée pour être envoyée à ldap.
L'idée est de définir des attributs et des classes d'objets. Au Crans, on a des identifiants uniques auprès de l'IANA pour ce faire (la suite toute moche 1.3.6....). La signification des attributs est assez claire. Regardons cela.
attributetype ( 1.3.6.1.4.1.25368.2.4 NAME 'tel' DESC 'Numéro de téléphone adhérent' EQUALITY telephoneNumberMatch SUBSTR telephoneNumberSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.50 )
On définit un attribut comme un élément attributetype (l'élément n°4 dans le cas présent), puis on fournit son identifiant, puis son nom, sa description, et enfin, les condition d'égalité de l'attribut, ou d'un sous-ensemble de celui-ci. On propose enfin une syntaxe, qui correspond à une norme. (à trouver sur le net).
On définit de façon voisine une classe d'objet
objectclass ( 1.3.6.1.4.1.25368.3.2 NAME 'adherent' SUP proprio #STRUCTURAL DESC 'Adhérent' MUST ( aid $ prenom $ tel $ mail ) MAY ( carteEtudiant $ etudes $ postalAddress $ mailInvalide $ charteMA $ adherentPayant $ typeAdhesion $ carteRFID ) )
La logique est un peu identique à la précédente. On définit un objet, SUP définissant l'objet dont il hérite. DESC est sa description, MUST la liste des attributs qu'il doit avoir, et MAY ceux qu'il peut avoir en plus. Il y a un #STRUCTURAL qui traîne dans le schéma. Après SUP blop, on peut ne rien mettre, mettre AUXILIARY, STRUCTURAL ou ABSTRACT. ABSTRACT signifie que l'objet est abstrait, on ne peut l'instancier directement. STRUCTURAL qu'il est concret, et AUXILIARY qu'on peut ajouter aux attributs objectClass d'un objet cette classe. Par défaut, on est en STRUCTURAL, si je ne me trompe.
Bon, c'est pas tout ça, mais maintenant, va falloir configurer from scratch.
Configuration
Méthodes
Configurer LDAP depuis son passage à squeeze n'est pas chose aisée. Déjà, après l'installation du paquet, une config par défaut est générée. Celle-ci peut suffire, mais pas forcément. Si vous voulez partir avec votre propre config from scratch, la première à chose à faire est la suite de commande suivante :
# service slapd stop # rm -rf /etc/ldap/slapd.d/*
Comme ça plus de config.
Plusieurs solutions s'offrent alors. La première est de créer des fichiers LDIF de config par vous-même, à mon avis c'est une perte de temps, la seconde est de générer un fichier slapd.conf, et un schéma qui vous convient, puis de générer la config LDAP qui va bien avec slaptest, via cette commande.
# sudo -u openldap slaptest -f /etc/ldap/slapd.conf -F /etc/ldap/slapd.d
Je vais utiliser la seconde.
Une fois votre config en place, c'est une très mauvaise idée de toucher à celle-ci en modifiant spontanément les fichiers de /etc/ldap/slapd.d/, c'est la config de votre base, et tout péter peut avoir de drôles de conséquences. L'idéal par la suite est de faire des modifications à chaud via fichier LDIF (même pour toucher au schéma), et la commande
$ ldapmodify -H ldap://127.0.0.1 -D "credentials" -W -f fichier.ldif
Dans la mesure du possible, essayez de garder votre fichier de schéma à jour quand vous le modifiez à chaud.
Création d'un fichier de configuration
On va attaquer slapd.conf, ci-après un fichier complet, dans lequel j'ai ajouté quelques autorisations d'accès pour dovecot et postfix (qu'il faut configurer en conséquence), ainsi que de quoi interfacer les groupes posix et ldap. De même, j'ai également implémenté l'interface PAM/LDAP. Pour ce faire, il suffit d'installer et configurer les paquets nslcd (interface NSS avec LDAP pour PAM) et nscd (cache pour PAM)
####################################################################### # 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/linkki.schema # Emplacement du pidfile pidfile /var/run/slapd/slapd.pid # Arguments passés au serveur argsfile /var/run/slapd/slapd.args # Niveau de journalisation loglevel 1 # Stockage des modules modulepath /usr/lib/ldap moduleload back_bdb # Limitation du nombre de réponses sizelimit 1000000000 # Méthode pour crypter les mots de passe password-hash {SSHA} ############################################################### # Directives de configuration pour le backend bdb backend bdb ####################################################################### # Configuration de la base de données principale database bdb # Base de recherche principale suffix "dc=linkki,dc=crans,dc=org" # Répertoire de stockage de la base de données directory "/var/lib/ldap" # Options d'indexation pour la base de données index entryUUID,entryCSN eq index objectClass,memberUid,uid,uidNumber eq index cn,sn eq,approx,sub index alias,mail eq index nom,prenom eq,approx,sub index ville eq,subinitial index droits eq # Sauvegarde des timestamps de modifications des objets lastmod on # Réplication de la base # LDAP a besoin de la déclaration du rootdn rootdn "cn=admin,dc=linkki,dc=crans,dc=org" rootpw "{SSHA}P1tqcYqrzvY5q4po+1nBObhiMNJd5yt4" # Plop # 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=linkki,dc=crans,dc=org" 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=linkki,dc=crans,dc=org$$" attrs=mail,alias,uid,mail,entry,objectClass by dn.regex="cn=postfix,dc=linkki,dc=crans,dc=org" read by * break # Pour dovecot access to dn.regex="^uid=[0-9]+,ou=users,dc=linkki,dc=crans,dc=org$$" attrs=uid,homeDirectory,uidNumber,gidNumber,userPassword,objectClass,entry by dn.regex="cn=dovecot,dc=linkki,dc=crans,dc=org" read by * break # Informations accessibles à tous access to dn.regex="^uid=[0-9]+,ou=users,dc=linkki,dc=crans,dc=org$$" attrs=uid,homeDirectory,uidNumber,gidNumber,gecos,objectClass,entry by anonymous read by * break access to dn="ou=users,dc=linkki,dc=crans,dc=org" 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 la réplication peut tout lire # can read everything. access to * by dn.regex="cn=admin,dc=linkki,dc=crans,dc=org" write by sockname.regex="ldapi" read by dn.regex="cn=readonly,dc=linkki,dc=crans,dc=org" read by self read by * none
Si des choses sont vraiment pas claires dans ce fichier, mettez un commentaire à la suite.
Création d'un schéma
Pas mal d'objets et attributs existent déjà dans les schémas fournis avec le paquet. Attention à ne pas les réinventer. Voici un exemple.
# Attributs attributetype ( 1.3.6.1.4.1.42424.2.1 NAME 'nom' DESC 'Name of the user' SUP name SINGLE-VALUE ) attributetype ( 1.3.6.1.4.1.42424.2.2 NAME 'prenom' DESC 'First name of the user' SUP name SINGLE-VALUE ) attributetype ( 1.3.6.1.4.1.42424.2.3 NAME 'tel' DESC 'Phone number of the user' EQUALITY telephoneNumberMatch SUBSTR telephoneNumberSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.50 ) attributetype ( 1.3.6.1.4.1.42424.2.4 NAME 'alias' DESC 'Alias mail' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} ) attributetype ( 1.3.6.1.4.1.42424.2.5 NAME 'ville' DESC 'Town' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} SINGLE-VALUE ) attributetype ( 1.3.6.1.4.1.42424.2.6 NAME 'droits' DESC 'Rights' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{64} ) # Classes objectclass ( 1.3.6.1.4.1.42424.3.1 NAME 'userAccount' SUP top STRUCTURAL DESC 'User account' MUST ( nom $ prenom $ ville $ mail ) MAY ( droits $ alias $ tel ) ) objectclass ( 1.3.6.1.4.1.25368.3.25 NAME 'groupeUnix' SUP top AUXILIARY DESC 'Pour pouvoir générer dynamiquement les groupes posix' MAY ( memberURL ) )
Ces deux fichiers installés là où il faut, on peut faire un slaptest.
# sudo -u openldap slaptest -f /etc/ldap/slapd.conf -F /etc/ldap/slapd.d
On a alors un système presque fonctionnel. Cependant, on n'a aucun accès en écriture à la configuration de la base, qui se trouve dans une base à part, dont le dn est "cn=config". Le fichier de conf se trouve dans /etc/ldap/slapd.d/cn=config/olcDatabase={0}config. Il faut donc éditer ce fichier, et chercher la ligne commençant par olcRootDN. Plusieurs choix s'offrent à vous. Pour ma part, je n'ai pas envie de m'encombrer d'un administrateur par base, j'ai donc remplacé cette ligne par :
olcRootDN: cn=admin,dc=linkki,dc=crans,dc=org
Ainsi, mon admin de base principale peut configurer ldap. Attention, il ne faut pas donner de mot de passe à cet utilisateur, vu qu'il est déjà défini dans la base principale.
Cela fait, on relance slapd via service slapd start.
Il faut maintenant remplir notre base (totalement vide), avec quelques entrées indispensables. J'ai pour ma part créé le nœud racines, et deux sous-nœuds. Pour cela, on crée un fichier ldif first.ldif, et on met ceci dedans :
dn: dc=linkki,dc=crans,dc=org objectClass: top objectClass: dcObject objectClass: organization dc: linkki o: linkki.crans.org dn: ou=users,dc=linkki,dc=crans,dc=org objectClass: top objectClass: organizationalUnit ou: users dn: ou=Group,dc=linkki,dc=crans,dc=org objectClass: top objectClass: organizationalUnit ou: Group
On importe ce fichier via la commande :
$ ldapadd -H ldap://127.0.0.1 -D "cn=admin,dc=linkki,dc=crans,dc=org" -W -f first.ldif
En lançant shelldap avec les bons crédentials, vous pourrez commencer à parcourir votre arbre.
Système de réplicas
OpenLDAP embarque un système de réplication, basé sur syncrepl, et syncprov. syncprov est le module de ce qu'on appelle un provider, c'est-à-dire un serveur maître qui fournit le contenu d'une base LDAP, syncrepl est le module des esclaves (replicas). Grâce à ce système, on duplique la base en plusieurs endroits sans se fatiguer, et toute modification sur la base maîtresse est envoyée aux esclaves sans délai. On peut même synchroniser une partie de la config, ou toute la config, ou, de manière plus intelligente, les schémas.
Dans la suite, nous n'allons travailler qu'en LDIF, car passer par slapd.conf serait aberrant, ne serait-ce que pour la lisibilité (les bases n'y sont pas du tout séparées les unes des autres…)
Pour commencer, on crée un compte de réplicas, qui a un droit de lecture partout : cn=replica,dc=linkki,dc=crans,dc=org. On lui fournit aussi un mot de passe. Vous savez déjà faire.
Ensuite, on configure le serveur maître.
dn: cn=module{0},cn=config changetype: modify add: olcModuleLoad: syncprov - dn: olcOverlay={0},olcDatabase={1}bdb,cn=config changetype: add objectClass: olcOverlayConfig objectClass: olcSyncProvConfig olcOverlay: {0}syncprov - dn: olcOverlay={0},olcDatabase={0}config,cn=config changetype: add objectClass: olcOverlayConfig objectClass: olcSyncProvConfig olcOverlay: {0}syncprov - dn: olcDatabase={0}config,cn=config changetype: modify add: olcAccess olcAccess: to * by dn.regex="cn=replica,dc=linkki,dc=crans,dc=org" read - dn: olcDatabase={1}bdb,cn=config changetype: modify add: olcAccess olcAccess: to * by dn.regex="cn=replica,dc=linkki,dc=crans,dc=org" read
Configurons maintenant un réplica :
dn: cn=module{0},cn=config changetype: modify add: olcModuleLoad: syncrepl - dn: olcDatabase={0}config,cn=config changetype: modify add: olcSyncrepl olcSyncrepl: rid=42 provider=ldap://linkki.crans.org bindmethod=simple timeout=0 network-timeout=0 binddn=cn=replica,dc=linkki,dc=crans,dc=org credentials=myveryverysecuredpassword filter="(objectClass=olcSchemaConfig)" searchbase="cn=schema,cn=config" scope=sub type=refreshAndPersist retry="30 20 300 +" - dn: olcDatabase={1}bdb,cn=config changetype: modify add: olcSyncrepl olcSyncrepl: rid=52 provider=ldap://linkki.crans.org bindmethod=simple timeout=0 network-timeout=0 binddn=cn=replica,dc=linkki,dc=crans,dc=org credentials=myveryverysecuredpassword filter="(objectClass=olcSchemaConfig)" searchbase="dc=linkki,dc=crans,dc=org" scope=sub type=refreshAndPersist retry="30 20 300 +"
Pour un détail des options, consultez la doc d'openLDAP.
- rid : un identifiant local unique du réplica.
- provider : le maître
- bindmethod : Méthode de connexion
- timeout : jamais
- network-timeout : jamais
- binddn : le dn avec lequel on se connect (doit être autorisé dans le maître en lecture)
- credentials : mot de passe
- filter : les objets à chercher et copier
- searchbase : la base dans laquelle chercher
- scope : précision (subtree est adapté pour de la synchro)
- type : le type de synchro souhaité
- retry : on réessaye 20 fois toutes les 30 secondes en cas de plantage, puis toutes les 300 secondes.
Reste à relancer slapd partout, et normalement, ça marche.
Si faire du ldif vous emmerde, vous pouvez éditer directement les fichiers dans /etc/ldap/slapd.d, mais seulement si vous savez exactement ce que vous faîtes.
Pour aller plus loin
Lire dans une base et écrire dans une base sont deux choses distinctes, créer un user readonly peut être adapté.
$ ldapadd -H ldap://127.0.0.1 -D "cn=admin,dc=linkki,dc=crans,dc=org" -W Enter LDAP password: dn: cn=readonly,dc=linkki,dc=crans,dc=org objectClass: top objectClass: simpleSecurityObject objectClass: organizationalRole cn: readonly description: readonly user userPassword: {SSHA}gjbbqz9A/cHXh/OzTx5jhU9m1H9sxKLL <CTRL+D>
PAM/LDAP
On peut synchroniser pam et ldap, il suffit pour cela d'installer nslcd et nscd. On remplit /etc/nslcd.conf.
# /etc/nslcd.conf # nslcd configuration file. See nslcd.conf(5) # for details. # The user and group nslcd should run as. uid nslcd gid nslcd # The location at which the LDAP server(s) should be reachable. uri ldap://127.0.0.1/ # The search base that will be used for all queries. base dc=linkki,dc=crans,dc=org # The LDAP protocol version to use. #ldap_version 3 # The DN to bind with for normal lookups. binddn cn=readonly,dc=linkki,dc=crans,dc=org bindpw plop # The DN used for password modifications by root. #rootpwmoddn cn=admin,dc=example,dc=com # SSL options #ssl off #tls_reqcert never # The search scope. scope sub
Et normalement, ça marche tout seul.
Par exemple, créons un utilisateur :
$ ldapadd -H ldap://127.0.0.1 -D "cn=admin,dc=linkki,dc=crans,dc=org" -W Enter LDAP password: dn: uid=maou,ou=users,dc=linkki,dc=crans,dc=org objectClass: top objectClass: posixAccount objectClass: shadowAccount objectClass: userAccount cn: maou description: user de test uid: maou uidNumber: 1003 gidNumber: 1003 homeDirectory: /home/maou loginShell: /bin/zsh userPassword: {SSHA}QOPOdSGFjxi4DIlFN5qErMqIEkfGROBT nom: Maou prenom: maou ville: Clichy mail: maou@linkki.crans.org droits: plouc tel: 0643342402 <CTRL+D>
Puis un groupe
$ ldapadd -H ldap://127.0.0.1 -D "cn=admin,dc=linkki,dc=crans,dc=org" -W Enter LDAP password: dn: cn=testgroup,ou=Group,dc=linkki,dc=crans,dc=org objectClass: top objectClass: posixGroup objectClass: groupeUnix cn: testgroup gidNumber: 1004 <CTRL+D>
Une fois l'utilisateur et le groupe ajouté, on peut s'assurer que nscd et nslcd tournent. Un id maou retournera alors les données de l'utilisateur maou en base ldap.
Documentation
La doc de zytrax, massive.