OpenLDAP

From HerzbubeWiki

Jump to: navigation, search

Contents

Debian packages

slapd
ldap-utils
phpldapadmin
sasl-bin
libsasl-modules-plain


References

OpenLDAP Administrator's Guide 
http://www.openldap.org/doc/admin/
LDAP HOWTO (no longer updated) 
http://howtos.linuxbroker.com/howtoreader.php?file=LDAP-HOWTO.html
RFC 2307 (definitions for nis.schema
ftp://ftp.rfc-editor.org/in-notes/rfc2307.txt


Glossary

See also: http://www.openldap.org/doc/admin/glossary.html.


LDAP 
Lightweight Directory Access Protocol (as opposed to the X.500 "heavyweight" DAP)
directory 
  • a specialized database optimized for reading, browsing and searching (i.e. not writing)
  • a directory consists of n entries that are organized in a hierarchical tree
DIT 
directory information tree
entry 
  • an entry stores information about something as a collection of attributes
  • within a directory an entry is uniquely identified by its DN; this DN consists of the concatenation of
  • the entry's RDN (e.g. cn=John Doe)
  • and the reference to the location within the directory where the entry is stored (e.g. ou=employee, dc=example, dc=org)
  • example of complete DN: dn: cn=Tom Syroid, ou=employee, dc=syroidmanor, dc=com
DN 
distinguished name
RDN 
relative distinguished name
attribute 
  • an attribute has a type
  • an attribute has one or more values
distinguished value 
the value of an attribute that contributes to the DN of an entry; this distinction is important only for attributes that can have multiple values
base DN 
references the root of the LDAP directory tree
OU 
Organizational unit
cn 
Common name
OID 
Object identifier
SASL 
Simple authentication and security layer


Schema definition

Overview

Every attribute of a directory entry has a type. The type definition of an attribute describes the kind of data that the attribute can store. For instance, a type definition could prescribe that the attribute value must have the form of an email address. A very useful feature is that a type definition may also allow an attribute to store multiple values for the same entry.

Every directory entry has a special attribute called

objectClass

The values of the objectClass attribute defines which other attributes are required and allowed in an entry. It can be said that the values of the objectClass attribute determine the schema rules the entry must obey.

A collection of attributetype and objectclass definitions together form a schema definition.


Existing schema definitions

Not everybody using an LDAP directory wants to reinvent the wheel, therefore a number of useful schema definitions already exist (some of them are derived from RFCs and other IETF documents). When you install the slapd package you will find these schema definitions in

/etc/ldap/schema


Custom schema definitions

When you consider the type of information you want to store in your directory, you may find that the existing schema definitions in /etc/ldap/schema do not suit your needs. For instance, if you want to store information about your friends and relatives, you will find that the InetOrgPerson object class is missing an attribute for storing birthdays.

To solve the problem, you can define your own custom schema that introduces a new objectClass and/or new attribute types. Your schema may define the objectClass from scratch, or it may derive the objectClass from an already existing objectClass and extend it by adding more attributes (if you are familiar with object-oriented programming, you might compare this to the concept of creating a subclass).

You should store your custom schema definitions in

/etc/ldap/schema


OIDs

Within LDAP schemas, each object class and each attribute type must have a unique OID (object identifier). OIDs are found not only in LDAP, they are also used in many other areas of computing (for instance the SNMP protocol). OIDs are used to globally and uniquely identify any (!!!) kind of object. An overview article about OIDs can be found at Wikipedia:

http://en.wikipedia.org/wiki/Object_identifier

An OID listing service can be found here:

http://www.alvestrand.no/objectid/

When you start your own custom schema definition, you might wonder what kind of OID you should use. To quote from the OpenLDAP Administrator's Guide:

Under no circumstances should you hijack OID namespace!

To obtain a registered OID at no cost, apply for an OID under the Internet Assigned Numbers Authority (IANA) maintained Private Enterprise arc. Any private enterprise (organization) may request an OID to be assigned under this arc. Just fill out the IANA form at http://www.iana.org/cgi-bin/enterprise.pl and your official OID will be sent to you usually within a few days. Your base OID will be something like 1.3.6.1.4.1.X where X is an integer.

I have followed this advice, and I have actually received my own private OID:

1.3.6.1.4.1.18427

Warning: On the application form you have to specify an email address. It is guaranteed that you will receive spam on that address since it will be visible to the world in a public listing.


naef.schema

This schema definition currently contains some attribute type definitions that are aggregated into the custom object class addressbookEntry. The main reason why I structured the object class like it is today is because I wanted to be able to import data from the Mac OS X "Address Book" application.

# ----------------------------------------------------------------------
# Namespace structure below OID 1.3.6.1.4.1.18427
# 1.3.6.1.4.1.18427
#  +-- 1.3.6.1.4.1.18427.1.*   attributes for object class "addressBookEntry"
#  +-- 1.3.6.1.4.1.18427.2.1   object class "addressBookEntry"
#  +-- 1.3.6.1.4.1.18427.3.*   object class "bugzillaAccount" and its attributes
#  +-- 1.3.6.1.4.1.18427.4.*   object class "davicalAccount" and its attributes
# ----------------------------------------------------------------------

attributetype (
   1.3.6.1.4.1.18427.1.1
   NAME 'workPhone'
   SUP telephoneNumber )

attributetype (
   1.3.6.1.4.1.18427.1.2
   NAME 'mobilePhone'
   SUP telephoneNumber )

attributetype (
   1.3.6.1.4.1.18427.1.24
   NAME 'pagerPhone'
   SUP telephoneNumber )

attributetype (
   1.3.6.1.4.1.18427.1.3
   NAME 'otherPhone'
   SUP telephoneNumber )

attributetype (
   1.3.6.1.4.1.18427.1.4
   NAME 'descOtherPhone'
   DESC 'A description that indicates the type of otherPhone'
   EQUALITY caseIgnoreMatch
   SUBSTR caseIgnoreSubstringsMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{32768} )

attributetype (
   1.3.6.1.4.1.18427.1.25
   NAME 'preferredPhone'
   DESC 'Contains the name of an LDAP phone attribute (e.g. mobilePhone); this phone number type is the preferred one used by a person.'
   EQUALITY caseIgnoreMatch
   SUBSTR caseIgnoreSubstringsMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{512} )

attributetype (
   1.3.6.1.4.1.18427.1.5
   NAME 'homeFax'
   SUP facsimileTelephoneNumber )

attributetype (
   1.3.6.1.4.1.18427.1.6
   NAME 'workFax'
   SUP facsimileTelephoneNumber )

attributetype (
   1.3.6.1.4.1.18427.1.7
   NAME 'otherFax'
   SUP facsimileTelephoneNumber )

attributetype (
   1.3.6.1.4.1.18427.1.8
   NAME 'descOtherFax'
   DESC 'A description that indicates the type of otherFax'
   EQUALITY caseIgnoreMatch
   SUBSTR caseIgnoreSubstringsMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{32768} )

attributetype (
   1.3.6.1.4.1.18427.1.26
   NAME 'preferredFax'
   DESC 'Contains the name of an LDAP fax attribute (e.g. workFax); this fax number type is the preferred one used by a person.'
   EQUALITY caseIgnoreMatch
   SUBSTR caseIgnoreSubstringsMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{512} )

attributetype (
   1.3.6.1.4.1.18427.1.9
   NAME 'homeEmail'
   SUP mail )

attributetype (
   1.3.6.1.4.1.18427.1.10
   NAME 'workEmail'
   SUP mail )

attributetype (
   1.3.6.1.4.1.18427.1.11
   NAME 'otherEmail'
   SUP mail )

attributetype (
   1.3.6.1.4.1.18427.1.12
   NAME 'descOtherEmail'
   DESC 'A description that indicates the type of otherEmail'
   EQUALITY caseIgnoreMatch
   SUBSTR caseIgnoreSubstringsMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{32768} )

attributetype (
   1.3.6.1.4.1.18427.1.27
   NAME 'preferredEmail'
   DESC 'Contains the name of an LDAP email attribute (e.g. homeEmail); this email address type is the preferred one used by a person.'
   EQUALITY caseIgnoreMatch
   SUBSTR caseIgnoreSubstringsMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{512} )

attributetype (
   1.3.6.1.4.1.18427.1.13
   NAME 'homepage'
   SUP labeledURI )

attributetype (
   1.3.6.1.4.1.18427.1.14
   NAME 'nickName'
   SUP name )

attributetype (
   1.3.6.1.4.1.18427.1.15
   NAME 'dateOfBirth'
   DESC 'Must be in format yyyymmddHHMMZ. The trailing Z is a literal and means that the value is in GMT/UTC'
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
   EQUALITY generalizedTimeMatch
   ORDERING generalizedTimeOrderingMatch
   SINGLE-VALUE )

attributetype (
   1.3.6.1.4.1.18427.1.16
   NAME ( 'imICQ' 'instantMessagingNumberICQ' )
   EQUALITY caseIgnoreMatch
   SUBSTR caseIgnoreSubstringsMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1024} )

