Wikiraum

Benutzer-Werkzeuge

Webseiten-Werkzeuge


openLDAP installieren

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).

openLDAP absichern

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.

openLDAP für die Nutzung von TLS überreden

Zertifikate vorbereiten

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.

  1. domain.tld-crt.pem (enthält das Zertifikat)
  2. domain.tld-key.pem (enthält den privaten Schlüssel für das Zertifikat)
  3. startssl.pem (enthält die Zwischenzertifikate von startssl, erst den Inhalt von ca.pem, dann dahinter den von sub.class1.server.ca.pem)

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:

tls_ldap.ldif
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:

iserv_tls.ldif
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:///" 

TLS für Verbindungen erzwingen

force_tls.ldif
dn: olcDatabase={1}hdb,cn=config
changetype:  modify
add: olcSecurity
olcSecurity: tls=1
ldapadd -Y EXTERNAL -H ldapi:/// -f <force_tls.ldif>

Brute force 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.

ppolicy.ldif
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:

loadmodule.ldif
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:

policycontext.ldif
 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):

defaultpolicy.ldif
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:

overlay.ldif
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

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.

Folgende Datei anlegen:

noanonymous_bind_ldap.ldif
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.

openLDAP durch Indizes tunen

Folgende Datei anlegen:

indexes_ldap.ldif
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>

Benutzer für die Administration von cn=config einrichten (optional)

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:

cn_config_admin.ldif
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:

cn_config_adminaccount.ldif
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

slapd neu starten und prüfen

service slapd restart

Wenn alles gutgegangen ist und slapd die Zertifikate gefressen hat, sollte ein

netstat -tulpen

einen offenen Port 389 zeigen.

Administratoraccounts für Unterbäume einrichten

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.

toggle_acl.ldif
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.

Paranoides / Datenschutz

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
  • a beschreibt, auf welches Objekt im LDAP Zugriff gewährt wird (hier unser Unterbaum)
  • b beschreibt, dass das Objekt „cn=subtreeadmin,dc=domain,dc=tld“ Schreibzugriff (write) auf den Unterbaum erhält
  • c beschreibt, dass alle anderen Objekte (also auch die aus ggf. anderen Unterbäumen) Leserechte (read) bekommen
  • d beschreibt, dass anonyme Nutzer nichts dürfen (die Regel ist im Prinzip überflüssig, da OpenLDAP grundsätzlich alle Rechte für nicht in einer ACL erwähnten Objekte entzieht, aber aus Gründen der Lesbarkeit für Menschen schreibt man das gerne noch so)

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.