Vederlichte directories: Het LDAP protocol

Netwerkdatabases zijn er in soorten en maten. Sun kwam jaren terug al met NIS en de RPC portmapper, Novell kwam met Netware, de IETF met DNS en DHCP, enzovoort. Langzaamaan komt er een standaardprotocol in zicht dat deze kakofonie kan vervangen, en dat is het Lightweight Directory Access Protocol.

Dit artikel verscheen eerder in Windows & .NET Magazine van december 2002.

In het kader van de ISO netwerkstandaardisatie is gewerkt aan een directory-standaard, bekend als de X.500 standaard. Hoewel zeer flexibel, is X.500 nooit van de grond gekomen omdat het een erg zwaarwichtig systeem is, onder andere ook door de nogal breed bemeten netwerkstandaard van de ISO. Er is dan ook een vertaalslag gemaakt van X.500 naar TCP/IP netwerken, met het LDAP protocol als resultaat.

De Amerikaanse universiteit van Michigan heeft de ontwikkeling en standaardisatie van LDAP ingezet met de ontwikkeling van een TCP/IP-frontend voor X.500 directories. Dit werd zo'n populaire wijze om directories aan te spreken dat de X.500 interface nog nauwelijks werd gebruikt, en de tijd rijp werd voor een zelfstandige LDAP service. Heden ten dage zijn er daar diverse van, zowel commercieel als open source. Hoewel het volledige X.500 dus min of meer geflopt is, wint LDAP steeds meer aan terrein, en dat is goeddeels te danken aan het grondige denkwerk achter X.500.

Opbouw van de directory

Het datamodel van een LDAP directory heeft de vorm van een boom, waarin elke node een distinguishedName (dn) heeft, die ook wel met OID wordt aangeduid. Op elke node van de boom wordt een object opgeslagen, dat bestaan uit een variabel aantal attribuutdefinities.

De OID van een node beschrijft een pad vanaf de wortel van de boom omlaag tot aan de node. Elke stap op dat pad definieert een attribuutnaam en -waarde, die op die plek in de boom een unieke vertakking beschrijft. Dit vertakkings-identificerende attribuut staat bekend als een relatieve dn, of kortweg een RDN. Bijvoorbeeld, onder c=nl (de korte vorm voor countryName=nl) kan een unieke vertakking worden gedefinieerd met o=OpenFortress (organization=OpenFortress). Deze twee RDNs samen dienen als OID waneer ze samengeplakt worden tot o=OpenFortress,c=nl. De node de gevonden wordt door vanuit de root naar c=nl te lopen, en dan door naar o=OpenFortress, leidt tot een node waarin een object worden opgeslagen. Bovendien kan met een vertakking ou=Contacts (organizationalUnit=Contacts) worden uitgeweid met meer gedetailleerde informatie in weer een volgende node.

Een object op een bepaalde node van de boom bestaat uit een aantal attributen, die gewoon als naam/waarde mapping staan opgesomd. Bijvoorbeeld cn: Rick van Rein (commonName: Rick van Rein). Met als interessante eigenschap dat bepaalde attribuutnaam meervoudig kunnen voorkomen, zodat naast deze naam ook cn: Henderikus van Rein kan worden opgenomen.

Zaken rond attributen worden geregeld in een schema. Daarin wordt per objectClass vastgelegd welke attribuutnamen er in mogen en moeten voorkomen, en welke attributen meerwaardig mogen zijn. Verder wordt er syntax en de voor LDAP benodigde semantiek gegeven, ofwel het antwoord op de vraag hoe attributen vergeleken moeten worden. Dat houdt dus in dat sommige attributen gevoelig kunnen zijn voor hoofdletters vs. kleine letters, terwijl andere dat niet zijn, en dat weer andere attributen whitespace kunnen negeren.