attributetype (
   1.3.6.1.4.1.18427.1.17
   NAME ( 'imAIM' 'instantMessagingNumberAIM' )
   EQUALITY caseIgnoreMatch
   SUBSTR caseIgnoreSubstringsMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1024} )

attributetype (
   1.3.6.1.4.1.18427.1.18
   NAME ( 'imJabber' 'instantMessagingNumberJabber' )
   EQUALITY caseIgnoreMatch
   SUBSTR caseIgnoreSubstringsMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1024} )

attributetype (
   1.3.6.1.4.1.18427.1.19
   NAME ( 'imMSN' 'instantMessagingNumberMSN' )
   EQUALITY caseIgnoreMatch
   SUBSTR caseIgnoreSubstringsMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1024} )

attributetype (
   1.3.6.1.4.1.18427.1.20
   NAME ( 'imYahoo' 'instantMessagingNumberYahoo' )
   EQUALITY caseIgnoreMatch
   SUBSTR caseIgnoreSubstringsMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1024} )

attributetype (
   1.3.6.1.4.1.18427.1.21
   NAME ( 'imOther' 'instantMessagingNumberOther' )
   EQUALITY caseIgnoreMatch
   SUBSTR caseIgnoreSubstringsMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1024} )

attributetype (
   1.3.6.1.4.1.18427.1.22
   NAME ( 'descIMOther' 'descInstantMessagingNumberOther' )
   DESC 'A description that indicates the type of instantMessagingNumberOther'
   EQUALITY caseIgnoreMatch
   SUBSTR caseIgnoreSubstringsMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{32768} )

attributetype (
   1.3.6.1.4.1.18427.1.28
   NAME ( 'preferredIM' 'preferredInstantMessagingNumber' )
   DESC 'Contains the name of an LDAP IM number attribute (e.g. imICQ); this IM number type is the preferred one used by a person.'
   EQUALITY caseIgnoreMatch
   SUBSTR caseIgnoreSubstringsMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{512} )

attributetype (
   1.3.6.1.4.1.18427.1.23
   NAME 'partner'
   EQUALITY caseIgnoreMatch
   SUBSTR caseIgnoreSubstringsMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1024} )

attributetype (
   1.3.6.1.4.1.18427.1.29
   NAME 'addressType'
   DESC 'Describes the type of the address for a person (e.g. home, work).'
   EQUALITY caseIgnoreMatch
   SUBSTR caseIgnoreSubstringsMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )

# As a reference, the three objectclasses that addressbookEntry is derived from
# are listed here.
#
# From core.schema:
# objectclass ( 2.5.6.6 NAME 'person'
#       DESC 'RFC2256: a person'
#       SUP top STRUCTURAL
#       MUST ( sn $ cn )
#       MAY ( userPassword $ telephoneNumber $ seeAlso $ description ) )
#
# From core.schema:
# objectclass ( 2.5.6.7 NAME 'organizationalPerson'
#       DESC 'RFC2256: an organizational person'
#       SUP person STRUCTURAL
#       MAY ( title $ x121Address $ registeredAddress $ destinationIndicator $
#               preferredDeliveryMethod $ telexNumber $ teletexTerminalIdentifier $
#               telephoneNumber $ internationaliSDNNumber $
#               facsimileTelephoneNumber $ street $ postOfficeBox $ postalCode $
#               postalAddress $ physicalDeliveryOfficeName $ ou $ st $ l ) )
#
# From inetorgperson.schema:
# objectclass   ( 2.16.840.1.113730.3.2.2
#    NAME 'inetOrgPerson'
#       DESC 'RFC2798: Internet Organizational Person'
#    SUP organizationalPerson
#    STRUCTURAL
#       MAY (
#               audio $ businessCategory $ carLicense $ departmentNumber $
#               displayName $ employeeNumber $ employeeType $ givenName $
#               homePhone $ homePostalAddress $ initials $ jpegPhoto $
#               labeledURI $ mail $ manager $ mobile $ o $ pager $
#               photo $ roomNumber $ secretary $ uid $ userCertificate $
#               x500uniqueIdentifier $ preferredLanguage $
#               userSMIMECertificate $ userPKCS12 )
#       )

objectclass (
   1.3.6.1.4.1.18427.2.1
   NAME 'addressbookEntry'
   SUP inetOrgPerson
   DESC 'Data for one addressbook entry'
   STRUCTURAL
   MAY
   (
      nickName $ countryName $ addressType $
      homePhone $ workPhone $ mobilePhone $ pagerPhone $ otherPhone $ descOtherPhone $ preferredPhone $
      homeFax $ workFax $ otherFax $ descOtherFax $ preferredFax $
      homeEmail $ workEmail $ otherEmail $ descOtherEmail $ preferredEmail $
      imICQ $ imAIM $ imJabber $ imMSN $ imYahoo $ imOther $ descIMOther $ preferredIM $
      homepage $ dateOfBirth $ partner
   ))

# ----------------------------------------------------------------------
# Define an auxiliary object class that can be used to augment a
# directory entry with a single email address attribute. This email
# address attribute is intended for use by the Bugzilla bugtracker.
# ----------------------------------------------------------------------
attributetype (
   1.3.6.1.4.1.18427.3.2
   NAME 'bugzillaEmail'
   SUP mail )

objectclass (
   1.3.6.1.4.1.18427.3.1
   NAME 'bugzillaAccount'
   SUP top
   DESC 'Attributes that describe a Bugzilla account'
   AUXILIARY
   MUST bugzillaEmail
   MAY displayName )

# ----------------------------------------------------------------------
# Define an auxiliary object class that can be used to identify a
# directory entry as a DAViCal account.
# ----------------------------------------------------------------------
attributetype (
   1.3.6.1.4.1.18427.4.2
   NAME 'davicalEmail'
   SUP mail )

objectclass (
   1.3.6.1.4.1.18427.4.1
   NAME 'davicalAccount'
   SUP top
   DESC 'Attributes that describe a DAViCal account'
   AUXILIARY
   MUST davicalEmail
   MAY displayName )


Notes about schema definition

  • the opening paranthesis of an objectclass or attributetype definition must be located on the same line as the keyword
  • the closing paranthesis of an objectclass or attributetype definition must be located on the last line of the definition; the closing paranthesis cannot be placed alone on the last line!!!


Organisation

Basics

Base DN:

dc=herzbube,dc=ch

Top-level entries:

ou=addressbook
ou=bookmarks
ou=groups
ou=hosts
ou=users


ou=users

Entries located below ou=users,dc=herzbube,dc=ch represent users or roles that are needed for authentication in various systems, services and applications. The minimal requirement is that the entries possess a name and a password attribute.


