Licence CC BY

Travailler avec LDAP en 2022

Un peu comme les bases de données, mais en moins pratique…

Salut les agrumes,

J’ai eu besoin récemment de travailler avec LDAP, et ce que j’y ai trouvé m’a pas mal surpris. Voici donc un billet pour préciser quelques points avec cette technologie, si ça peut vous éviter de perdre autant de temps que j’en ai perdu, ça sera toujours ça de pris.

Qu’est-ce que LDAP ?

Aujourd’hui, c’est une norme pour les systèmes d’annuaires, on peut le voir comme une base de données arborescente. Cf Wikipédia ou ldap.com pour plus de détails.

Un serveur LDAP ?

Historiquement, le serveur de référence est OpenLDAP. C’est beaucoup moins vrai aujourd’hui, et il existe des alternatives : plus de détails ici. Par contre, OpenLDAP continue à être la référence d’implémentation, pour le meilleur et pour le pire. En particulier, OpenLDAP est atrocement complexe à utiliser1, et à tendance à tout casser à chaque mise à jour (qui ne suivent absolument pas SemVer).

Enfin, tout ça c’est si vous avez la possibilité de choisir le serveur, ce qui est rarement le cas en pratique.

D’autre part, on peut utiliser le protocole LDAP pour se connecter à Azure Active Directory, le serveur d’annuaire de Microsoft, et ça sera souvent le cas en entreprise. C’est bien le même protocole qu’avec les autres implémentations, mais avec plein de microsofteries au milieu.

La documentation sur LDAP

La documentation d’OpenLDAP semble très complète, mais est difficilement utilisable : c’est plus un aide-mémoire à destination des personnes qui savent faire plutôt qu’une vraie documentation.

Le livre sur LDAP sur zytrax.com (non-libre mais en accès libre) m’a été d’une bien plus grande aide.

L’aide sur LDAP sur Internet

C’est le point le plus important de ce billet :

Personne, sur Internet, ne sait se servir de LDAP.

Je n’ai trouvé virtuellement aucune aide utile sur LDAP sur Internet. On est dans un culte du cargo massif et généralisé, chacun y va de son petit bout de code miracle à copier/coller. Dans le meilleur des cas, on a une solution qui était techniquement exacte, mais il y a plus de 10 ans – et complètement obsolète aujourd’hui.

Je suppose que c’est parce qu’en réalité personne ou presque ne développe quoi que ce soit avec LDAP (contrairement à SQL par exemple, qui est massivement utilisé par les développeurs eux-mêmes). Les utilisateurs de LDAP semblent être surtout des administrateurs système qui savent administrer un LDAP pour une utilisation simple, mais qui n’ont pas besoin de compétences avancées ni d’une bonne compréhension du truc.

Communiquer avec LDAP

En ligne de commande

Ça va dépendre de votre implémentation, mais avec OpenLDAP, il y a toute une batterie de commandes différentes selon ce que vous voulez faire : ldapsearch, ldapadd, ldapmodify… leur syntaxe est assez absconse, et les différentes options ont tendance à se cumuler pour faire des effets supplémentaires quand utilisées ensemble. Les amateurs de jeux de carte à collectionner ou de jeux de decks se sentiront chez eux, les autres… moins.

Par interface graphique

La seule interface graphique encore développée est Apache Directory Studio, basée sur Eclipse. Elle est d’une ergonomie catastrophique : elle ne respecte à peu près aucun des standards habituels de l’ergonomie, les informations sont éparpillées partout (dédicace à la gestion des logs) et utilise énormément de caches qui font qu’on travaille sur des données complètement périmées (vérifier la présence d’un bouton de rafraichissement sur l’interface pour avoir les données à jour).

Mais c’est la seule : le plugin pour les IDE JetBrains ou phpLDAPadmin ne sont plus développés depuis des années et ne fonctionnent plus. Et encore, quand je dis que Apache Directory Studio est « développé » c’est à prendre avec des pincettes : la dernière release officielle date de 2010, depuis on tourne sur des « milestones » d’une future version 2.0 (qui sont en réalité la seule version utilisable).

Via les API

À voir ce que vous avez comme API disponible dans votre langage.

Dans le cas de la JVM, c’est la jungle : plein de monde s’est décidé à faire son implémentation dans son coin, beaucoup de ces implémentations ne sont plus maintenues et surtout sont inutilisables. Quand les implémentations ne sont pas partielles, elles sont scandaleusement lentes. Par exemple, sur un projet, on a gagné un facteur 100 (cent) sur les performances juste en changeant la bibliothèque de connexion au LDAP – pas le serveur LDAP, hein : juste la bibliothèque de connexion.

Si vous utilisez la JVM, je conseille UnboundID LDAP SDK, qui est encore maintenue, est complète et a des bonnes performances.

Des points internes à LDAP

Du vocabulaire

« DN » est l’identifiant d’une entrée LDAP.

On appele « bind » l’action de s’authentifier. Le login est donc un « bind DN ».