Met ingang van de laatste versie van het protocol, namelijk LDAPv3, wordt het database-schema door de LDAP server geëxporteerd, opdat de client het kan controleren of interpreteren. Dit is een belangrijke ondersteuning voor zelfontworpen objectClasses. De link van een object naar het schema wordt gemaakt door de vermelding van een speciaal attribuut objectClass in het object. Ook dit attribuut mag meerwaardig zijn, zodat inheritance van datastructuren mogelijk is. De verplicht en optioneel aanwezige attributen per object mengen zich daarbij op de voor de hand liggende wijze.

LDAP services worden heden ten dage vooral gebruikt voor telefoonboeken, en in systeem- en netwerkbeheer. Denk bij het laatste bijvoorbeeld aan het opslaan van passwords. Met LDAP zijn die voldoende snel opvraagbaar over een netwerk. In het kader over LDIF is een voorbeeld van deze informatie te vinden.

Zoeken met LDAP

De opbouw van een directory is hiërarchish, en daar maakt het LDAP protocol gebruik van bij het zoeken naar gegevens. In principe kan dat op twee manieren worden gedaan.

De eerste zoekmethode is het browsen in een directory. Hierbij wordt begonnen in een bepaalde node van de boom, en kan naar wens een sub-boom worden opgengevouwen en binnengewandeld. Van elke node kan naar wens het object, ofwel de verzameling van opgeslagen attributen, worden opgevraagd. Er kan worden aangegeven dat alle attributen interessant zijn, of slechts enkele. De server kan ook worden gevraagd om de teruggekeerde nodes in een sub-boom te ordenen volgens bepaalde criteria.

De tweede zoekmethode is het zoeken in de boom, met een gegeven node als startpunt. Daarbij is het mogelijk om te filteren op bepaalde attribuutwaarden, desnoods met eenvoudige reguliere expressies; zoeken naar cn=Rick* is bijvoorbeeld mogelijk. De server antwoordt nu uitsluitend met de objecten waarvan de attributen matchen met de filtervoorwaarde. Filtervoorwaarden mogen met logische operatoren worden gecombineerd. Een vaak middels AND toegevoegde voorwaarde is een eis dat een bepaalde objectClass aanwezig moet zijn, omdat dit helpt te bepalen op welke attribuutwaarden gerekend kan worden, en tevens hoe die attribuutwaarden bedoeld zijn. Een commonName van een printer heeft immers een andere bedoeling dan een commonName voor een persoon.

De combinatie van browsen en zoeken sluit prima aan bij de manier waarop we in de praktijk zoeken in een directory zoals een telefoonboek: eerst browsen we naar de plaatsnaam, en dan zoeken we daaronder min-of-meer lineair naar de naam van de persoon.

Naast deze methoden om informatie aan LDAP te onttrekken, is er ook een methode om vergelijkingen aan te vragen, en genoegen te nemen met een true/false resultaat. Deze mogelijkheid kan worden gebruikt om privacy-gevoelige informatie tegen lezen te beschermen, zoals een password record of, conform de inlichtingendienst van KPN Telecom, de straatnaam bij iemands telefoonnummer. Wie informatie al denkt te hebben mag het controleren, maar wie die informatie nog niet heeft krijgt het ook niet uit de directory. Dat houdt meteen ook in dat zo'n attribuut of object niet in een zoekresultaat zal voorkomen.

Deze interactie kan met vaak ook op websites doorlopen, maar daar zit dan steevast een glossy interface aan vast, en daar zit je niet altijd op te wachten. Het vertraagt en het voegt niets toe aan de zoekresultaten. Ook het moeten parsen van opgeleverde HTML is onhandig bij automatisering, zodat in zo'n geval het gebruik van een standaardprotocol voor dataoverdracht duidelijk geschikter is. Deze droge aanpak blijkt dan ook heel geschikt voor administratieve toepassingen.

Er zijn ook op andere plekken voordelen te verwachten van LDAP; het zou schrijver dezes niet verbazen als het een grote vlucht gaat nemen in handelsrelaties tussen bedrijven, bijvoorbeeld om een produktcatalogus op dezelfde droge wijze aan te bieden aan inkoopafdelingen, die daarmee snel kunnen zoeken in de databases van al hun leveranciers. Vooral wanneer de gebruikte schema's gestandaardiseerd zouden worden zou dat verstrekkende dienstverlening kunnen zijn, die niet zomaar op web-niveau te evenaren valt.

