OpenLDAP ab 2.4 installieren und einrichten
Vorweg
Mir ist keine Quelle im Netz bekannt, die die Einrichtung von OpenLDAP wirklich umfassend darstellt – schon gar nicht auf Deutsch. Diese Informationen hier sind aus allen möglichen Ecken zusammengeklaubt – selbst die meisten Bücher zu OpenLDAP empfinde ich als sehr wenig hilfreich.
Grundinstallation
Hinweis: domain.tld muss man natürlich immer an den eigenen openLDAP anpassen.
Zuerst installieren wir die Binaries, in Debian und seinen Abkömmlingen (Ubuntu, Mint etc.) z.B. so:
1 | apt-get install slapd ldap-utils |
Die Installationsroutine von Debian legt dabei nur eine sehr rudimentäre Konfiguration an, sodass etwas Nacharbeit vonnöten ist. Bei anderen Distributionen kenne ich mich nicht so gut aus. Ein
1 | dpkg-reconfigure slapd |
ermöglicht uns hier die Eingabe einer korrekten Basis-DN (auf die muss unser SSL-Zertifikat ausgestellt sein), meist sowas wie
- dc=domain, dc=tld
und zusätzlich definieren wir dabei ein Rootpasswort für den LDAP-User
- cn=admin,dc=domain,dc=de
.
Handling von OpenLDAP
Ab Debian Squeeze speichert der OpenLDAP-Server seine Konfiguration in einem internen LDAP-Baum und nicht mehr in einem Konfigurationsfile. Das macht die Pflege auf den ersten Blick erheblich aufwändiger, weil man an diesen Baum in der Standardkonfiguration nur umständlich über Konsolentools herankommt. Zudem kann eine fehlerhafte Datenbank dazu führen, dass der OpenLDAP nach einer Konfigurationsänderung gar nicht mehr hochkommt.
Nur der Rootbenutzer des Systems kommt immer auch direkt an die Daten. Man kann sich die bestehenden Inhalt nur als root anzeigen lassen mit:
1 | ldapsearch -Y EXTERNAL -H ldapi:/// -b "cn=config" |
Neue Einträge können über *.ldif-Files hinzugefügt werden:
1 | ldapmodify/ldapadd -Y EXTERNAL -H ldapi:/// -f |
Hinweis zu Ubuntu 14.04 LTS
Der Installer setzt den Accountnamen für den Benutzer mit Zugriff auf den cn=config-Baum standardmäßig auf: cn=admin,dc=domain,dc=tld. Dann macht man Befolgen dieser Anleitung ein langes Gesicht. Um das auf den Standard zu ändern, benötigt man nur für Ubuntu 14.04 LTS noch eine kleine Änderung (change_admin.ldif):
1 2 3 4 | dn: olcDatabase={0}config,cn=config changetype: modify replace: olcRootDN olcRootDN: cn=admin,cn=config |
Obligatorisch:
1 | ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f change_admin.ldif |
Sicherheit
OpenLDAP-Verbindungen per TLS absichern
Vorweg: Hier kann ganz viel schiefgehen, obwohl ich dieses Kapitel mit am wichtigsten finde. Wenn OpenLDAP aus irgendwelchen Gründen die Zertifikatfiles nicht frisst, kann man mit der Konfiguration von vorne beginnen oder man hat vorher ein Backup der alten Datenbank gemacht. Deswegen überspringe ich diesen Schritt gerne und binde den OpenLDAP einfach nicht an öffentlich erreichbare Netzwerkdevices. Wenn man das machen muss, führt aus Datenschutzgründen aber kein Weg an dieser Prozedur hier vorbei. LDAP ist genau wie FTP ein Klartextprotokoll, dass ohne Transportverschlüsselung auf dem gesamten Datenweg offenliegt und gerade in WLAN-Umgebungen sehr leicht belauscht werden kann.
Generell gibt es zwei Möglichkeiten, wie man an kostenlose Zertifikate kommen kann. Wosign oder StartSSL. Es gibt diverse Tutorials im Netz zur Nutzung dieser Dienste. Von Letsencrypt würde ich im Kontext von OpenLDAP eher abraten. StartSSL und WoSign sind mittlerweile Geschichte. Wenn es kostenlos sein soll, führt kein Weg an letsencrypt vorbei. Man muss dann dafür sorgen, dass OpenLDAP nach jedem Certupdate neu gestartet wird, also alle drei Monate mindestens.
Man hat am Ende des Zertifizierungsprozesses in der Regel drei Dateien vorliegen:
- domain.tld-crt.pem (enthält das Zertifikat)
- domain.tld-key.pem (enthält den privaten Schlüssel)
- ca_chain.pem (enthält die Zertifizierungschain der CA)
domain.tld ist dabei der Wurzelbaum des openLDAP. Ich habe die Dateien nach /etc/ldap/ssl gelegt. Besser aufhoben sind sie in /etc/ssl/cert – dann muss slapd Leserechte dort bekommen.
Folgende Datei (tls_ldap.ldif) anlegen:
1 2 3 4 5 6 7 8 9 | dn: cn=config add: olcTLSCACertificateFile olcTLSCACertificateFile: /etc/ldap/ssl/ca_chain.pem - add: olcTLSCertificateKeyFile olcTLSCertificateKeyFile: /etc/ldap/ssl/domain.tld-key.pem - add: olcTLSCertificateFile olcTLSCertificateFile: /etc/ldap/ssl/domain.tld-crt.pem |
… und über die Konsole einspielen:
1 | ldapadd -Y EXTERNAL -H ldapi:/// -f tls_ldap.ldif |
oder auch
1 | ldapmodify -Y EXTERNAL -H ldapi:/// -f tls_ldap.ldif |
Ich bin zusätzlich ein Freund davon, sichere Verbindungen zu erzwingen. Clients, die das nicht wollen oder können, sollen bitte draußenbleiben.
Folgende Datei (force_tls.ldif) anlegen:
1 2 3 4 | dn: olcDatabase={1}hdb,cn=config changetype: modify add: olcSecurity olcSecurity: tls=1 |
Und wieder einspielen:
1 | ldapadd -Y EXTERNAL -H ldapi:/// -f force_tls.ldif |
oder auch
1 | ldapmodify -Y EXTERNAL -H ldapi:/// -f force_tls.ldif |
Jetzt noch in /etc/default/slapd nachschauen, ob die Services stimmen (ldaps über Port 639 gilt als veraltet und sollte nicht mehr verwendet werden). In der Regel steht da so etwas:
1 | SLAPD_SERVICES="ldap://0.0.0.0:389/ ldapi:///" |
Wenn man mehrere NICs besitzt, kann man natürlich statt 0.0.0.0 auch die IP einer spezifischen Netzwerkkarte angeben oder den OpenLDAP nur an localhost (127.0.0.1) binden.
Ein
1 | service slapd restart |
bringt Aufklärung, ob das Ganze funktioniert hat. Theoretisch ist das nicht notwendig, da OpenLDAP durch das neue Verfahren ohne Konfigurationsdatei quasi live im Betrieb gepatcht wird. Jetzt sollte der OpenLDAP Verbindungen von außen nur noch verschlüsselt akzeptieren. Von der Konsole aus ( ldapi:/// ) klappt das nach wie vor auch normal. Wir vertrauen uns ja schon selbst.
Bruteforce erschweren
Ein offener LDAP-Server ist anfällig für brute-force Attacken – zumal gerade im Schulbereich viele unsichere Passwörter im Umlauf sein dürften. Durch das ppolicy.schema kann man z.B. nach einigen fehlgeschlagenen Logins den Account für eine Weile automatisch sperren. openLDAP bringt das dafür notwendige Schema in /etc/ldap/schema/ppolicy.ldif schon mit.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | dn: cn=ppolicy,cn=schema,cn=config objectClass: olcSchemaConfig cn: ppolicy olcAttributeTypes: {0}( 1.3.6.1.4.1.42.2.27.8.1.1 NAME 'pwdAttribute' EQUALITY objectIdentifierMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 ) olcAttributeTypes: {1}( 1.3.6.1.4.1.42.2.27.8.1.2 NAME 'pwdMinAge' EQUALITY in tegerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) olcAttributeTypes: {2}( 1.3.6.1.4.1.42.2.27.8.1.3 NAME 'pwdMaxAge' EQUALITY in tegerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) olcAttributeTypes: {3}( 1.3.6.1.4.1.42.2.27.8.1.4 NAME 'pwdInHistory' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) olcAttributeTypes: {4}( 1.3.6.1.4.1.42.2.27.8.1.5 NAME 'pwdCheckQuality' EQUAL ITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) olcAttributeTypes: {5}( 1.3.6.1.4.1.42.2.27.8.1.6 NAME 'pwdMinLength' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) olcAttributeTypes: {6}( 1.3.6.1.4.1.42.2.27.8.1.7 NAME 'pwdExpireWarning' EQUA LITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) olcAttributeTypes: {7}( 1.3.6.1.4.1.42.2.27.8.1.8 NAME 'pwdGraceAuthNLimit' EQ UALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) olcAttributeTypes: {8}( 1.3.6.1.4.1.42.2.27.8.1.9 NAME 'pwdLockout' EQUALITY b ooleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE ) olcAttributeTypes: {9}( 1.3.6.1.4.1.42.2.27.8.1.10 NAME 'pwdLockoutDuration' E QUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) olcAttributeTypes: {10}( 1.3.6.1.4.1.42.2.27.8.1.11 NAME 'pwdMaxFailure' EQUAL ITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) olcAttributeTypes: {11}( 1.3.6.1.4.1.42.2.27.8.1.12 NAME 'pwdFailureCountInter val' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) olcAttributeTypes: {12}( 1.3.6.1.4.1.42.2.27.8.1.13 NAME 'pwdMustChange' EQUAL ITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE ) olcAttributeTypes: {13}( 1.3.6.1.4.1.42.2.27.8.1.14 NAME 'pwdAllowUserChange' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE ) olcAttributeTypes: {14}( 1.3.6.1.4.1.42.2.27.8.1.15 NAME 'pwdSafeModify' EQUAL ITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE ) olcAttributeTypes: {15}( 1.3.6.1.4.1.4754.1.99.1 NAME 'pwdCheckModule' DESC 'L oadable module that instantiates "check_password() function' EQUALITY caseExa ctIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE ) olcObjectClasses: {0}( 1.3.6.1.4.1.4754.2.99.1 NAME 'pwdPolicyChecker' SUP top AUXILIARY MAY pwdCheckModule ) olcObjectClasses: {1}( 1.3.6.1.4.1.42.2.27.8.2.1 NAME 'pwdPolicy' SUP top AUXI LIARY MUST pwdAttribute MAY ( pwdMinAge $ pwdMaxAge $ pwdInHistory $ pwdCheck Quality $ pwdMinLength $ pwdExpireWarning $ pwdGraceAuthNLimit $ pwdLockout $ pwdLockoutDuration $ pwdMaxFailure $ pwdFailureCountInterval $ pwdMustChange $ pwdAllowUserChange $ pwdSafeModify ) ) |
Eingespielt wird das Schema mit:
1 | ldapadd -Q -Y EXTERNAL -H ldapi:/// -f ppolicy.ldif |
Das Schema ppolicy.ldif selbst definiert nur Objekte für das entsprechende Modul, was jetzt noch geladen werden muss, wofür wir eine Datei policy_module.ldif mit folgendem Inhalt anlegen:
1 2 3 4 | dn: cn=module{0},cn=config changetype: modify add: olcModuleLoad olcModuleLoad: ppolicy.la |
Und das alte Spiel:
1 | ldapadd -Q -Y EXTERNAL -H ldapi:/// -f policy_module.ldif |
Jetzt brauchen wir noch eine Ablage (policy_context.ldif) für die verschiedenen Regelsätze:
1 2 3 4 | dn: ou=policies,dc=domain,dc=tld objectClass: organizationalUnit objectClass: top ou: policies |
1 | ldapadd -Q -Y EXTERNAL -H ldapi:/// -f policy_context.ldif |
Und als nächstes eine Default-Policy (default_policy.ldif):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | dn: cn=default,ou=policies,dc=domain,dc=tld objectClass: top objectClass: device objectClass: pwdPolicy cn: default pwdAttribute: 2.5.4.35 pwdMaxAge: 15552000 pwdInHistory: 3 pwdMinLength: 6 pwdMaxFailure: 3 pwdLockout: TRUE pwdLockoutDuration: 1800 pwdGraceAuthNLimit: 3 pwdMustChange: TRUE pwdAllowUserChange: TRUE pwdSafeModify: TRUE |
In diesen Beispiel wird nach drei fehlgeschlagenen Loginversuchen ( pwdMaxFailure: 3 ) das Login für 1800 Sekunden ( pwdLockoutDuration: 1800 ) gesperrt. Das sollte klebrig genug sein.
Muss ich es noch schreiben?
1 | ldapadd -Q -Y EXTERNAL -H ldapi:/// -f default_policy.ldif |
Da OpenLDAP ja so fluffig und intuitiv ist, brauchen wir jetzt noch ein Overlay (policy_overlay.ldif), dass dem OpenLDAP sagt, dass statt des normalen Loginhandlings jetzt immer auch die Default-Policy gelten soll:
1 2 3 4 5 6 7 | dn: olcOverlay=ppolicy,olcDatabase={1}hdb,cn=config objectClass: olcOverlayConfig objectClass: olcPPolicyConfig olcOverlay: ppolicy olcPPolicyDefault: cn=default,ou=policies,dc=domain,dc=tld olcPPolicyHashCleartext: TRUE olcPPolicyUseLockout: TRUE |
Ihr wisst, was jetzt kommt:
1 | ldapadd -Q -Y EXTERNAL -H ldapi:/// -f policy_overlay.ldif |
Damit hätten wir grundsätzlich verschlüsselte Verbindungen erzwungen und zusätzlich Bruteforce-Angriffe erschwert. Bleibt noch eines zu tun:
Anonymous-Bind verbieten
Standardmäßig erlaubt openLDAP einen sogenannte anonymous bind, d.h. man erhält lesend Zugriff auch ohne die Eingabe eines Passwortes. Diese lesende Zugriff ist sehr eingeschränkt, z.B. gibt es keinen Zugriff auf bestimmte Objektklassen oder gar Passworthashes. Mir ist die Vorstellung trotzdem nicht geheuer, dass sich u.a. Nutzernamen auf diesem Weg auslesen lassen. Daher verwende ich für den lesenden Zugriff einen separaten User, der sich mit Passwort authentifizieren muss, ansonsten aber nicht mehr Rechte als beim anonymous bind hat. Deswegen unterbinden wir das mit einer neuen Datei noanonymous.ldif:
1 2 3 4 5 6 7 | dn: olcDatabase={1}hdb,cn=config add: olcRequires olcRequires: authc dn: olcDatabase={-1}frontend,cn=config add: olcRequires olcRequires: authc |
Und jetzt kommt etwas anderes, weil wir einen bereits bestehenden Datenbankeintrag aktualisieren:
1 | ldapmodify -Y EXTERNAL -H ldapi:/// -f noanonymous.ldif |
Nach dem Einspielen der letzten Änderung hat man ohne Authentifizierung auch über die Konsole keinen Zugriff mehr auf den Hauptbaum des OpenLDAP (dc=domain, dc=tld) – die war bisher auch sowas wie „anonym“ aus Sicht des LDAP. Man muss dann ausweichen auf eine andere Befehlszeile (cn=config ist davon nicht betroffen):
1 | ldapadd -x -D cn=admin,dc=domain,dc=tld -W -f |
Danach wird man zur Eingabe des Adminpasswortes aufgefordert und kann so den Hauptbaum beschreiben und verändern.
Optionale Arbeiten
Performancetuning
Diese Datei ( index.ldif )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | dn: olcDatabase={1}hdb,cn=config changetype: modify add: olcDbIndex olcDbIndex: cn pres,sub,eq - add: olcDbIndex olcDbIndex: sn pres,sub,eq - add: olcDbIndex olcDbIndex: uid pres,sub,eq - add: olcDbIndex olcDbIndex: displayName pres,sub,eq - add: olcDbIndex olcDbIndex: default sub - add: olcDbIndex olcDbIndex: uidNumber eq - add: olcDbIndex olcDbIndex: gidNumber eq - add: olcDbIndex olcDbIndex: mail,givenName eq,subinitial - add: olcDbIndex olcDbIndex: dc eq |
einspielen mit
1 | ldapadd -Q -Y EXTERNAL -H ldapi:/// -f index.ldif |
Benutzer für die Administration von cn=config einrichten
Wenn man sauch den cn=config-Baum auch über komfortablere Frontends wie phpldapadmin oder LAM verwalten möchte, muss man das Objekt cn=admin, cn=config noch um weitere Einträge ergänzen. Zunächst erzeugen wir uns über die Konsole ein Passwort:
1 | slappasswd -h {SSHA} |
Wir erhalten einen Hash zurück, den wir in die Zwischenablage kopieren. Jetzt erstellten wir ein ldif-File (manager.ldif):
1 2 3 4 5 6 7 8 9 10 11 | dn: olcDatabase={0}config,cn=config changetype: modify add: olcRootPW olcRootPW: {SSHA} # auskommentieren, wenn wir den Zugriff von root auf cn=config # ohne Passwort sperren wollen. Sollte als Fallback besser erhalten bleiben #dn: olcDatabase={0}config,cn=config #changetype: modify #delete: olcAccess |
1 | ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f manager.ldif |
Wenn wir jetzt in phpldapadmin (ab Version 1.2.2) oder lam als Base-DN cn=config verwenden und als Login cn=admin, cn=config, können wir cn=config auch grafisch verwalten.
Quelle: https://wiki.debian.org/PhpLdapAdmin
War doch ganz einfach oder?
OpenLDAP ist extrem sperrig, aber eine hervorragende Authentifizierungsmöglichkeit, da im Gegensatz zu Datenbanksystemen die Struktur hochgradig standardisiert sowie mustergültig objektorientiert ist und sich OpenLDAP so recht schnell in beliebige Anwendungen integrieren lässt – fast alle ernstzunehmenden Onlinetools unterstützen die Authentifizierung über LDAP. OpenLDAP diente nicht umsonst nicht als Vorlage für die LDAP-Funktionen von Samba4 – weil es eben so sperrig ist.
Pingback: OpenLDAP automatisch installieren und einrichten « Allgemein « riecken.de
Vielen Dank für diese Anleitung.
Ich habe gerade ein kleines Problem an der Stelle beim Ausführen des Befehls als root user:
ldapadd ‑Q ‑Y EXTERNAL ‑H ldapi:/// ‑f policy_context.ldif
Folgender Fehler wird ausgegeben:
adding new entry „ou=policies,dc=subdomain,dc=domain,dc=tld“
ldap_add: Insufficient access (50)
additional info: no write access to parent
Es kann sein, dass mittlerweile die Grundkonfiguration durch die Distribution eine andere ist. Versuche mal ein
ldapadd ‑x ‑D ‚cn=Manager,dc=domain,dc=local‘ ‑w ‑H ldapi:/// ‑f
… wobei „cn=Manager,dc=domain,dc=local“ durch deine Daten (dein DN) zu ersetzen ist und „/path/to/file“ zum LDIF zeigt.
Hallo Herr Rieken,
können Sie mir beim installieren eines LDAP-Servers ein kurzes Heads-Up geben.
Was sind technische Grundvorraussetzungen? Also nicht blechseits her sondern welche Dienste werden benötigt? LetsEncrypt z.B.? Brauche ich standardmäßig HTTPS?
Ich habe ein Debian-Stretch System bzw. Server-System auf das ich per FTP von einem Windows Rechner zugreife, nun soll eine User-Authentification mittels PHP durchgeführt werden. Da PHP LDAP nativ unterstützt bietet sich das an. Den PHP Teil kann ich schon, nur steige ich grade nicht so durch was ich, in welcher Reihenfolge abarbeiten muß auf meine Linux System damit ich eine LDAP-Userdatenbank anlegen kann? Beim Thema LDAP scheint auch jedes Forum überfordert zu sein…
Danke und Gruß.
Sie brauchen zum Einsatz eines openLDAP-Servers Rootzugriff via SSH und fundierte Kenntnisse auf der Konsole. Andernfalls ist die Aufgabe für Sie eine recht anspruchsvolle.
Beim Thema „OpenLDAP-Verbindungen per TLS absichern“:
Sind in den Zeilen
olcTLSCACertificateFile: /etc/ldap/ssl/domain.tld-crt.pem
olcTLSCertificateFile: /etc/ldap/ssl/ca_chain.pem
die Zertifikate durcheinander geraten?
Danke, ist korrigiert!
Um den Passwort-Hash SHA-512 für LDAP zu nutzen, siehe https://www.redpill-linpro.com/techblog/2016/08/16/ldap-password-hash.html