Name:

  • the actual attribute that provides the "name" value varies, depending on the system, service or application performing the authentication
  • for instance, LDAP authentication uses the cn attribute because cn is the RDN attribute
  • PAM on the other hand uses the uid attribute


Password:

  • the main attribute that provides the "password" value always is the userPassword attribute
  • certain services may store the password in various representations in additional attributes (e.g. Samba stores an MD4 hash in sambaNTPassword)


Core rules that an ou=users entry must follow:

  • objectClass = organizationalRole
    • provides basic attributes such as cn and description
    • cn is the RDN attribute of the entry
    • this is the structural object class
  • objectClass = simpleSecurityObject
    • adds the attribute userPassword
    • the password stored in this attribute is used for binding to the LDAP directory
    • binding occurs...
      • ... either with the goal to gain access to parts of the directory
      • ... or to perform authentication on behalf of an external system, service or application
      • in the latter case, the external system, service or application may also be interested in accessing additional attributes of the entry such as "displayName" to get the full user name
    • this is an auxiliary object class


More rules that depend on the system, service or application that should be able to use the entry:

  • objectClass = uidObject
    • adds the attribute uid; the value must be unique (i.e. no two entries must have the same value)
    • must be used on entries that are used to login to a service or application (entries typically represent human users or user roles)
    • may be used on entries that represent UNIX system users, although such entries have objectClass = posixAccount which also provides the uid attribute
    • must not be used on entries that exist only due to IT requirements (e.g. an entry that a webmail application uses to bind for searching the address book)
    • the value of the uid attribute is the user name entered during a classic login procedure
    • this is an auxiliary object class
  • objectClass = posixAccount
    • must be used on entries that represent UNIX system user accounts
    • adds various attributes that describe the user account in a similar fashion as it would appear in /etc/passwd
    • see PAM page for details
    • this is an auxiliary object class
  • objectClass = shadowAccount
    • must be used on entries that represent UNIX system user accounts
    • adds various attributes that describe the user account in a similar fashion as it would appear in /etc/shadow
    • see PAM page for details
    • this is an auxiliary object class
  • objectClass = bugzillaAccount
    • must be used on entries that are used to access Bugzilla
    • see Bugzilla page for details
    • this is an auxiliary object class
  • objectClass = sambaSamAccount
    • must be used on entries that are used to access the Samba service
    • see Samba page for details
    • this is an auxiliary object class
  • objectClass = davicalAccount
    • must be used on entries that are used to access DAViCal
    • see DAViCal page for details
    • this is an auxiliary object class


ou=groups

Entries located below ou=groups,dc=herzbube,dc=ch represent UNIX system groups traditionally stored in /etc/group. They must follow these rules:

  • objectClass = posixGroup
    • the memberUid attribute must contain one value for each member of the group
    • a memberUid value must correspond to an uid attribute value of one of the entries below ou=groups,dc=herzbube,dc=ch
    • the gidNumber attribute value must be a unique numerical ID
    • see PAM page for details
    • this is the structural object class
  • objectClass = sambaGroupMapping
    • must be used on entries that are used to access the Samba service
    • see Samba page for details
    • this is an auxiliary object class


ou=hosts

Entries located below ou=hosts,dc=herzbube,dc=ch represent IP hosts traditionally stored in /etc/hosts. They must follow these rules:

  • objectClass = ipHost
    • adds the essential attributes cn and ipHostNumber
    • the value of cn that contributes to the DN (the distinguished value) is the canonical name of the host (see RFC2307)
    • additional values of cn are the aliases of the host
    • this is an auxiliary object class
  • objectClass = device
    • the definition of object class ipHost in RFC2307 says that "device SHOULD be used as a structural class"
    • note that this recommendation is not present in /etc/ldap/schema/nis.schema, it must be looked up in the RFC document itself
    • currently I don't use any of the attributes of device, I just follow the recommendation of RFC2307
    • this is the structural object class


ou=samba

There is currently only one entry located below ou=samba,dc=herzbube,dc=ch which represents the Samba domain for the file server osgiliath.

This entry is not manually crafted. It is automatically created by Samba, as sambaDomainName=OSGILIATH,dc=herzbube,dc=ch, when Samba is restarted after its configuration has been changed to use LDAP.

To remove clutter from the base DN dc=herzbube,dc=ch, I created the ou=samba subtree and manually moved the sambaDomain entry to this subtree. It appears that this works because Samba does not search for the sambaDomain entry in a specific subtree, but instead starts its search in the LDAP directory root. I do not expect this to have much negative impact because I do not expect many Samba connections. Hopefully, proper indexing will also reduce search time.


Binding

The act of being authenticated by an LDAP directory is called "binding".

To bind, a client must specify a DN and a password. For successful binding, the entry matching the specified DN must contain an attribute userPassword whose value matches the specified password. The value of the attribute is stored as

{encoding type}<encoded password>

There are 4 types of authentication

  • anonymous: an empty DN and password are transmitted
  • simple: the password is transmitted in the clear
  • SSL/TLS: the password is transmitted over an encrypted transport layer
    • SSL = port 636
    • TLS = port 389
  • SASL: authentication via negotiable scheme

Note on TLS: The client must explicitly ask for TLS encryption by issuing the LDAP command StartTLS.

Note on SASL: Authentication happens before the optional encrypted transport layer is negotiated, i.e. the password hash (or the clear-text password if the negotiated scheme is PLAIN) is not transmitted over the encrypted layer!


Configuration

slapd.conf vs. slapd-config

In older versions of OpenLDAP, the daemon/server configuration was stored in a single file

/etc/ldap/slapd.conf

Support for this file is going to be withdrawn in some future version of OpenLDAP. The reason for this is that OpenLDAP 2.3 introduced a new type of configuration called slapd-config. Data for this configuration system is stored in a collection of LDIF files that are located in this folder

/etc/ldap/slapd.d


slapd-config

From the point of view of the LDAP server, the slapd-config configuration system is just another LDAP directory with the following properties

  • Its root entry has the DN cn=config
  • It is managed using standard LDAP operations
  • It has a special pre-defined schema
  • It is managed by a special backend which is responsible for storing the configuration data in LDIF files
  • Changing the configuration usually does not require a server restart for the changes to take effect

Useful references are

Important note: Although the slapd-config configuration system stores its data as LDIF files, these files should never be edited! Configuration changes should always be performed via LDAP operations, e.g. using ldapadd</code, <code>ldapdelete, or ldapmodify.


Basic

The configuration file /etc/ldap/slapd.conf contains the options for the slapd daemon. It contains 0-n database definitions and options, and a number of global options, i.e. options that are not tied to any specific database.

The following list contains some important/interesting global options:

  • If a password attribute value does not have a prefix, what is the default prefix that should be assumed?
password-hash {SSHA}
  • allow binding through LDAPv2 (some applications may require this; known example: ldapnavigator)
allow bind_v2
  • include your own schema definition(s)
include /etc/ldap/schema/naef.schema


SSL/TLS

Overview

There are two ways how communication between LDAP server/client can be encrypted with SSL:

  • LDAP over SSL (LDAPS): through Port 636
  • TLS: through Port 389 (because normal communication over this port is not encrypted, the client must explicitly ask for TLS encryption by issiuing the LDAP command StartTLS)

Server configuration

In /etc/ldap/slapd.conf, the following global entries are required:

  • Key length >=128 Bits is preferred (HIGH); for details see the OpenSSL man page about the ciphers option, or list the ciphers that will get selected by the value of this option with openssl ciphers -v HIGH:MEDIUM
TLSCipherSuite HIGH:MEDIUM
  • Where the server certificate and its RSA key can be found
TLSCertificateFile      /etc/ssl/certs/ldap.herzbube.ch.crt
TLSCertificateKeyFile   /etc/ssl/private/ldap.herzbube.ch.key.unsecure
  • Where the CA certificate can be found (only necessary if the server certificate is signed; note: specify the entire certificate chain up to the root certificate)
