Hinweis: domain.tld muss man natürlich immer an den eigenen openLDAP anpassen.
Zuerst installieren wir die Binaries.
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. Ein
dpkg-reconfigure slapd
ermöglicht uns die Eingabe einer korrekten Basis-DN (auf die muss unser SSL-Zertifikat ausgestellt sein).
Ab Debian Squeeze speichert der OpenLDAP-Server seine Konfiguration in einem internen LDAP-Baum und nicht mehr in einem Konfigurationsfile. Das macht die Pflege erheblich aufwändiger, weil man an diesen Baum in der Standardkonfiguration nur umständlich über Konsolentools herankommt. Nur der Rootbenutzer des Systems kommt immer auch direkt an die Daten. Man kann sich die bestehenden Inhalt nur als root anzeigen lassen mit:
ldapsearch -Y EXTERNAL -H ldapi:/// -b "cn=config"
Neue Einträge können über *.ldif-Files hinzugefügt werden:
ldapmodify/ldapadd -Y EXTERNAL -H ldapi:/// -f <filename.ldif>
Davon machen wir jetzt fleißig Gebrauch.
De kostenlosen Zertifikate von startSSL (Anleitung zum Erstellen z.B. hier) können wir auch für unseren openLDAP-Server nutzen. Benötigt werden drei Dateien, auf die der Benutzer, unter dem slapd läuft, lesend zugreifen können muss.
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 anlegen:
dn: cn=config add: olcTLSCACertificateFile olcTLSCACertificateFile: /etc/ldap/ssl/startssl.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:
ldapmodify -Y EXTERNAL -H ldapi:/// -f <tls_ldap.ldif>
Speziell für IServ könnte man einfach die ohnehin vorhandenen Zertifikate mitnutzen:
dn: cn=config add: olcTLSCACertificateFile olcTLSCACertificateFile: /etc/ssl/certs/iserv.chain - add: olcTLSCertificateKeyFile olcTLSCertificateKeyFile: /etc/ssl/private/iserv.key - add: olcTLSCertificateFile olcTLSCertificateFile: /etc/ssl/certs/iserv.crt
… da aber der openLDAP von IServ bzw. Debian wheezy nicht gegen OpenSSL gelinkt ist, schlägt das fehl, bzw. der openLDAP wird dann unbenutzbar.
Jetzt in /etc/default/slapd nachschauen, ob die Services stimmen (ldaps über Port 639 gilt als veraltet und sollte nicht mehr verwendet werden).
SLAPD_SERVICES="ldap://0.0.0.0:389/ ldapi:///"
dn: olcDatabase={1}hdb,cn=config changetype: modify add: olcSecurity olcSecurity: tls=1
ldapadd -Y EXTERNAL -H ldapi:/// -f <force_tls.ldif>
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.
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:
ldapadd -Q -Y EXTERNAL -H ldapi:/// -f ppolicy.ldif
ppolicy-Modul in openLDAP laden:
dn: cn=module{0},cn=config changetype: modify add: olcModuleLoad olcModuleLoad: ppolicy.la
ldapadd -Q -Y EXTERNAL -H ldapi:/// -f loadmodule.ldif
Und openLDAP zur Sicherheit einmal neu starten:
service slapd restart
Policykontext erzeugen:
dn: ou=policies,dc=domain,dc=tld objectClass: organizationalUnit objectClass: top ou: policies
ldapadd -Q -Y EXTERNAL -H ldapi:/// -f policycontext.ldif
Standardverhalten definieren (Default-Policy):
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.
ldapadd -Q -Y EXTERNAL -H ldapi:/// -f defaultpolicy.ldif
Es fehlt nur noch die Einrichtung eines Overlays:
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
ldapadd -Q -Y EXTERNAL -H ldapi:/// -f overlay.ldif
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.
Folgende Datei anlegen:
dn: olcDatabase={1}hdb,cn=config add: olcRequires olcRequires: authc dn: olcDatabase={-1}frontend,cn=config add: olcRequires olcRequires: authc
… und über die Konsole einspielen:
ldapmodify -Y EXTERNAL -H ldapi:/// -f <noanonymous_bind_ldap.ldif>
Nach dem Einspielen der letzten Änderung hat man ohne Authentifizierung auch über die Konsole keinen Zugriff mehr auf den Hauptbaum des openLDAP. Man muss dann ausweichen auf eine andere Befehlszeile (cn=config ist davon nicht betroffen):
ldapadd -x -D cn=admin,dc=domain,dc=tld -W -f <example.ldif>
Danach wird man zur Eingabe des Adminpasswortes aufgefordert.
Folgende Datei anlegen:
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
… und über die Konsole einspielen:
ldapmodify -Y EXTERNAL -H ldapi:/// -f <indexes_ldap.ldif>
Wenn man 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:
slappasswd -h {SSHA}
Wir erhalten einen Hash zurück, den wir in die Zwischenablage kopieren. Jetzt erstellten wir ein *.ldif-File:
dn: olcDatabase={0}config,cn=config changetype: modify add: olcRootPW olcRootPW: {SSHA}<unser_hash_von_eben> # 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
Und auch dieses File spielen wir ein:
ldapmodify -Y EXTERNAL -H ldapi:/// -f <cn_config_admin.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: Debian Wiki
Böse Falle bei 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. Um das auf den Standard zu ändern, benötigt man nur für Ubuntu 14.04 LTS noch eine kleine Änderung:
dn: olcDatabase={0}config,cn=config changetype: modify replace: olcRootDN olcRootDN: cn=admin,cn=config
ldapmodify -Y EXTERNAL -H ldapi:/// -f cn_config_adminaccount.ldif
service slapd restart
Wenn alles gutgegangen ist und slapd die Zertifikate gefressen hat, sollte ein
netstat -tulpen
einen offenen Port 389 zeigen.
Wenn man den LDAP mit mehreren Instanzen mit jeweils getrennten Unterbäumen nutzt, möchte man eigentlich gerne vermeiden, dass alle Scripten mit den Rechten der Hauptadministrators von OpenLDAP arbeiten, sondern vielmehr erreichen, dass jedes Script auf „seinen“ Unterbaum beschränkt bleibt. Das geht bei OpenLDAP mit Hilfe von ACLs, die von oben nach unten abgearbeitet werden, d.h. was oben verboten wurde, kann weiter unten nicht mehr erlaubt sein - und ist die ACL auch noch zu freizügig. Daher müssen unsere neuen ACLs ganz nach oben.
dn: olcDatabase={1}hdb,cn=config changetype: modify replace: olcAccess {0}to dn.subtree="ou=subtree,dc=domain,dc=tld" by dn="cn=subtreeadmin,dc=domain,dc=tld" write by * read by anonymous none {1}to attrs=userPassword,shadowLastChange by self write by anonymous auth by dn="cn=admin,dc=domain,dc=tld" write by * none {2}to dn.base="" by * read {3}to * by self write by dn="cn=admin,dc=domain,dc=tld" write by * read
In diesem Beispiel bekommt der Nutzer „cn=subtreeadmin,dc=domain,dc=tld“ Schreibrecht im Unterbaum „ou=subtree,dc=domain,dc=tld“.
ldapmodify -Y EXTERNAL -H ldapi:/// -f toggle_acl.ldif
In Bezug auf IServ möchte man ggf. allen Nutzern der Gruppe admins dieses Schreibrecht geben. Wenn sich die Nutzer eines IServ z.B. im Unterbaum „ou=subtree, dc=domain, dc=tld“ befinden, lässt sich das durch folgende ACL implementieren:
{0}to dn.subtree="ou=subtree, dc=domain, dc=tld" by group.exact="cn=admin, ou=groups, ou=subtree, dc=domain, dc=tld" write by * read by anonymous none
Für verschiedene IServs kann man nun einfach verschiedene ACLs setzen:
{0}to dn.subtree="ou=subtree01 ,dc=domain, dc=tld" by group.exact="cn=admin, ou=groups, ou=subtree01, dc=domain, dc=tld" write by * read by anonymous none {1}to dn.subtree="ou=subtree02 ,dc=domain, dc=tld" by group.exact="cn=admin, ou=groups, ou=subtree02, dc=domain, dc=tld" write by * read by anonymous none [...] {n}to dn.subtree="ou=subtree0n ,dc=domain, dc=tld" by group.exact="cn=admin, ou=groups, ou=subtree0n, dc=domain, dc=tld" write by * read by anonymous none
Damit kann ich bequem im IServ Nutzer der Gruppe „admins“ zuweisen, die dann Schreibrecht im Unterbaum gewährt bekommen.
Wir schauen uns die ACL einmal genauer an, was durch eine etwas andere Schreibweise einfacher wird:
(a) to dn.subtree="ou=subtree,dc=domain,dc=tld" (b) by dn="cn=subtreeadmin,dc=domain,dc=tld" write (c) by * read (d) by anonymous none
Gäbe es c nicht, könnten Nutzer sich gar nicht authentifizieren, da ihre eigenen Einträge gar nicht lesen dürften. Man kann jetzt © zusätzlich beschränken:
a to dn.subtree="ou=subtree,dc=domain,dc=tld" b by dn="cn=subtreeadmin,dc=domain,dc=tld" write c by dn.subtree="cn=subtree,dc=domain,dc=tld" read d by anonymous none
Damit können jeweils nur Nutzer des jeweiligen Unterbaumes ihre Einträge lesen.