Een LDAP server is doorgaans ingericht op het razendsnel zoeken naar de gevraagde informatie, en is doorgaans minder vlot in het verwerken van schrijfakties. Dat is grotendeels een kwestie van optimalisatie van indexering en dergelijke op de LDAP server, en natuurlijk is het ook een beetje de cultuur die rond het protocol hangt. Ook op dit gebied valt dus nog wel wat ontwikkeling te verwachten. Het moet daarbij wel gezegd worden dat LDAP geen transactieprotocollen bevat, dus verder dan atomair schrijven met één object tegelijk is te veel gevraagd. Voor dat soort toepassingen is een RDBMS een geschiktere keus.

Authenticatie

Niet iedereen mag zomaar alles met elk object in de LDAP server uitvoeren, en daarom is er goede toegangscontrole. Dat gebeurt zoals wel vaker met behulp van een gebruikersnaam met credentials.

De gebruikersnaam kan op twee manieren worden uitgevoerd. De eerste is de klassieke X.500 manier, met een OID zoals cn=Rick van Rein,ou=Contacts,o=OpenFortress,c=nl. Dit identificeert een object in LDAP met daarin een userPassword attribbuut. Een modernere werkwijze is gebaseerd op een userid (kort genoteerd als uid) met een aantal domainComponent (dc) attributen, zoals uid=rick,dc=openfortress,dc=nl. Ook deze vorm van OID kan een object aanwijzen waarin een userPassword staat.

De credentials kunnen userPasswords zijn die door een secure hash zijn gehaald; hiervoor worden diverse algoritmen ondersteund, te weten Unix crypt, en secure hashes MD5 en SHA1, elk in een onbespoten uitvoering en in een `salted' uitvoering (dus met toegevoegde random data om de hash beter te spreiden).

Maar passwords zijn niet de enige wijze tot authenticate bij LDAP; een verbinding kan ook via TLS (het vroegere SSL) of via SASL worden opgezet, waarmee strong crypto wordt gebruikt als toegangsmechanisme. Verder is het mogelijk om groepen van gebruikers op te stellen, en losse gebruikers op zulke groepen te laten inloggen.

Afhankelijk van de authenticatie kan een LDAP server meer of minder permissies verlenen aan de gebruiker. Dit is geen standaard onderdeel van LDAP, het hangt af van de serverimplementatie.

Het moge duidelijk zijn dat userPassword een attribuut is dat, net als objectClass, een speciale behandeling krijgt van de LDAP server, maar dat hij ook gewoon beschikbaar is voor gebruik door eenieder die het weet in te lezen. Een programma dat dus de access rights heeft om dit veld op te vragen, kan gebruikers authenticeren. En ziedaar, een mogelijkheid om een belangrijk deel van NIS te vervangen. En ziehier ook de manier van werken met Active Directory.

Programmatische interface

Bij de standaard voor LDAP hoort niet alleen een netwerkprotocol, maar ook een standaard API voor de programmeertaal C. Dat zorgt er voor dat een library voor elk operating systeem te vinden zal zijn, en dat die zich op een standaardwijze gedraagt. Het soort operaties dat op zo'n API te vinden is behelst het verbinden met de server, het authenticeren (ofwel `binden' in LDAP-terminologie), het opvragen van attributen en het doorvragen naar subbomen en het gefilterd zoeken daarin. Precies wat je zou verwachten van een directory dus.

De operaties zijn alle aanwezig in een synchrone en een asynchrone variant, zodat de client kan kiezen voor een concurrency protocol naar keuze. Er zijn geen transactionele eigenschappen, alleen het schrijven naar een object is wel altijd een atomaire aktie. Ook zal een LDAP server controleren of een object het schema volgt, alvorens het te accepteren.

Nu is C niet ieders favouriete taal, en het is dan ook goed te weten dat er interfaces bestaan voor scripting talen als Perl (CPAN, Net::LDAP) en Python (python-ldap.sf.net). En ook de populaire webtaal PHP kent al heel lang een interface naar LDAP. Deze zaken berusten doorgaans op de open source implementatie van LDAP.