TLSCACertificateFile /etc/ssl/certs/cacert.org.certchain
  • The server never asks the client for a certificate
TLSVerifyClient never
  • Minimum TLS strength encryption that is required (if this is specified, the use of TLS is enforced!)
security tls=128

Note about TLSVerifyClient: It would be nice if we could say "try" for this option, in which case the server would ask the client for a client certificate: if the client provides none the session proceeds normally, but if the client provides a certificate, that certificate must be valid. Unfortunately, we can't use "try" because this doesn't always seem to work - for instance, ldapsearch seems to always send a certificate on the server's request, but it is a bad certificate, and I have no idea where ldapsearch gets that certificate from...

Client configuration

If the server certificate is signed by a CA (i.e. it is not self-signed), all LDAP clients need access to the CA certificate so that the server certificate's validity can be verified. If the server certificate was signed by an intermediate CA, the entire certificate chain up to the root CA must be known to the client.


For clients that run on osgiliath (e.g. ldapsearch, PHP applications), the CA certificate can easily be configured in /etc/ldap/ldap.conf:

TLS_CACERT /etc/ssl/certs/cacert.org.certchain


For clients that run on other machines, the CA certificate must be made known in other ways.


Authentication

Regular authentication

To prevent anonymous access, specify either one of the following options in /etc/ldap/slapd.conf:

require authc
disallow bind_anon


SASL authentication

See SASL for an overview of SASL.

To use SASL in OpenLDAP, add these global options to /etc/ldap/slapd.conf:

sasl-host ldap.herzbube.ch   [Note: use FQDN]
sasl-real ldap
sasl-secprops noplain,noanonymous