Les objets LDAP ont des classes (une ou plusieurs classes par objet), qui elles-mêmes ont des attributs (obligatoires ou non). C’est la présence de ces attributs qui déclenche certains comportements, comme la possibilité de s’authentifier avec un objet (qui a donc un attribut de mot de passe).

Tout attribut non déclaré dans une des classes de l’objet est interdit, sauf si l’objet a une classe qui le permet et dont j’ai oublié le nom. Certains attributs sont obligatoires (c’est la classe qui décide quels attributs sont obligatoires).

Des formats de fichier

Le format LDIF (l’équivalent du SQL pour LDAP) est très pinailleur sur des choses comme la longueur de ligne, la présence de sauts de lignes à des endroits précis, ou la présence d’un tiret seul sur une ligne (seul, c’est vraiment seul, sans espaces ni rien).

dn: uid=myaccount,ou=accounts,dc=example,dc=com
changetype: modify
add: description:
description: this is my account
-
replace: homeDirectory
homeDirectory: /bin/bash
-

dn: uid=youraccount,ou=accounts,dc=example,dc=com
changetype: modify
add: description:
description: this is your account

Les deux premiers changements (séparés par un - sur une ligne et pas de ligne blanche) concernent la même entrée uid=myaccount,ou=accounts,dc=example,dc=com ; le - suivi d’une ligne blanche indique qu’on va gérer une autre entrée2.

Le format de définition des schémas (classes et attributs) est particulièrement abscons, notamment à cause de la présence d’OID, des identifiants qui se présentent comme une suite de nombres. Voyez plutôt :

        attributetype ( 1.1.2.1.1 NAME 'myUniqueName'
                DESC 'unique name with my organization'
                EQUALITY caseIgnoreMatch
                SUBSTR caseIgnoreSubstringsMatch
                SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
                SINGLE-VALUE )
                
        attributetype ( 1.1.2.1.2 NAME 'myPhoto'
                DESC 'a photo (application defined format)'
                SYNTAX 1.3.6.1.4.1.1466.115.121.1.40
                SINGLE-VALUE )

L’entrée SYNTAX définit le type de données, 1.3.6.1.4.1.1466.115.121.1.15 désigne une chaine de caractères Unicode, et 1.3.6.1.4.1.1466.115.121.1.40 une suite arbitraire d’octets. C’est pourtant clair, non ?

Le pire, c’est que ces fichiers ne sont même pas utilisés directement : ils sont d’abord convertis en LDIF – qui sont tellement complexes qu’il est vain de vouloir les écrire à la main.

Un détail amusant avec les OID : on peut acheter (je ne sais pas comment) son propre préfixe, pour pouvoir créer des classes et des attributs publics sans risque de confusion avec ceux d’autres entreprises.

La conservation d’état (protocole stateful)

(Ce point dépend peut-être de votre bibliothèque, ici c’est testé avec UnboundID).

Le protocole LDAP est stateful, c’est-à-dire qu’il conserve un état d’une requête sur l’autre, et que le résultat des requêtes va dépendre de cet état préalable. Ça a un côté très pratique pour rendre le protocole peu verbeux en interne (il n’y a pas besoin de répéter des informations déjà connues du serveur) mais pose aussi d’énormes problèmes de cohérence et de reproductibilité, au point qu’aujourd’hui on essaie au maximum d’utiliser des communications sans état (stateless).

L’impact le plus gênant, c’est le couplage entre l’état de la connexion au LDAP et l’opération d’authentification bind. Cette opération va permettre de savoir si un couple (DN, credential)3 a le droit de se connecter au LDAP. Le problème, c’est que cette opération va aussi modifier l’utilisateur connecté au LDAP en remplaçant les informations existantes par celle du nouveau bind.

Ainsi, votre connexion partagée avec des droits d’administration peut se retrouver remplacée par une connexion avec un utilisateur normal, ou par une connexion non fonctionnelle si le bind a échoué, ou n’importe quelle combinaison catastrophique en terme de fonctionnement voire de sécurité.

Spécial OpenLDAP

On ne peut pas changer le DN racine d’une base OpenLDAP une fois cette base créée.


  1. Dédicace à la disparition du fichier de configuration compréhensible, remplacé par des commandes LDAP à lancer sur la base de données LDAP de configuration… résultat, le moyen le plus simple d’initialiser une configuration OpenLDAP moderne est… d’utiliser l’ancien fichier de config, et de lui accoler la moulinette (officielle, heureusement) de compatibilité.
  2. Ça devient amusant quand on découvre que la nouvelle version majeure de sa bibliothèque – la seule à recevoir encore des correctifs de sécurité – ne gère pas de fichier LDIF contenant des -… ce qui impose immédiatement et irrévocablement de changer de bibliothèque.
  3. Respectivement nommés « identifiant » et « mot de passe » hors de l’univers LDAP.


En résumé, si vous devez développer quelque chose avec LDAP, tant que vous vous contenterez de faire des opérations simples (comme vérifier qu’un utilisateur peut se connecter) avec une bibliothèque d’API performantes, ça devrait bien se passer.