Wie helemaal wars van programmeren is zal toch de uitwerking van al deze ondersteuning niet missen; er is het nodige aan modules, proxies en dergelijke om systemen op LDAP aan te sluiten. LDAP heeft overduidelijk voldoende kritische massa verzameld om een blijvertje te zijn. Het lijkt er sterk op dat alle proprietary protocollen van diverse leveranciers zich steeds meer zullen gaan laven aan LDAP.

Het is overigens expliciet niet de intentie van LDAP om DNS over te nemen. Dit misverstand wordt nog wel eens gehoord, en het is het te ver doordrukken van het protocol. DNS is vrij goed in wat het doet, en er is niet echt een reden om het door LDAP te vervangen.

Replicatie

Voor snelle toegang, maar zeker ook voor robuuste beschikbaarheid van bijvoorbeeld de login prompt, is het prettig te weten dat LDAP zeer geschikt is voor meervoudig uitgevoerde servers. Dat zal vaak zijn in een opzet met een master server, die wijzigingen aan slave servers doorseint door in te loggen als een speciaal daarvoor ingestelde update-gebruiker. Dat is echter niet het enige mogelijke; ook een multi-master aanpak is denkbaar; hierin kan elke LDAP server beschreven worden, en zullen de andere servers door die master worden aangepast.

Er is nog geen standaard voor de wijze waarop LDAP servers elkaar updaten. Voor de hand ligt het gebruik van het LDIF formaat voor informatie-uitwisseling, maar er zijn nog wel de nodige dingen te regelen op het gebied van uit sync raken van servers en herstel daarvan. Ook het bootstrappen van een nieuwe server moet zonder kleerscheuren worden geregeld. Enfin, dit borrelt momenteel nog in de kookpot van de IETF.

Ongeacht het mechanisme van replicatie, het eindresultaat is een meervoudig uitgevoerd systeem, en dat werkt uitstekend. Prettig te weten dat een password-server down kan gaan zonder dat de telefoon te heet wordt om op te pakken.

Koppelen van servers

Uit de genoemde voorbeelden moge blijken dat een bedrijf doorgaans niet een eigen root node met OID "" heeft, maar dat ze zich onder een (al dan niet bestaande) node op overkoepelend niveau schaart; bijvoorbeeld dc=OpenFortress,dc=nl hoort thuis onder de node voor dc=nl. Zo'n hogergelegen node hoeft echter niet daadwerkelijk te bestaan; vaak dient de LDAP server van een bedrijf als startpunt om te browsen of zoeken met de kortste OID die het bedrijf hanteert.

Een voorbeeld van een LDAP URL is

ldap://zeta.ldaphosting.com/dc=openfortress,dc=nl
In wezen worden hier twee stukken informatie verstrekt die prima gekoppeld kunnen worden. Dat is ongemakkelijk voor gebruikers, en het kan leiden tot referenties die niet meer kloppen. Het is dus prettiger de informatie niet zo dubbel te vermelden, en daar zijn ook wel oplossingen voor.

Gegeven de domeinnaam is de server te vinden met SRV records voor dat domein, die er bijvoorbeeld als volgt kunnen uitzien:

_ldap._tcp.openfortress.nl. IN SRV 0 0 389 zeta.ldaphosting.com.
Dit verwijst het LDAP protocol voor dc=openfortress,dc=nl naar poort 389 (de standaard LDAP poort) van zeta. Dit werkt natuurlijk alleen met een client die zich van deze records bewust is, maar gelukkig is er een publieke gateway die de minder geïnformeerde client doorstuur; bijvoorbeeld me
openfortress.nl. IN CNAME root.openldap.org.
is er voor te zorgen dat een verwijzing naar dc=openfortress,dc=nl toch op de goede plek terecht komt.