Note: the above has is largely untested and probably does not work properly :-(

To enforce SASL authentication:

require SASL


Database definition

Basic

  • starting a database definition, at the same time defines the backend type of the database
database bdb
  • the base DN of the directory
suffix "dc=herzbube,dc=ch"
  • the file system location of the directory
directory "/var/lib/ldap/herzbube.ch"


Indices

Indices are vital for accessing a directory's content by searching. No indices, or the wrong indices, will cause a directory search to take an unbearable amount of time.

Every index of a directory starts with the index keyword. Next comes the name of the attribute that should be indexed. The last part is a comma-separated list of index types. For instance:

index cn sub,approx

The four index types are:

  • approx (approximate): index the information for an approximate, or phonetic, match of an attribute's value
  • eq (equality): index the information necessary to perform an exact match of an attribute value; the matching rule in the attribute's syntax defines whether the match may be case-sensitive or whitespace-sensitive
  • pres (presence): index the information necessary to determine if an attribute has any value at all
  • sub (substring): index the information necessary to perform a simple substring match on attribute values

My indices are:

# Basic indices
index objectClass eq
index cn sub,approx
# Indices for addressbookEntry
index mail sub,approx
index description sub,approx
index l sub,approx
# Indices for PAM/NSS (taken from Samba3-HOWTO)
index uidNumber eq
index gidNumber eq
index memberUid eq
# Indices for Samba (taken from Samba3-HOWTO)
index sambaSID eq,sub
index sambaPrimaryGroupSID eq
index sambaDomainName eq
index default sub
## required to support pdb_getsampwnam
index uid pres,sub,eq
## required to support pdb_getsambapwrid()
index displayName pres,sub,eq

After changing the index configuration, the indices need to be updated/regenerated using the slapindex tool. Unfortunately, this requires that the slapd daemon must be stopped first. For instance, to re-index database 1:

/etc/init.d/slapd stop
/usr/sbin/slapindex -n 1
/etc/init.d/slapd start

After running slapindex as root, any index files now owned by root must be given back to the user that slapd is running as:

chown openldap:openldap /var/lib/ldap/herzbube.ch/*


Caching

Another means to increase performance when accessing LDAP directories is caching.

TBD

The O'Reilly book talks of caching a number of entries. The Debian sample configuration file slapd.conf contains an option that reserves memory for DB caching.


Access rights

It is possible to specify credentials for a super user account in /etc/ldap/slapd.conf. Personally, I do not like this as it means that yet another file needs to be protected because it contains a password. Instead, I prefer to use ACLs for managing the access rights to my directory.

An ACL (access control list) is a rule that defines who has what kind of access rights to what.

Possible forms of "who" are

  • * matches any connect user (including anonymous connections)
  • self matches the DN of the currently connected (and authenticated) user
  • anonymous matches non-authenticated connections
  • users matches authenticated connections
  • regular expression matches a DN

Possible types of access rights are

  • write
  • read
  • search
  • compare
  • auth (access only for the purpose of binding/authenticating)
  • none


The order in which ACLs appear in slapd.conf is important! The first ACL that matches an operation is used for that operation. This means that more specific ACLs must appear before less specific ones.

Example for an ACL:

access to attrs=userPassword,shadowLastChange
        by dn="cn=admin,ou=users,dc=herzbube,dc=ch" write
        by anonymous auth
        by self write
        by * none

What does this ACL mean? It refers to the attributes userPassword and shadowLastChange. It restricts access to the attributes as follows:

  • the attributes can be changed (write) by the entry owning them if they are authenticated (self)
  • anonymous users have access only for the purpose of binding
  • others have no access at all
  • except the admin entry, which can change the attributes everywhere


Note: An ACL that does not explicitly end with a statement that regulates access for "*" has a default statement "by * none"


At the moment I have defined the following ACLs for my directory (keep in mind that I have disabled anonymous access by specifying "disallow bind_anon" in slapd.conf):

# Default access
# - the admin dn has full write access (specify this access right
#   at the beginning so we don't have to repeat it again on each rule)
# - all others have no access; the "break" statement tells slapd to
#   continue evaluating rules: the result is that certain rules
#   further down may give the user more rights on specific DNs
access to *
  by dn.exact="cn=admin,ou=users,dc=herzbube,dc=ch" write
  by * none break

# The userPassword, of course, needs to be accessible by anonymous
# for authentication (binding). Once authenticated, it can be changed
# by the entry owning it (this is used e.g. by PAM). Others should
# not be able to see it, except the libnss-ldap-root entry (which is
# used by libnss-ldap if it runs as root)
access to attrs=userPassword,shadowLastChange
  by anonymous auth
  by self write
  by dn.exact="cn=libnss-ldap-root,ou=users,dc=herzbube,dc=ch" read
  by * none

# The Samba password-related attributes need to be accessible only
# by the samba-service dn.
access to attrs=sambaNTPassword,sambaLMPassword,sambaPwdLastSet
  by dn.exact="cn=samba-service,ou=users,dc=herzbube,dc=ch" write
  by self read
  by * none

# Everyone should have read-access to the directory base DN
# -> this allows users to navigate the directory tree to
#    ou=users and from there further on to their "self" entry
access to dn.exact="dc=herzbube,dc=ch"
  by * read

# Everyone should have read-access to the ou=users entry
# -> this allows users to navigate the directory tree to
#    their "self" entry
access to dn.exact="ou=users,dc=herzbube,dc=ch"
  by * read
# Access to user entries
# - read-only access by DNs that require access to user accounts
# - read+write access by the entry itself
# - no access to everybode else
access to dn.children="ou=users,dc=herzbube,dc=ch"
  by dn.exact="cn=readonly-users,ou=users,dc=herzbube,dc=ch" read
  by dn.exact="cn=libnss-ldap,ou=users,dc=herzbube,dc=ch" read
  by dn.exact="cn=libnss-ldap-root,ou=users,dc=herzbube,dc=ch" read
  by dn.exact="cn=samba-service,ou=users,dc=herzbube,dc=ch" read
  by self write
  by * none

# Access to group entries
# - read-only access by DNs that require access to group entries
# - no access to everybode else
access to dn.exact="ou=groups,dc=herzbube,dc=ch"
  by dn.exact="cn=readonly-users,ou=users,dc=herzbube,dc=ch" read
  by dn.exact="cn=libnss-ldap,ou=users,dc=herzbube,dc=ch" read
  by dn.exact="cn=libnss-ldap-root,ou=users,dc=herzbube,dc=ch" read
  by dn.exact="cn=samba-service,ou=users,dc=herzbube,dc=ch" read
  by * none
access to dn.children="ou=groups,dc=herzbube,dc=ch"
  by dn.exact="cn=readonly-users,ou=users,dc=herzbube,dc=ch" read
  by dn.exact="cn=libnss-ldap,ou=users,dc=herzbube,dc=ch" read
  by dn.exact="cn=libnss-ldap-root,ou=users,dc=herzbube,dc=ch" read
  by dn.exact="cn=samba-service,ou=users,dc=herzbube,dc=ch" read
  by * none

# Access to host entries
# - read-only access by the two libnss-ldap users because this is the
#   purpose of these entries
# - no access to everybode else
access to dn.exact="ou=hosts,dc=herzbube,dc=ch"
  by dn.exact="cn=libnss-ldap,ou=users,dc=herzbube,dc=ch" read
  by dn.exact="cn=libnss-ldap-root,ou=users,dc=herzbube,dc=ch" read
  by * none
access to dn.children="ou=hosts,dc=herzbube,dc=ch"
  by dn.exact="cn=libnss-ldap,ou=users,dc=herzbube,dc=ch" read
  by dn.exact="cn=libnss-ldap-root,ou=users,dc=herzbube,dc=ch" read
  by * none

# Access to samba entries
# - read-only access by samba-service because this is the purpose of
#   this entry
# - no access to everybode else
access to dn.exact="ou=samba,dc=herzbube,dc=ch"
  by dn.exact="cn=samba-service,ou=users,dc=herzbube,dc=ch" read
  by * none
access to dn.children="ou=samba,dc=herzbube,dc=ch"
  by dn.exact="cn=samba-service,ou=users,dc=herzbube,dc=ch" read
  by * none

# Access to DHCP entries
# - read-only access by dhcp-service because this is the purpose of
#   this entry
# - no access to everybode else
access to dn.exact="cn=dhcp,dc=herzbube,dc=ch"
  by dn.exact="cn=dhcp-service,ou=users,dc=herzbube,dc=ch" read
  by * none
access to dn.children="cn=dhcp,dc=herzbube,dc=ch"
  by dn.exact="cn=dhcp-service,ou=users,dc=herzbube,dc=ch" read
  by * none

# Access to addressbook entries
# - read-only access by readonly-addressbook because this is the purpose
#   of this entry
# - read+write access by selected users
# - no access to everybode else
access to dn.exact="ou=addressbook,dc=herzbube,dc=ch"
  by dn.exact="cn=readonly-addressbook,ou=users,dc=herzbube,dc=ch" read
  by dn.exact="cn=patrick,ou=users,dc=herzbube,dc=ch" write
  by dn.exact="cn=francesca,ou=users,dc=herzbube,dc=ch" write
  by * none
access to dn.children="ou=addressbook,dc=herzbube,dc=ch"
  by dn.exact="cn=readonly-addressbook,ou=users,dc=herzbube,dc=ch" read
  by dn.exact="cn=patrick,ou=users,dc=herzbube,dc=ch" write
  by dn.exact="cn=francesca,ou=users,dc=herzbube,dc=ch" write
  by * none

# Access to bookmark entries
# - read-only access by readonly-bookmarks because this is the purpose
#   of this entry
# - read+write access by selected users
# - no access to everybode else
access to dn.exact="ou=bookmarks,dc=herzbube,dc=ch"
  by dn.exact="cn=readonly-bookmarks,ou=users,dc=herzbube,dc=ch" read
  by dn.exact="cn=patrick,ou=users,dc=herzbube,dc=ch" write
  by dn.exact="cn=francesca,ou=users,dc=herzbube,dc=ch" write
  by * none
access to dn.children="ou=bookmarks,dc=herzbube,dc=ch"
  by dn.exact="cn=readonly-bookmarks,ou=users,dc=herzbube,dc=ch" read
  by dn.exact="cn=patrick,ou=users,dc=herzbube,dc=ch" write
  by dn.exact="cn=francesca,ou=users,dc=herzbube,dc=ch" write
  by * none

# Access to entries representing DHCP servers
# - read-only access by the dhcp-service dn
# - no access to everybode else
access to dn.onelevel="dc=herzbube,dc=ch"
          filter="(objectClass=dhcpServer)"
  by dn.exact="cn=dhcp-service,ou=users,dc=herzbube,dc=ch" read
  by * none


Administration

Daemon

Starting/stopping the daemon

/etc/init.d/slapd start|stop

Testing whether the configuration looks OK:

slaptest -v


Generate passwords

To generate an userPassword value suitable for use with ldapmodify or the rootpw configuration directive in slapd.conf:

slappasswd [-h {SHA|SSHA|MD5|SMD5|CRYPT|CLEARTEXT}]

Note 1: The braces are necessary, and they need to be protected from interpretation from the shell.

Note 2: The generated password hash cannot be used for /etc/shadow


Command line usage

Search entries

ldapsearch -ZZ -h ldap.herzbube.ch -D cn=patrick,ou=users,dc=herzbube,dc=ch -W -x -b "dc=herzbube,dc=ch" "(objectclass=*)"

Discussion:

  • -ZZ = use TLS
  • -h = LDAP host name to connect to; if -ZZ is being used, the name specified here must be the FQDN (e.g. ldap.herzbube.ch)
  • -D = DN of the user that should be used for authentication
  • -W = ask for password (instead of specifying it on the command line)
  • -x = simple authentication
  • -b = base DN for the search
  • cn = space separated list of attributes that should be read


Delete entries

ldapdelete -r -D cn=admin,ou=users,dc=herzbube,dc=ch -W -x -ZZ -f list-of-dns.ldif -h ldap.herzbube.ch

Discussion:

  • -r = delete recursive
  • entries in the file must be DNs, but without the the "dn:" prefix usually required by other tools (e.g. ldapadd, ldapmodify); example
cn=last first,ou=addressbook,dc=herzbube,dc=ch


Add entries

ldapmodify -a -D cn=admin,ou=users,dc=herzbube,dc=ch -W -x -ZZ -f changes.ldif -h ldap.herzbube.ch
ldapadd       -D cn=admin,ou=users,dc=herzbube,dc=ch -W -x -ZZ -f changes.ldif -h ldap.herzbube.ch

Discussion:

  • -a = add new entries; this flag is turned on automatically if ldapadd is used (which in reality is a hardlink to ldapmodify/tt>)
  • <tt>-n = show what would be done, but don't actually modify entries; useful for debugging in conjunction with -v


It is possible to specify "changetype: add" within the LDIF file instead of using the command line (i.e. -a option) to say that we want to add entries. Example LDIF file:

osgiliath:~# cat hosts.ldif 
dn: cn=osgiliath,ou=hosts,dc=herzbube,dc=ch
changetype: add
cn: osgiliath
cn: localhost
ipHostNumber: 127.0.0.1
objectClass: device
objectClass: ipHost

Corresponding usage of ldapmodify:

osgiliath:~# ldapmodify -D cn=admin,ou=users,dc=herzbube,dc=ch -W -x -f hosts.ldif -h localhost


Change entries

ldapmodify -D cn=admin,ou=users,dc=herzbube,dc=ch -W -x -ZZ -f changes.ldif -h ldap.herzbube.ch

Discussion:

  • none


The syntax of the LDIF file is too complicated to discuss here; my reference is in the O'Reilly book "LDAP System Administration". An example copied verbatim from the book:

## Add entry for Peabody Soup
dn: cn=Peadbody Soup,ou=people,dc=plainjoe,dc=org
changetype: add
cn: Peabody Soup
sn: Soup
objectClass: inetOrgPerson

## Add new telephoneNumber for Jerry Carter
dn: cn=Jerry Carter,ou=people,dc=plainjoe,dc=org
changetype: modify
delete: telephoneNumber
telephoneNumber: 555-123-1234
-
add: telephoneNumber
telephoneNumber: 234-555-6789

## Remove entry Peabody Soup
dn: cn=Peadbody Soup,ou=people,dc=plainjoe,dc=org
changetype: delete

## Rename entry
dn: cn=Jerry Carter,ou=people,dc=plainjoe,dc=org
changetype: modrdn
newrdn: cn=Gerry Carter
deleteoldrdn: 1


Note 1: Only leaf nodes should be renamed with changetype modrdn. If a node has child nodes, using modrdn just like that will orphan the child nodes because the DN of their parent has changed.

Note 2: There is another changetype named moddn. There is currently no example for this.


Test access to entries

slapacl -D "cn=dhcp-service,ou=users,dc=herzbube,dc=ch" -b "cn=osgiliath.localnet.herzbube.ch,dc=herzbube,dc=ch" "dhcpServiceDN"
slapacl -D "cn=dhcp-service,ou=users,dc=herzbube,dc=ch" -b "cn=osgiliath.localnet.herzbube.ch,dc=herzbube,dc=ch" "dhcpServiceDN/write"

Discussion:

-D 
Specifies the DN that should be assumed for authenticating
-b 
Specifies the DN of the entry that should be tested
dhcpServiceDN 
Specifies the attribute that should be tested
dhcpServiceDN/write 
Specifies whether write access is possible


Importing data

Address Book.app -> vcard -> LDIF -> LDAP directory

Export from the Mac OS X "Address Book" application:

  • select all entries (or select the group "all")
  • select menu entry File->Export vCards

The resulting .vcf file

  • possibly contains data where every (!) character is delimited by a null byte
    • to fix this, open the .vcf file in vi, search for the null byte

(Ctrl+V 0 <space>) and replace it by an empty string

  • has the CR+LF line ending (instead of just LF)
    • to fix this, open the .vcf file in vi, search for the CR byte

(Ctrl+V <enter key>) and replace it by an empty string

  • in theory has the vCard encoding that is configured in Preferences.app
    • in practice, the encoding is always LATIN1
    • the encoding can be changed by saying set fileencoding=utf-8;
  when the file is saved the next time, it will be written to disk
  using the encoding 

Now that the .vcf file has been prepared, you can process it, for instance, with an awk script. The output of this step should be an LDIF file.

The import of the LDIF file into the directory is done by the following command:

ldapadd -D cn=admin,ou=users,dc=herzbube,dc=ch -W -x -ZZ -f alle.ldif -h ldap.herzbube.ch


Exporting data

LDAP directory -> LDIF -> CSV

Export from the LDAP directory:

ldapsearch -h localhost -D cn=patrick,ou=users,dc=herzbube,dc=ch -W -x -b "ou=addressbook,dc=herzbube,dc=ch" "(objectclass=*)" >abook.ldif

An alternative is to use slapcat; its main advantage is that it is faster than ldapsearch, but on the other hands the drawbacks are that for some database backend types the slapd daemon needs to be stopped (see man slapcat for details), and that the output contains many fields that are not actually user data.

The resulting file abook.ldif can now be processed by any means (e.g. an awk or a perl script) and converted into CSV format.

There is one important thing to note: the LDIF format encodes non-ASCII values as base64. This affects, for instance, the common German umlaut characters 'ä', 'ö' and 'ü'. Fields that use base64 to encode their values can be identified by a double-colon. For instance:

cn:: TsOkZi1CcmF1biBMeWRpYQ==

To conveniently decode base64 values in perl, use the MIME::Base64 module:

use MIME::Base64;
$encoded = encode_base64('Aladdin:open sesame');
$decoded = decode_base64($encoded);


Clients

The clients in the following table use the directory as a data source:

Client Data Purpose Access type DN used for binding Links Remarks
PAM ou=users,dc=herzbube,dc=ch find user DN read-only cn=readonly-users,ou=users,dc=herzbube,dc=ch PAM
authenticate and read attributes user DN
password change read+write user DN
NSS ou=users,dc=herzbube,dc=ch
ou=groups,dc=herzbube,dc=ch
ou=hosts,dc=herzbube,dc=ch
read attributes read-only cn=libnss-ldap,ou=users,dc=herzbube,dc=ch NSS
cn=libnss-ldap-root,ou=users,dc=herzbube,dc=ch
Bugzilla ou=users,dc=herzbube,dc=ch  find user DN read-only cn=readonly-users,ou=users,dc=herzbube,dc=ch Bugzilla
authenticate and read attributes user DN
SquirrelMail ou=addressbook,dc=herzbube,dc=ch  read attributes read-only cn=readonly-addressbook,ou=users,dc=herzbube,dc=ch SquirrelMail
Samba ou=users,dc=herzbube,dc=ch read attributes read-only cn=samba-service,ou=users,dc=herzbube,dc=ch Samba
ou=users,dc=herzbube,dc=ch password change read+write
ou=groups,dc=herzbube,dc=ch read attributes read-only
ou=samba,dc=herzbube,dc=ch
Apache ou=users,dc=herzbube,dc=ch find user DN read-only cn=readonly-users,ou=users,dc=herzbube,dc=ch Apache
authenticate user DN
ou=groups,dc=herzbube,dc=ch check group membership cn=readonly-users,ou=users,dc=herzbube,dc=ch
DHCP Server entries directly below dc=herzbube,dc=ch
with (objectClass=dhcpServer)
find entry that matches the server's hostname,
then follow the entry's pointer to the subtree
that contains the server's DHCP configuration
read-only cn=dhcp-service,ou=users,dc=herzbube,dc=ch ISCDHCP
cn=dhcp,dc=herzbube,dc=ch read server's DHCP configuration and look up hosts
DAViCal ou=users,dc=herzbube,dc=ch find user DN read-only cn=readonly-users,ou=users,dc=herzbube,dc=ch DAViCal
authenticate and read attributes user DN
Mediawiki ou=users,dc=herzbube,dc=ch find user DN read-only cn=readonly-users,ou=users,dc=herzbube,dc=ch Mediawiki
authenticate user DN
ou=groups,dc=herzbube,dc=ch check group membership cn=readonly-users,ou=users,dc=herzbube,dc=ch
phpLDAPadmin ou=users,dc=herzbube,dc=ch find user DN read-only cn=readonly-users,ou=users,dc=herzbube,dc=ch phpLDAPadmin
authenticate user DN
Everything, everywhere Browse and modify the entire directory read+write user DN


Applications

phpLDAPadmin

There is quite a bit of software out there that allows you to nicely administrate your address book contacts in an LDAP directory. Unfortunately, 99% of all those tools are geared towards working with the InetOrgPerson object class, which is of no use to me since I have defined my own schema.

phpLDAPadmin belongs to the remaining 1%. This application is the best and most flexible tool I encountered that is also able to administrate my address book entries without too much trouble. It also has support for templates - if one day I manage to create such a template for my addressbookEntry schema, nothing will stand in the way of adding comfort to capability.


Installation

In the beginning I had my own installation in /var/www/phpldapadmin, but at some point, Debian started to have a regular phpldapadmin package, so this is what I use nowadays.

The main configuration file is stored in

/etc/phpldapadmin/config.php

Most of the following sections discuss modifications to that file.


Apache configuration

phpLDAPadmin brings its own Apache configuration file that defines everything necessary to run phpLDAPadmin either under an alias path /phpldapadmin, or under its own virtual host.

I have assigned an Apache vhost to phpLDAPadmin that is accessible under http://ldap.herzbube.ch/. These are the configuration details:

# --------------------------------------------------------------------------------
# ldap.herzbube.ch
# --------------------------------------------------------------------------------
<VirtualHost *:80>
  ServerName ldap.herzbube.ch
  ServerAdmin webmaster@herzbube.ch
  ErrorLog /var/log/apache2/herzbube.ch/error.log
  CustomLog /var/log/apache2/herzbube.ch/access.log combined
  
  DocumentRoot /usr/share/phpldapadmin/htdocs
  Alias /robots.txt /var/www/herzbube.ch/ldap.herzbube.ch/robots.txt

  <Directory /usr/share/phpldapadmin/>
    <IfModule mod_php5.c>
      php_admin_flag engine on
#      php_value error_reporting "E_ALL & ~E_DEPRECATED"
    </IfModule>
  </Directory>
  <Directory /var/www/ldap.herzbube.ch/>
    Allow from all
  </Directory>
</VirtualHost>

# --------------------------------------------------------------------------------
# SSL Host
# --------------------------------------------------------------------------------
<IfModule mod_ssl.c>
  <VirtualHost *:443>
    ServerName ldap.herzbube.ch
    ServerAdmin webmaster@herzbube.ch
    ErrorLog /var/log/apache2/herzbube.ch/error.log
    CustomLog /var/log/apache2/herzbube.ch/access.log combined

    DocumentRoot /usr/share/phpldapadmin/htdocs
    Alias /robots.txt /var/www/herzbube.ch/ldap.herzbube.ch/robots.txt

    <Directory /usr/share/phpldapadmin/>
      <IfModule mod_php5.c>
        php_admin_flag engine on
      </IfModule>
    </Directory>
    <Directory /var/www/ldap.herzbube.ch/>
      Allow from all
    </Directory>

    SSLEngine on
    SSLCertificateFile    /etc/ssl/certs/herzbube.ch.crt
    SSLCertificateKeyFile /etc/ssl/private/herzbube.ch.key.unsecure
    SSLCertificateChainFile /etc/ssl/certs/cacert.org.certchain
    SetEnvIf User-Agent ".*MSIE.*" nokeepalive ssl-unclean-shutdown
  </VirtualHost>
</IfModule>


Connecting to the directory

Documentation for server definitions can be found here. My configuration looks like this:

$servers->newServer('ldap_pla');
$servers->setValue('server','name','Lokal, pelargir');
$servers->setValue('server','host','127.0.0.1');
$servers->setValue('server','port',389);
$servers->setValue('server','base',array('dc=herzbube,dc=ch'));
$servers->setValue('login','auth_type','session');
$servers->setValue('login','bind_id','cn=readonly-users,ou=users,dc=herzbube,dc=ch');
$servers->setValue('login','bind_pass','secret');
$servers->setValue('server','tls',false);
$servers->setValue('login','attr','cn');
$servers->setValue('login','base',array('ou=users,dc=herzbube,dc=ch'));

Notes:

  • We want to enter a simple user name for login, not a full DN
  • This means that we must tell phpLDAPadmin the rules how it must translate the simple user name into a full DN
  • The first step is to set "login, auth_type" to "session": This tells phpLDAPadmin that authentication information must be entered by the user (instead of taking a hardcoded login DN directly from config.php). At the same time, this tells phpLDAPadmin to store the authentication information in a PHP session variable on the server (not in a browser cookie in the client!)
    • phpLDAPadmin will encrypt the session variable, currently using the Blowfish algorithm
    • In earlier configurations I used to define a simple Blowfish passphrase in config.php, and I was careless enough to write the passphrase into this wiki
    • I no longer do this. When leaving the Blowfish passphrase blank, phpLDAPadmin will use the session ID as the passphrase, which is good enough for me.
  • The next step is to set "login, attr" to the attribute that stores the user name. In my case this is the attribute "cn".
  • Finally we have to set "login, bind_id" and "login, bind_pass" in order to tell phpLDAPadmin how it should bind to the directory to lookup the login DN
  • I also think it makes sense to set "login, base" to restrict the search for login DNs to a given subtree. In my case this is really important because I use the attribute "cn", which is used everywhere in the directory
    • An alternative for restricting the search is to set "login, class"
  • Important final note: The file contains a password, therefore it must be protected so that its permissions look like this
 root@pelargir:~# ls -l /etc/phpldapadmin/config.php 
 -rw-r----- 1 root www-data 24363 Jun 12 17:12 /etc/phpldapadmin/config.php


Template related settings

phpLDAPadmin provides a number of templates that are meant to be examples that can be used to create custom templates. The presence of these example templates has a couple of annoying consequences.


When the first entry is selected after a login, there are a number of warning messages ("Automatically removed objectClass from template", "Automatically removed attribute from template"). These warnings can be disabled with this setting:

$config->custom->appearance['hide_template_warning'] = true;


The other problem is that, whenever an entry is selected, phpLDAPadmin prompts the user to select the template that should be used for editing. The user's choice is remembered, but only during the current session - when the user logs out, all selections are discarded. The only way how to fix this is to disable these example templates, by using the following setting:

$config->custom->appearance['custom_templates_only'] = true;


Note: The custom_templates_only setting also fixes the "warning messages" issue because the example templates are completely disabled.


Other settings

  • General settings:
$config->custom->appearance['timezone'] = 'Europe/Zurich';
  • Command settings: Allow all commands, i.e. remove the code comments /* */ around the variables that define the available commands.


Predefined Searches

Predefined searches appear to have been removed in PLA 1.2. The PLA wiki config page currently hints at some search-related settings (see bottom of page), but at the moment these are undocumented and I have not spent any time fiddling around with them.

If predefined (or custom) searches become available again in the future, the following searches would be useful:

  • List of users
  • List of groups
  • List of hosts
  • R7b list
  • Contacts marked by me in the description attribute


Templates

phpLDAPadmin supports two kinds of templates for editing LDAP entries: one type is for creating new entries, the other is for modifying existing entries. On a Debian system, templates are stored in

root@pelargir:/etc/phpldapadmin/templates# l
total 20
drwxr-x--- 4 root www-data 4096 Jun  9 23:04 .
drwxr-xr-x 3 root root     4096 Jun 12 17:12 ..
drwxr-x--- 2 root www-data 4096 Jun  9 23:04 creation
drwxr-x--- 2 root www-data 4096 Jun  9 23:04 modification
-rw-r----- 1 root www-data 2089 Oct 19  2010 template.dtd

The following resources are useful for understanding phpLDAPadmin templates:


The following steps are necessary to define a template for my custom objectClass "addressBookEntry":

  • TODO


Apache Directory Studio

http://directory.apache.org/studio/


Other

  • gq: an X11 client
  • ldapnavigator: a PHP application hosted on SourceForge that is quite useful but whose development seems to have stopped in 2002


Upgrades

Change backend from ldbm to bdb

Useful information can be found in

/usr/share/doc/slapd/README.Debian


First the daemon needs to be stopped

/etc/init.d/slapd stop

Then a backup of the database is in order

slapcat -n 1 >backup.ldif

Now change the configuration in slapd.conf

  • the backend module needs to be loaded
moduleload back_bdb
  • the database must be configured with the new backend
database bdb

Now either move the original database files in the file system to a safe place, or change the database location inside slapd.conf; in both cases it is important that the database directory in the file system exists and is empty (the next step will create its contents)

Now add the backup data to the new database (first make a test by specifying -u, then do the real thing)

slapadd -n 1 -u <backup.ldif
slapadd -n 1 <backup.ldif

Modify permissions (somewhere in between 2.2.26-4 and 2.3.27-1 the system user/group openldap has been introduced)

chown -R openldap:openldap /var/lib/ldap/herzbube.ch

Now restart the daemon

/etc/init.d/slapd start

Et voilà.


From slapd-2.2.26-4 to slapd-2.3.27-1

Somewhere in between the two versions named in the chapter title, the Debian package manager introduced a new system user/group named openldap. After I upgraded the package, the new slapd daemon ran as openldap, but the database files in /var/lib/ldap/herzbube.ch were still owned by root.

The following permission issues need to be fixed to make the upgrade complete:

chown -R openldap:openldap /var/lib/ldap
chown -R openldap:openldap /var/lib/slapd
chown -R openldap:openldap /var/run/slapd
chgrp openldap /etc/ssl/private/ldap.herzbube.ch.key*
chmod 440 /etc/ssl/private/ldap.herzbube.ch.key*
adduser openldap ssl-cert

(the last command is required so that the openldap user has the permission to get into the directory /etc/ssl/private)


As an afterthought, I decided to use the opportunity to clean-up my OpenLDAP installation a bit:

  • make sure that I have a backup of my LDAP directory and of everything in /etc/ldap
  • purge the slapd package
  • remove (rm -r) the directories /etc/ldap and /var/lib/ldap/herzbube.ch
  • re-install the slapd package
  • stop the daemon
  • remove (rm -r) the directory /var/lib/ldap that was created during package installation
  • update the pristine slapd.conf with the modifications necessary to run my directory
  • create the directory /var/lib/ldap/herzbube.ch
  • use slapadd to restore the last backup of the herzbube.ch LDAP directory
  • re-create indices with slapindex -n 1
  • chown -R openldap:openldap /var/lib/ldap/herzbube.ch
  • restart server


From slapd-2.3.27-1 to slapd-2.4.10-3

The following messag box displayed by dpkg seems to indicate that the new OpenLDAP package is now built with GnuTLS instead of OpenSSL:

A "TLSCipherSuite" option was found in your slapd config when upgrading. The values allowed for this option are determined by the SSL implementation used, which has been
changed from OpenSSL to GnuTLS.  As a result, your existing TLSCipherSuite setting will not work with this package.

This setting has been automatically commented out for you.  If you have specific encryption needs that require this option to be re-enabled, see the output of
'gnutls-cli -l' in the gnutls-bin package for the list of ciphers supported by GnuTLS.

In order to get the gnutls-cli utility, I first install the package gnutls-bin. I then have a look at the OpenSSL ciphers that the original value for TLSCipherSuite (HIGH:MEDIUM) selected:

osgiliath:~# openssl ciphers -v HIGH:MEDIUM
ADH-AES256-SHA          SSLv3 Kx=DH       Au=None Enc=AES(256)  Mac=SHA1
DHE-RSA-AES256-SHA      SSLv3 Kx=DH       Au=RSA  Enc=AES(256)  Mac=SHA1
DHE-DSS-AES256-SHA      SSLv3 Kx=DH       Au=DSS  Enc=AES(256)  Mac=SHA1
AES256-SHA              SSLv3 Kx=RSA      Au=RSA  Enc=AES(256)  Mac=SHA1
ADH-AES128-SHA          SSLv3 Kx=DH       Au=None Enc=AES(128)  Mac=SHA1
DHE-RSA-AES128-SHA      SSLv3 Kx=DH       Au=RSA  Enc=AES(128)  Mac=SHA1
DHE-DSS-AES128-SHA      SSLv3 Kx=DH       Au=DSS  Enc=AES(128)  Mac=SHA1
AES128-SHA              SSLv3 Kx=RSA      Au=RSA  Enc=AES(128)  Mac=SHA1
ADH-DES-CBC3-SHA        SSLv3 Kx=DH       Au=None Enc=3DES(168) Mac=SHA1
EDH-RSA-DES-CBC3-SHA    SSLv3 Kx=DH       Au=RSA  Enc=3DES(168) Mac=SHA1
EDH-DSS-DES-CBC3-SHA    SSLv3 Kx=DH       Au=DSS  Enc=3DES(168) Mac=SHA1
DES-CBC3-SHA            SSLv3 Kx=RSA      Au=RSA  Enc=3DES(168) Mac=SHA1
DES-CBC3-MD5            SSLv2 Kx=RSA      Au=RSA  Enc=3DES(168) Mac=MD5 
ADH-RC4-MD5             SSLv3 Kx=DH       Au=None Enc=RC4(128)  Mac=MD5 
RC4-SHA                 SSLv3 Kx=RSA      Au=RSA  Enc=RC4(128)  Mac=SHA1
RC4-MD5                 SSLv3 Kx=RSA      Au=RSA  Enc=RC4(128)  Mac=MD5 
RC2-CBC-MD5             SSLv2 Kx=RSA      Au=RSA  Enc=RC2(128)  Mac=MD5 
RC4-MD5                 SSLv2 Kx=RSA      Au=RSA  Enc=RC4(128)  Mac=MD5 

Checking against the ciphers listed by gnutls-cli -l, which do not match the OpenSSL ciphers at all, I am somewhat overwhelmed. The following Debian bug documents how the current upgrade behaviour (TLSCipherSuite getting commented out) came into being, what the original problems were, and generally provides nice information about TLS (in the context of OpenLDAP):

http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=462588

The bug report convinces me that I don't need a specific TLSCipherSuite setting because 1) I have no real clue about ciphers so I don't know what to choose, and 2) the bug report says (and I believe the statement) that "The most common use of this directive is to restrict use of weak ciphers, which GnuTLS doesn't support in the first place."

