OpenLDAP
From HerzbubeWiki
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
- http://www.openldap.org/doc/admin/slapdconf2.html
-
man slapd-config
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:
- http://phpldapadmin.sourceforge.net/wiki/index.php/Templates
- http://phpldapadmin.sourceforge.net/wiki/index.php/FAQ#Template_FAQs
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:
- Back up directory
- Install new stuff
- Create new directory
- 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