De root.openldap.org server is een heel aardig idee. Deze server zoekt naar genoemde SRV records op basis van de domeinnaam waaronder hij wordt aangesproken; voor elk gevonden SRV record wordt een referral teruggegeven. De client kan vervolgens, als hij tenminste LDAPv3 begrijpt en dus referrals correct verwerkt, contact gaan leggen met de eigenlijke server.

Er is echter veel meer mogelijk met doorverwijzingen. Stel dat een server die dc=openfortress,dc=nl aankan een verzoek krijgt voor dc=tralala,dc=nl; dan kan dit worden doorverwezen naar een server die dc=nl aankon, en dat kan wederom genoemde server root.openldap.org zijn. Dit leidt tot een globale, gedistribueerde directory!

Omgekeerd kan het ook. Wanneer de LDAP server voor dc=openfortress,dc=nl wordt gevraagd naar een subdomein dc=resellers,dc=openfortress,dc=nl dan daarop gereageerd worden met een doorverwijzing naar een andere LDAP server die dat subdomein bedient. Een LDAPv3 server zal daar doorgaans configuraitemogelijkheden voor bieden.

Merk op dat met onder meerdere "root" services voor de globale directory kan vallen. Een server die dc=openfortress,dc=nl verwerkt, zal vaak ook een alias o=OpenFortress,c=nl accepteren. Maar een aanvraag voor o=tralala,c=nl moet niet worden doorverwezen naar root.openldap.org, omdat het hier niet gaat om een domainComponent hiërarchie; het verschil volgt uit dc=nl versus c=nl. Voor de laatste moet worden doorverwezen naar een root die c=nl bedient, of iets waar dat onder valt (de OID "" voor het landensysteem dus). Voor c=nl biedt SURFnet haar diensten aan en voor "" in het landensysteem biedt dante.net zich aan; hoewel bij Dante niet alle landen meedraaien, valt te verwachten dat op dat niveau gesynchroniseerd gaat worden.

Kort gezegd ondersteunt LDAPv3 dus het opnemen van de ene boom in de andere, waarbij de verwijzingen in beide richtingen geregeld kunnen worden. En daarmee is het mogelijk een groot bouwsel te maken. Allemaal BOFH's die hun directory samenvoegen onder een root of all evil, dat kan wat worden...

Directory of RDBMS?

Een vraag die geregeld de kop opsteekt wanneer over directories wordt gesproken is wat het bijdraagt ten opzichte van een RDBMS. Hoewel dat eigenlijk appels met peren vergelijkt, verdient het toch enige aandacht omdat het op een bepaalde manier gaat om een alternatieve opslagstruktuur.

Het moge duidelijk zijn dat juist de hiërarchische opbouw van de directory oorsprong is van veel flexibiliteit, zoals het aliasen van delen van bomen, het inhangen van de ene boom in de andere en het afwisselen van browsen en gefilterd zoeken. Een RDBMS is doorgaans lokaal afgeschermd, is moeilijk te koppelen aan andere systemen, en er is één zoekmechanisme met de kracht en overhead van een voorhamer. Optimalisatie van SQL is zeer wel mogelijk, maar is ook erg complex, wat leidt tot duurdere software en meer kans op bugs.

Qua efficiëntie kan een directory in theorie beter presteren dan een RDBMS, omdat alle attributen die tegelijk nodig zijn altijd dicht bij elkaar staan. Wie gebruik maakt van de mogelijkheid om te variëren met de objectClasses van een object, en wie gebruik maakt van meervoudige attributen, zou datzelfde met een RDBMS alleen (handig) kunnen nabootsen door het combineren van meerdere tabellen die verspreid op een schijf staan opgeslagen. Ten gevolge van indexering in een LDAP server hoeft deze flexibiliteit weinig invloed te hebben op de zoeksnelheid. Maar hier heeft een directory juist de ingebakken kracht van een voorhamer; met name bij schrijfakties kan dat de efficiëntie gaan drukken.

Een RDBMS is natuurlijk uitermate geschikt voor het uitvoeren van updates, en al helemaal transactioneel. Zeker wanneer meerdere databases dezelfde update moeten doorvoeren (bijvoorbeeld een order die bij klant, verkoper en bank als één geheel commit of faalt) dan voldoet de simpele atomiciteit-per-object garantie van een directory niet. En wie bulk updates op gestructureerde data moet uitvoeren komt ook niet goed op gang met een directory.