I make a final check whether I can make an LDAP request using TLS, and it works, so the issue is closed for me. Details how I made the check:

  • using ldapsearch -ZZ (see further up in this document for a detailed example)
  • the connection must be made via ldap.herzbube.ch because that is the DN of the server certificate
  • on osgiliath it is currently not possible to connect to ldap.herzbube.ch because the current DNAT rule seems to be insufficient to properly re-reoute packets to herzbube.ch if they come from osgiliath
  • I make the attempt from tharbad; here I first get the following error
ldap_start_tls: Connect error (-11)
	additional info: error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed
  • I then transmit the CA certificate chain file cacert.org.certchain to tharbad and edit the LDAP client configuration file /etc/openldap/ldap.conf so that it contains the option TLS_CACERT pointing to the CA certificate chain file
  • it works!


From slapd-2.4.10-3 to slapd-2.4.17-1

The upgrade procedure seems to work like this:

  1. Back up directory
  2. Install new stuff
  3. Create new directory
  4. Import from backup

This time, step 4 didn't work. The output looked like this:

  Loading from /var/backups/slapd-2.4.10-3: 
  - directory dc=herzbube,dc=ch... failed.

Loading the database from the LDIF dump failed with the following
error while running slapadd:
    slap_sasl_init: SASL library version mismatch: expected 2.1.23, got 2.1.22
    slapadd: slap_init failed!