Dès que vous essayerez quelque chose de plus complexe, préparez-vous à lire des tonnes de documentation pour comprendre ce qu’il se passe, à faire beaucoup d’essais avec des outils à l’ergonomie inexistante ; et surtout : n’espérez pas trouver de l’aide sur Internet.


Logo : Logo de ldap.com.

26 commentaires

En particulier, il est prévu un schéma d’adressage qui facilite la vie si on veut un accès web (au moins pour les requêtes) là avec SQL on doit réinventer le REST.

Gil Cot

Je ne vois pas le rapport : tu attaques ta base de données (au sens large) directement en REST, sans aucune couche de contrôle au milieu ? Personnellement, je ne fais pas ça.

C’est aussi et surtout un protocole client-serveur qui permet à n’importe quel client LDAP de pouvoir dialoguer avec n’importe quel serveur LDAP : côté bases de données ce n’est pas du tout la même chanson…

Gil Cot

Ça, c’est un vrai avantage LDAP, il est vrai.

Pour avoir écrit des commandes sqlsel/sqladd/sqldelete/sqlchange/etc., je peux t’assure qu’on abouti au même résultat : on fait peu de chose, ou on a des options qui permettent d’adresser plus de cas. certains éditeurs de SGBDR proposent aussi des commandes pour faire des opérations qu’on pourrait faire en lançant un client dans lequel on taperait les directives équivalentes ; c’est juste pas pratique (i.e. scriptable tout ça.) Je t’assure que tu peux utiliser les commandes ldap* sans faire de LDIF ;) Mais OpenLDAP a prévu que ces mêmes commandes puissent traiter des fichiers de ce format : c’est utile pour passer plusieurs ordres, c’est la même logique/similitude qui fait que les clients SQL permettent de charger des fichiers de requêtes… Par contre, il faut effectivement savoir lire le format LDIF qui est celui de sortie de ldapsearch, et il faut se tourner vers ldap-attributes-selector par exemple si tu veux directement du CSV (beurk) Si tu veux travailler dans un client et non via des commandes, je te conseillerai de regarder du côté de : ldapvi —avec le binding de l’éditeur vi— ou shelldap —avec plutôt l’analogie avec le shell : cat/read et ls pour lire, grep pour chercher, vi/edit pour modifier, touch/create pour ajouter, etc.— ou ldapclient —qui est un client GUI et une fenêtre pour utiliser le binding SQL comme tu souhaites— et probablement d’autres.

Gil Cot

Je crois qu’en fait tu mets en exergue une différence fondamentale dans nos approches du truc, ce qui explique ton incompréhension face à mon billet.

Pour moi, la notion de « commandes scriptables » n’est en aucun cas un argument. Parce que je déteste les scripts shell. Ces machins n’ont pratiquement aucun avantage : c’est très vite illisible, c’est presque impossible à tester et à débugger, c’est pourri de comportements spécifiques1, c’est pas spécialement portable (voire pas du tout si Windows est dans la boucle), tu dois vérifier à la main que chaque exécutable existe ou être prêts à gérer des erreurs horribles3, il y a des tonnes de pièges2, le risque de laisser ton système dans un état instable est immense… Pour moi tout script qui dépasse les 10 lignes, contrôles inclus, devrait être écrit dans un vrai langage de programmation.

C’est d’autant plus vrai maintenant avec les techniques de déploiement modernes, qui nécessitent de moins en moins d’interventions directes sur le serveur.

La conséquence, c’est que je n’ai aucun intérêt à avoir des commandes multiples, qu’elles soient « scriptables » ou non. Par exemple, ça ne me viendrait jamais à l’idée d’avoir des commandes sqlsel/sqladd/sqldelete/sqlchange. Je préfère de loin une commande unique sql qui mange tous les fichiers .sql que lui donne : je n’ai qu’une syntaxe à apprendre, et aucun risque d’incohérence entre les différentes commandes. Accessoirement ça me permet de faire du transactionnel (quoique avec LDAP…).

Ce qui m’intéresse, c’est donc des bonnes bibliothèques d’intégration avec des langages de programmation, et une interface qui n’oublie pas qu’on a maintenant un peu mieux qu’un écran en 80 x 25 caractères et un clavier 60 touches.

PS : j’ai fait du bash (j’ai même programmé un jeu de dames « graphique » en réseau local avec, pendant mes études). Mais chaque jour que j’ai passé à maintenir des scripts m’a convaincu un peu plus que ce système était fait pour des petits outil one-shot de dix, quinze ligne grand max. En aucun cas pour des programmes complexes de plusieurs centaines de lignes comme on en trouve trop souvent.


  1. Un exemple classique : un script qui contient du spécifique bash se retrouve exécuté avec sh.
  2. On en parle de tous ces scripts qui plantent dès qu’il y a un nom de fichier ou de dossier avec une espace dedans ?
  3. Dédicace à ce script IBM qui partait du principe que tu avais ed (et donc ne le vérifiait pas), mais cette installation de l’hébergeur qui n’avait pas ed « parce que plus personne n’utilise cette antiquité ». L’état du serveur quand le script plantait était tel que le plus simple a été de le réinstaller de 0.
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte