ldap.domain.tld
Alle Dienste in Ihrer Zone fragen diesen Server ab, daher ist eine flexible Struktur sinnvoll und vereinfacht die Verwaltung maximal.
Wir trennen alle Dienste voneinander, diese können so alle auf derselben Maschine laufen, aber auch im Netzwerk verteilt werden. Spätere Lastverteilung ist so ohne Konfigurationsänderungen machbar.
Krei.se bietet einen rudimentären LDAP-Server für alle Subdomains. Hier die Anleitung wie man diesen exakt 1:1 selbst aufsetzt.
Vorteile eines LDAP-Servers:
Aufbau des Tutorials:
📝 Was nicht zwingend notwendig, aber gut zu wissen ist markiert der Notizblock.
Die Einrichtung ist Linux-agnostisch und funktioniert und Debian wie unter Arch, allerdings liegen die Einstellungen bei Debian in /etc/ldap, Arch /etc/openldap
🐧 Debian:
root@server:~# apt install slapd schema2ldif
root@server:/etc/ldap#
🏹 Arch:
pacman -S openldap && yay -S schema2ldif
root@server:/etc/openldap#
Hier wird i.d.R. ein 👑 Administrator-Passwort festgelegt, unbedingt sicher wählen und notieren. Mehr dazu in 4.
LDAP braucht für das Wissen welche Eigenschaften ein Objekt besitzen muss sogenannte Schema. Diese legen z.B. fest, dass alle Objekte des Typs Benutzer einen Eintrag für seinen Namen haben müssen.
Dieses Schema erlaubt das Anlegen verschachtelter Gruppen was ein sehr wichtiges Grundprinzip der Krei.se-Systemarchitektur abbildet.
ou=Gruppen keine GID
├── ou=Mitarbeiter GID 2000
│ ├── ou=Manager GID 2100
│ │ ├── cn=Inhaber GID 2110
│ │ ├── cn=Partner GID 2120
│ │ └── cn=Geschäftsführer GID 2150
│ ├── ou=Kundendienst GID 2500
│ │ ├── cn=HomeOffice GID 2550 <-- Beispielnutzer cn=maxmueller als member
│ │ └── cn=Kundendienstleitung GID 2560
│ └── cn=Aussendienst GID 2900
├── cn=Lieferanten GID 5000
└── cn=Gäste GID 9000
Mit verschachtelten Gruppen können Sie einen Benutzer in HomeOffice eintragen - er erhält so als sekundäre Gruppen HomeOffice, Kundendienst und Mitarbeiter.
Die Benutzer selbst können Sie in LDAP in Ordner und Unterordner sortieren wie sie wollen, eindeutig muss nur die User-ID (uid maxmueller) sein. Mit der Gruppenstruktur muss diese Benutzer-Ordnung nicht übereinstimmen, Sie dient allein der besseren Lesbarkeit des Domänen-Baums.
ou=Benutzer keine GID - rein optische Sortierung
├── ou=Mitarbeiter keine GID - rein optische Sortierung
│ ├── cn=maxmueller prim. GID 2000
│ ├── cn=lieschenmueller prim. GID 2000
│ ├── cn=...
└── ou=Gäste keine GID - rein optische Sortierung
└── cn=guest prim. GID 9000
Benutzer sind alle Identitäten, die sich gegen Ihren LDAP-Server authentifizieren müssen um Dienste zu nutzen. Es macht Sinn auch Dienste die keinen Login benötigen gegen einen Gast-Account zu authentifizieren, denn so filtern Sie alle Bots heraus die Anfragen stellen ohne von einer Ihrer Internetseiten o.ä. zu kommen.
📝 Die primäre Gruppen-ID eines Benutzers setzen wir immer als die GID der obersten Ebene seiner Zuordnung im Gruppen-Baum. maxmueller ist in unseren Beispielen in ou=Gruppen,ou=Kundendienst,cn=HomeOffice als member eingetragen und daher primär Mitarbeiter mit GID 2000.
Schema herunterladen und in ldif umwandeln:
root@server:/etc/ldap/schema# wget https://github.com/jtyr/rfc2307bis/raw/master/rfc2307bis.schema
root@server:/etc/ldap/schema# schema2ldif rfc2307bis.schema > rfc2307bis.ldif
Das Schema ersetzt das NIS-Schema mit dem keine verschachtelten Gruppen möglich sind. Das alte NIS-Schema müssen wir daher vor dem Einspielen des neuen Schemas löschen.
(Das manuelle erstellen der delete.ldif können Sie auch überspringen und hier direkt herunterladen: https://github.com/krei-se/OLC-rfc2307bis/raw/main/delete_nis_attributes_and_objects.ldif)
root@server:/etc/ldap/schema# wget https://github.com/krei-se/OLC-rfc2307bis/raw/main/delete_nis_attributes_and_objects.ldif
Stellen Sie für ein manuelles Erstellen zunächst sicher, dass OpenLDAP bereits läuft.
Ziehen Sie das alte Schema als LDIF aus Ihrer cn=config:
ldapsearch -Y EXTERNAL -H ldapi:/// -b cn={2}nis,cn=schema,cn=config > delete_nis_attributes_and_objects.ldif.ldif
Diese LDIF-Datei passen Sie nun mit delete-Direktiven an:
dn: cn={2}nis,cn=schema,cn=config
changetype: modify
delete: olcObjectClasses
olcObjectClasses: ...
-
delete: olcObjectAttributes
olcObjectAttributes: ...
Stellen Sie sicher, dass keine leerzeilen etc. enthalten sind.
Jetzt können Sie bei laufendem Server das alte Schema löschen und das rfc2307bis.ldif einspielen:
root@server:/etc/ldap/schema# ldapmodify -Y EXTERNAL -H ldapi:/// -f delete_nis_attributes_and_objects.ldif
root@server:/etc/ldap/schema# ldapadd -Y EXTERNAL -H ldapi:/// -f rfc2307bis.ldif
cn={2}nis,cn=schema,cn=config
ist jetzt leer und rfc2307bis werden Sie unter cn={4},cn=schema,cn=config
finden. Wenn Sie das nicht wollen können Sie den Server anhalten und die Dateien in etc/ldap/slap.d/cn=config einfach umbenennen. Generell wird abgeraten diese Dateien zu modifizierien, aber ich habe das schon zig mal so gemacht und garantiere, dass das klappt. Denn mehr als diese Dateien hat LDAP nicht als Datenbank für cn=config.
root@server:/etc/ldap/schema# systemctl stop slapd
root@server:/etc/ldap/schema# rm /etc/ldap/slapd.d/cn\=config/cn\=schema/cn\={2}nis.ldif
root@server:/etc/ldap/schema# mv /etc/ldap/slapd.d/cn\=config/cn\=schema/cn\={4}rfc2307bis.ldif /etc/ldap/slapd.d/cn\=config/cn\=schema/cn=\{2}rfc2307bis.ldif
root@server:/etc/ldap/schema# systemctl start slapd
Das wars schon.
Damit wir für jeden Benutzer festlegen können unter welchen Adressen er Mails empfangen und versenden darf spielen wir noch das Postfix-Schema ein:
/etc/ldap/schema# wget https://raw.githubusercontent.com/credativ/postfix-ldap-schema/master/postfix.schema
/etc/ldap/schema# schema2ldif postfix.schema > postfix.ldif
/etc/ldap/schema# ldapadd -Y EXTERNAL -H ldapi:/// -f postfix.ldif
👷👷👷 --- ab hier wird noch gebaut --- 👷👷👷
Für Dovecot brauchen wir noch Einstellungen für die maximale Postfach-Größe, etc. - hier gibt es einiges an Schemas zur Auswahl, da ich derzeit nur MailQuota nutze und evtl. noch Änderungen durchführe habe ich es mal anderen gleichgetan und ein eigenes für Dovecot erstellt:
/etc/ldap/schema# wget https://krei.se/schemas/dovecot.schema
👷👷👷 --- bis hier wird noch gebaut --- 👷👷👷
Ist schon in rfc2307bis mit drin.
Das wars schon. Mail-Versand ist nicht Thema dieses Artikels.
OpenLDAP läuft mit Zertifikaten von letsencrypt sofort, da Sie auf den Domänencontroller aber nicht per Browser o.ä. zugreifen sondern immer mit Diensten oder eigenen Verwaltungstools lohnt sich die Nutzung eines eigenen CAs und von ED25519-Kurven. Das ist nicht Thema des Artikels, wir setzen das Vorliegen von Schlüsseln hier voraus. Wie Sie Schlüssel von letsencrypt automatisch verteilen können Sie hier nachlesen.
In /etc/ldap werden auch für den Client-Zugriff Konfigurationen in der Datei ldap.conf gespeichert und manchmal liegt dort noch die Datei slapd.conf rum. Die letztere brauchen Sie nicht. slapd, der Dienst für OpenLDAP speichert alle Einstelungen in der cn=config Datenbank. Wichtig ist erstmal nur zu wissen, dass beim Start des Dienstes festgelegt wird auf welchen URLs/Ports er läuft. ldap:/// ist unverschlüsselt, ldapi:/// erlaubt Zugriff von der lokalen Konsole aus und ldaps:/// ist SSL-verschlüsselt.
Unter Arch liegt diese Einstellung etwas versteckt.
/etc/systemd/system/multi-user.target.wants/slapd.service
verrät uns, dass standardmäßig ldap:/// und ldapi:/// aktiv ist.
Zudem wird /etc/conf.d/slapd
eingebunden. Stellen Sie daher sicher, dass Sie URLs/Ports so einstellen und ggf. deaktivieren, dass später nur noch Zugriff per SSL ldaps:/// und lokal mit ldapi:/// sowie ldap://localhost/ möglich ist.
In Debian ist der Start von OpenLDAP in /etc/inid.d/slapd
geregelt und diese bindet nur /etc/default/slapd
ein.
Stellen Sie in beiden Fällen sicher, dass bereits vor dem Installieren der SSL-Schlüssel ldaps:/// aktiviert ist. Wir nutzen kein StartTLS, OpenLDAP läuft mit ldaps:/// auf Port 636 und erwartet zwingend zu Verbindungsaufbau SSL.
Sie erstellen nun eine LDIF-Datei enablessl.ldif in der der Pfad zu den Schlüsseln steckt:
dn: cn=config
changetype: modify
add: olcTLSCACertificateFile
olcTLSCACertificateFile: /etc/ssl/certs/ca.domain.tld.pem
-
add: olcTLSCertificateKeyFile
olcTLSCertificateKeyFile: /etc/ssl/private/ldap.domain.tld.key
-
add: olcTLSCertificateFile
olcTLSCertificateFile: /etc/ssl/certs/ldap.domain.tld.crt
📝 Den olcTLSCACertificateFile Eintrag benötigen Sie nur, wenn Sie eine 🏰 eigene CA verwenden, die noch nicht systemweit hinterlegt ist. Wie das geht können Sie hier nachlesen. Ansonsten genügen die beiden letzten Zeilen. Das - trennt übrigens im LDIF, dass Sie mehrere Einträge unter demselben changetype machen wollen.
OpenLDAP wird die Einträge nur annehmen, wenn die Zugriffsrechte exakt passen was ohne hilfreiche Fehlermeldung sehr frustrierend enden kann, denn auch das chown muss exakt stimmen. Prüfen Sie genau unter welchem Benutzer OpenLDAP läuft, in Arch ist das ldap/ldap, Debian nutzt openldap/openldap. Dienste die den Server-Schlüssel lesen müssen können Sie daher:
usermod -a -G ssl-cert openldap
) und dieser Gruppe 750 Rechte in /etc/ssl/private einräumen ODERDas Server-Zertifikat benötigt ZWINGEND chmod 644, der Schlüssel MUSS chmod 640 haben. Andernfalls wird OpenLDAP die Einträge nicht übernehmen!
📝 Letsencrypt gewährt Zugriffe auf Schlüssel standardmäßig nur für root und erstellt neue Schlüssel auch nur für root, daher macht Sinn diese als renewal-hook zu verteilen und per chown lesbar zu machen (Siehe Blog-Eintrag für letsencrypt)
Ob LDAP den Pfad und Schlüssel lesen kann können Sie bereits vor Einspielen und Neustart des Servers prüfen:
sudo -u openldap file /etc/ssl/private/ldap.domain.tld.key
/etc/ssl/private/ldap.domain.tld.key: ASCII text
sudo -u openldap file /etc/ssl/certs/ldap.domain.tld.crt
/etc/ssl/private/ldap.domain.tld.key: ASCII text
Dies prüft zwar nicht, ob die chown Rechte exakt passen ist aber hilfreich um den gesamten Pfad zu prüfen.
Alles doppelt und dreifach geprüft - auch das Zertifikat und nicht nur den Schlüssel? Dann spielen Sie nach aktivieren von ldaps:/// und Neustart des Dienstes das LDIF ein:
ldapadd -Y EXTERNAL -H ldapi:/// -f enablessl.ldif
Wenn hier keine Fehlermeldung kommt sollte der Zugriff der ldaps:/// nun funktionieren und Sie haben sich mit diesem langweiligen Blog-Post stundenlange Fehlersuche und spätere Restrukturierungen erspart.
Am nervigsten für Neulinge ist zu verstehen, wozu man cn=admin, olcRootDN oder olcRootPW braucht und was die Einträge bewirken wenn der olcRootDN als User existiert. Debian hat z.B. cn=admin,dc=domain,dc=tld User und einen cn=admin,cn=config User, legt aber diesen Nutzer nicht in der dc=domain,dc=tld Datenbank an.
Spätestens ab mehreren Datenbanken macht es Sinn zentral das RootPW über einen Verwalter-DN nur in dessen BaseDN Datenbank zu setzen und diesen als olcRootDN in den einzelnen Datenbanken zu hinterlegen. Dann kann man immernoch für cn=config als Fallback einen oldRootDN mit Passwort setzen der nicht in einer dieser Datenbanken als Verwalter auftaucht und nur für Admin-Aufgaben Datenbank-übergreifend gebraucht wird. Das ist dann cn=admin,cn=config und hat sein Passwort nur als olcRootPW hinterlegt.
Sie wollen es sicherlich zwar möglichst einfach, aber das Minimum kann unterschiedlich sein.
Wichtig ist zu verstehen, dass wir für die Datenbank, also sowohl cn=config als auch jede vom Server bereitgestellte BaseDN (das können mehrere sein) unterschiedliche Root-User festlegen können.
Denn das erlaubt OpenLDAP zwar beim Verwalten, startet dann aber erstmal nicht mehr :D
olcRootDN legt für die Datenbanken fest welcher User die maximalen Zugriffsrechte hat. Das KANN ein Eintrag in einer BaseDN sein, cn=Manager,dc=domain,dc=tld ist völlig legitim. MUSS es aber nicht.
olcRootPW ist ein Fallback für das Root-Passwort. Sie können diesen Eintrag setzen, er ist z.B. auch noch valide falls Ihre Benutzerdatenbank gerade mal nicht will. Sie müssen aber hier nichts setzen (Danger-Zone) und können das SSHA-Passwort eines gültigen DN in Ihrer Benutzerdatenbank nutzen. Die ganz Mutigen machen das auch für cn=config, denn wenn für den RootDN von cn=config kein passwort gefunden wird und auch kein olcRootPW sind Sie zumindest remote erstmal aus der Server-Konfiguration ausgesperrt.
Beispiel: olcRootDN ist cn=admin,cn=config und hat ein olcRootPW. Es ist nur dieser Login gültig, da cn=config nirgendwo sonst Benutzerpasswörter speichert.
Beispiel 2: olcRootDN ist cn=admin,dc=domain,dc=tld und hat kein olcRootPW. Es muss die Datenbank für dc=domain,dc=tld aktiv sein und dort unbedingt ein cn=admin Eintrag existieren. Setzen Sie nun aber für olcRootPW auch noch ein Passwort, dann können Sie sich mit beiden anmelden und das wird Sie später verwirren.
Beispiel 3: olcRootDN ist cn=admin,dc=domain,dc=tld und hat ein olcRootPW, es existiert in dc=domain,dc=tld kein cn=admin Eintrag. Das ist halbwegs sauber und sperrt Sie bei der Einrichtung neuer Domain-Datenbanken nicht aus diesen aus.
config error processing olcDatabase={2}mdb,cn=config: <olcRootPW> can only be set when rootdn is under suffix
Hier hat man für die Domain2-Datenbank ein cn=admin,dc=domain1,dc=tld als olcRootDN gesetzt, das würde aber in olcDatabase{1} nachgeschlagen. Der Server startet dann nicht mehr. Sie können ein olcRootPW für einen DN der als suffix / basedn verwaltet wird setzen, aber nur in der jeweiligen Datenbank-Definition.
Das verkompliziert die Sache nochmals, weshalb es sinnvoller ist olcRootPW nur für cn=admin,cn=config in olcDatabase={0}config zu setzen.
Bei der Einrichtung macht also Sinn für cn=config oder dc=domain,dc=tld jeweils einen cn=admin zu setzen und auch ein olcRootPW zu vergeben, denn dann sperren Sie sich nicht aus. Sobald alle Datenbanken laufen können Sie die olcRootPW Einträge rausnehmen.
olcDatabase={-1}frontend,cn=config
olcDatabase={0}config,cn=config
olcDatabase={1}mdb,cn=config
An dieser Stelle können Sie auch mehrere BaseDNs von OpenLDAP servieren lassen indem Sie eine neue olcDatabase=mdb erstellen. Wichtig: Muss ein anderes olcDbDirectory besitzen, z.B. /var/lib/ldap/domain.tld
.
So aufgeteilt können Sie mit cn=admin,cn=config auch remote neue Domains hinzufügen aber müssen für die jeweiligen Admins nur den für eine Domain notwendigen cn=Verwalter aushändigen.
📝 In einer neuen Datenbank setzen Sie den BaseDN in der Root DSE mittels "New Context Entry"
📝 Mit mehreren Datenbanken erhalten alle Clienten über Zugriff auf namingContexts Wissen über alle anderen BaseDNs. Wer das nicht will kann in olcDatabase{-1}frontend folgende olcAccess-Regeln einfügen um nur selektiv namingContexts anzuzeigen:
{1}to dn.exact="" attrs=namingContexts val/distinguishedNameMatch="dc=example,dc=com" by dn.subtree="dc=example,dc=com" read
{2}to dn.exact="" attrs=namingContexts val/distinguishedNameMatch="dc=example,dc=net" by dn.subtree="dc=example,dc=net" read
Das fängt Anfragen zur RootDSE ab (dn.exact="") für das Attribut namingContexts mit dem bestimmten Wert für die Domain und zeigt es nur für Logins unter dieser Domain/BaseDN an.
slappasswd
generiert SSHA-Passwörter, das brauchen Sie im LDIF.
Folgendes LDIF nutzen:
dn: olcDatabase={0}config,cn=config
changetype: modify
replace: olcRootDN
olcRootDN: cn=admin,cn=config
-
replace: olcRootPW
olcRootPW: {SSHA}xxxxxxxxxxxxxxxxxxxxx
Einspielen mit:
ldapmodify -Y EXTERNAL -H ldapi:/// -f /etc/ldap/allowcnadmincnconfig.ldif
Jetzt können Sie auch mit Apache Directory Studio, etc. eine Verbindung zu cn=config nutzen. Den BaseDN dort ignorieren / nicht fetchen und explizit cn=config angeben.
Das Thema olcAccess behandeln wir ausführlicher in OpenLDAP-olcAccess