Luckily, the fix this time was easy: Simply upgrade libsasl2-2 to 2.1.23. The broken slapd upgrade was retried automatically after libsasl2-2 had been upgraded, and this time everything worked. Phew!


From slapd-2.4.17-1 to 2.4.25-1+b1

This upgrade is not documented. It probably occurred while switching from osgiliath to pelargir.

Note: The upgrade to slapd-2.4.25-1+b1 automatically converted the single-file configuration into a directory-based configuration.


From slapd-2.4.25-1+b1 to slapd-2.4.25-3

The first attempt at upgrading completely broke my installation! Apparently a set of SASL packages with undocumented ABI changes had made it into testing, and I had unwittingly upgraded those. The effect was that it was no longer possible to run slapd or any of the LDAP command line tools (e.g. slapcat). The error printed when trying to run one of those programs:

slap_sasl_init: auxprop add plugin failed
slapadd: slap_init failed!

Downgrading SASL packages to a reportedly good version (2.1.23.dfsg1-7) did not help, nor was it possible to remove them completely because OpenLDAP depends on SASL being present on the system.

Debian bug 628237 had more information on the problem. The only solution I finally found was to manually download and install the latest OpenLDAP packages (version 2.4.25-4) from Debian unstable (!). Interestingly, slapd could not even be properly uninstalled by the regular package management system, so I had to manually uninstall it as follows before I was able to install the new version:

rm /var/lib/dpkg/info/slapd.*
dpkg --remove --force-remove-reinstreq slapd
Personal tools
francesca