De tabellen in een RDBMS hebben bijna per definitie een proprietary struktuur. Hoewel ze vaak hetzelfde soort informatie uitdrukken (personen, bedrijven, adressen, orders) is er in de cultuur rond RDBMSen geen standaardisatie in dit soort structuren ontstaan. Met als gevolg dat databases niet zonder meer te koppelen zijn. Mogelijk brengt XML hier verandering in, maar het zal nog wel een paar jaar duren voordat in die gemeenschap consensus ontstaat over de te gebruiken DTDs. Voor LDAP bestaat een aantal standaards dat veelgebruikte data vastlegt, wat bijzonder nuttig is wanneer directories gekoppeld worden, of wanneer client code een bepaalde toepassing uitvoert. Deze standaardisatie houdt overigens niet in dat het onmogelijk wordt om zelf uitbreidingen op het schema te maken; maar een hoop voorwerk is al gedaan.

Tenslotte, en hier wordt de vergelijking echt eentje tussen appels en peren, is LDAP een protocol en een RDBMS is dat niet. Eigenlijk zou je LDAP moeten vergelijken met SQL-over-een-netwerk. In situaties waarin dit leidt tot een onbepaald groot aantal gebruikers, die allen bepaalde rechten hebben op het zien en veranderen van hun eigen data, loopt SQL snel stuk terwijl LDAP er beter mee overweg kan door de vermelding van de authenticerende informatie in de directory.

Enfin, er zijn dus wel wat eigenschappen die de keus tussen directory of RDBMS als opslagstruktuur beïnvloeden, maar de hamvraag is natuurlijk welk model het best aanvoelt voor een gegeven probleemstelling. Sommige data past goed in een directory, andere juist beter in een RDBMS.

Samenvatting

LDAP heeft alle kenmerken van een volwassen technologie. Er is consensus over technologie en schijnbaar zelfs over root repositories. Er zijn oplossingen in de open source community en van gangbare commerciële bedrijven, en langzaamaan migreren steeds meer proprietary netwerkdatabases richting LDAP. Verder is er goed nagedacht over zaken als bereikbaarheid door replicatie, doorverwijzingen, datarepresentatie en access control, zodat het de rol van netwerkdatabase uitstekend kan spelen. LDAP is een blijvertje.

Appendix: Servers voor LDAP

Met LDAP heb je geen database, maar alleen een protocol om een database aan te spreken. Diverse servers implementeren heden ten dage dit protocol, met als belangrijkste:

  • Microsoft Active Directory
  • Novell NDS
  • Netscape Directory Server
  • IBM SecureWay Directory
  • OpenLDAP (open source)
Client software is dik gezaaid; vaak geschreven met een bepaalde toepassing in gedachten, zoals een rolodex-achtig systeem of een login-module die via LDAP werkt.

Appendix: LDIF, het LDAP Interchange Format

Data in een directory wordt doorgaans uitgewisseld in het standaardformaat LDIF, een tekstformaat dat bestaat uit blokken tekst die gescheiden zijn door lege regels. Elk blok bevat een regel met de OID (en begint daarom met "dn:") gevolgd door attributen. Een voorbeeld van zo'n blok:

dn: cn=Rick van Rein,ou=Contacts,o=OpenFortress,c=nl
objectClass: person
objectClass: organizationalPerson
cn: Rick van Rein
cn: Henderikus van Rein
cn: H. van Rein
ou: Technical
userPassword: {SHA}aab5009930d23316649f0c59366b753fff31c453
Wat hier opvalt is dat meerdere attributen met dezelfde naam mogelijk zijn; de objectClass attributen zijn speciaal, en door deze op te zoeken in het schema zijn de verplicht en optioneel aanwezige attribuutnamen te bepalen.


 
   ------ 8< ---------- 8< ----------- 8< ------ | OpenFortress*