Table of Contents

The OpenLDAP Chapter

Identity management IS a big issue. And definitely something you want to take care of in a growing, multi-tiers, infrastructure. So the goal of this chapter is to describe the implementation of a reliable LDAP infrastructure using Debian and OpenLDAP.

We will discuss the following:

That's quite a lot of work, so let's get started ! My main source of information is Mastering OpenLDAP, from Matt Butcher, published at PACKT Publishing, and the official OpenLDAP documentation.

OpenLDAP on Debian

OpenLDAP is a set of tools to use and manage LDAP annuaries. Among them lives Slapd, the OpenLDAP Server. This one can be easily installed using the debian repositories.

ramiel:/# aptitude install slapd ldap-utils

ldap-utils is an additional package that contain most of the LDAP tools we will use with the directory, such as ldapsearch or ldapadd.

The installation process then asks for the password of the administrator:

We have a directory: /etc/ldap that contains the few following files:

ses0gnoc:/etc/ldap# ls -l
total 28
-rw-r--r-- 1 root root      245 nov.  26 11:12 ldap.conf
drwxr-xr-x 2 root root     4096 nov.  10 20:24 sasl2
drwxr-xr-x 2 root root     4096 févr.  7 11:17 schema
-rw-r----- 1 root openldap 4760 févr.  7 11:38 slapd.conf
-rw-r----- 1 root root     4786 févr.  7 10:15 slapd.conf.orig

Let's backup the original slapd.conf file:

ses0gnoc:/etc/ldap# cp slapd.conf slapd.conf.orig  

Listen on localhost

By default, slapd accepts requests from everywhere. We can change this in /etc/default/slapd. Simply edit the line SLAPD_SERVICES.

# vim /etc/default/slapd

[ ... ]

# listen on localhost only
SLAPD_SERVICES="ldap://127.0.0.1:389/ ldapi:///"

The slapd.conf configuration file

This is the main configuration file for slapd. We will spend some time here, so understanding its syntax is important.

The file is structures as follow:

 ~~~~~~~~~~~~~~
{    GLOBAL    }
 ~~~~~~~~~~~~~~
      This section contains the global configuration parameters, such as the modules 
to load, or the daemon information (pid file and so on...).
In this section, we will also declares the **schemas** usuable by the LDAP directory.

 ~~~~~~~~~~~~~~
{   DATABASES  }
 ~~~~~~~~~~~~~~
      This section will contain the trees of the LDAP Directory. Each tree starts with 
a declaration of the **database** keyword (that also contains the database type, such 
as 'hdb') and it has a root defined by the value of **suffix**.
The declaration of a new tree starts with the declaration of a new **database** value.

 ~~~~~~~~~~~~~~
{     ACLs     }
 ~~~~~~~~~~~~~~
      Access Control Lists define the rules to access the information inside the LDAP 
Directory. 

Now, let's dig into slapd.conf section by section.

Global

Schemas

The first part include the global parameters, and in particular the schemas used by OpenLDAP. A schema contain the definition of a set of objects and attributes. For example, the core.schema file contain the definition of the object person as follow:

407 objectclass ( 2.5.6.6 NAME 'person'
408         DESC 'RFC2256: a person'
409         SUP top STRUCTURAL
410         MUST ( sn $ cn )
411         MAY ( userPassword $ telephoneNumber $ seeAlso $ description ) )

This definition clearly state that this object class person represents a person as defined in RFC 2256. This object must contain one sn (surname, or last name) and one cn (full name) fields, and may contain several other optional fields. The $ sign is a separator sign (like a comma or a hyphen).

<note important>You will probably notice that, during your discovery of the LDAP world, many signs and operators have a very different meaning than usually. More of this later when discussing search queries.</note>

Therefore, when the LDAP administrator will create a new person, he will have to respect this definition. Otherwise, the LDAP directory will reject the entry.

In addition to the object classes, the schema also contains definitions of attributes, like the mail attribute that defines a case un-sensitive string representing a RFC 822 email address.

558 attributetype ( 0.9.2342.19200300.100.1.3
559         NAME ( 'mail' 'rfc822Mailbox' )
560         DESC 'RFC1274: RFC822 Mailbox'
561     EQUALITY caseIgnoreIA5Match
562     SUBSTR caseIgnoreIA5SubstringsMatch
563     SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )

Other schemas, like inetorgperson, can contain more exotic information, such as:

attributetype ( 0.9.2342.19200300.100.1.60
        NAME 'jpegPhoto'
        DESC 'RFC2798: a JPEG image'
        SYNTAX 1.3.6.1.4.1.1466.115.121.1.28 )

… a jpegphoto (could be anything, but let's say it is your ID photo).

By default, slapd on debian includes four schemas:

  1. core: the main schema
  2. cosine: a schema definition standardized in 1991 (!) as a pilot for X500 directories
  3. nis: a schema for unix users, useful to define a login shell or a home directory
  4. inetorgperson: a schema for “internet” users

Except for 'nis', the three other are linked and reuse objects from each other. So we will keep those three, and comment 'nis' out.

# Schema and objectClass definitions
include         /etc/ldap/schema/core.schema
include         /etc/ldap/schema/cosine.schema
#include         /etc/ldap/schema/nis.schema
include         /etc/ldap/schema/inetorgperson.schema

<note warning>By removing the nis schema, we remove the description of some attributes, like 'shadowLastChange'. We thus need to remove any reference to this attribute from the configuration, eg. from the 'ACL' rule that references it at line 109 of slapd.conf.</note>

Logs

Slapd logs using syslog. It sends its logs to the LOG_LOCAL4 facility. So we need to modify syslogd configuration to send LOCAL4 to an actual file, and adapt the loglevel of slapd to get more information.

<note tip>Debian Squeeze ships rsyslog instead of the old syslogd by default.</note>

Syslog can be configured in /etc/rsyslog.conf. Just add the following lines at the end.

# for slapd
local4.*        /var/log/slapd.log

And now, in slapd.conf, set loglevel to stats (the loglevel are listed in man slapd.conf).

# Read slapd.conf(5) for possible values
loglevel        stats

For additionnal diagnostic, I like to set loglevel to the following items: “loglevel stats sync ACL config filter”.

The rest of the file is self commented. We do not change these values…yet.

# Where the pid file is put. The init.d script
# will not stop the server if you change this.
pidfile         /var/run/slapd/slapd.pid

# List of arguments that were passed to the server
argsfile        /var/run/slapd/slapd.args

# Where the dynamically loaded modules are stored
modulepath      /usr/lib/ldap
moduleload      back_hdb

# The maximum number of entries that is returned for a search operation
sizelimit 500

# The tool-threads parameter sets the actual amount of cpu's that is used
# for indexing.
tool-threads 1

Database

Slapd support a large number of database backends. I will not cover all of them, because, for the most part, people stick to HDB. HDB is an enhanced version of Berkeley DataBase.

<note tip>Berkeley DB (BDB) is a computer software library that provides a high-performance embedded database, with bindings in C, C++, Java, Perl, Python, Ruby, Tcl, Smalltalk, and other programming languages. BDB stores arbitrary key/data pairs as byte arrays, and supports multiple data items for a single key. BDB can support thousands of simultaneous threads of control or concurrent processes manipulating databases as large as 256 terabytes, on a wide variety of operating systems including most Unix-like and Windows systems, and real-time operating systems. source: wikipedia</note>

HDB is only an optimization of BDB to solve the special needs of hierarchical trees that are LDAP directories.

Back to slapd.conf, let's take a look at the declaration of the database. Debian automatically creates a database for the domain of which the system belongs. So, in our case, it would be linuxwall.info. Therefore, the default database section looks like this:

#######################################################################
# Specific Directives for database #1, of type hdb:
# Database specific directives apply to this database until another
# 'database' directive occurs
database        hdb

# The base of your directory in database #1
suffix          "dc=linuxwall,dc=info"

# Where the database file are physically stored for database #1
directory       "/var/lib/ldap"

# For the Debian package we use 2MB as default but be sure to update this
# value if you have plenty of RAM
dbconfig set_cachesize 0 2097152 0

# Number of objects that can be locked at the same time.
dbconfig set_lk_max_objects 1500
# Number of locks (both requested and granted)
dbconfig set_lk_max_locks 1500
# Number of lockers
dbconfig set_lk_max_lockers 1500

# Indexing options for database #1
index           objectClass eq
 
# Save the time that the entry gets modified, for database #1
lastmod         on
 
# Checkpoint the BerkeleyDB database periodically in case of system
# failure and to speed slapd shutdown.
checkpoint      512 30

[ .. truncated file, we will cover the ACLs later ...]
database

This directive as two functions:

  1. I defines the type of backend to use for this directory. In our case, that would be HDB.
  2. I starts the definition of the directory. All of the directives that follow this one will be applied to the current database, until another database directive starts the definition of another directory.
suffix

This is the root of our database. Nothing can be created before this root, and all of the objects of the directory will have a distinguished name ending with this suffix. For example:

Distinguished Name: cn=Jean-Kevin Michel,ou=people,dc=linuxwall,dc=info
directory

This is the location of the data files. Slapd will store LDAP objects, views and internal data for this directory into files located at /var/lib/ldap/.

samchiel:/var/lib/ldap# ls -al
total 1116
drwxr-xr-x  2 openldap openldap     4096 2010-05-05 11:50 .
drwxr-xr-x 23 root     root         4096 2010-05-05 11:24 ..
-rw-r--r--  1 openldap openldap     2048 2010-05-05 11:50 alock
-rw-------  1 openldap openldap    24576 2010-05-05 11:50 __db.001
-rw-------  1 openldap openldap   253952 2010-05-05 12:20 __db.002
-rw-------  1 openldap openldap  2629632 2010-05-05 12:20 __db.003
-rw-------  1 openldap openldap    98304 2010-05-05 12:20 __db.004
-rw-------  1 openldap openldap   778240 2010-05-05 11:53 __db.005
-rw-------  1 openldap openldap    32768 2010-05-05 12:20 __db.006
-rw-r--r--  1 openldap openldap       96 2010-05-05 10:15 DB_CONFIG
-rw-------  1 openldap openldap     8192 2010-05-05 12:20 dn2id.bdb
-rw-------  1 openldap openldap    32768 2010-05-05 12:20 id2entry.bdb
-rw-------  1 openldap openldap 10485760 2010-05-05 12:20 log.0000000001
-rw-------  1 openldap openldap     8192 2010-05-05 12:20 objectClass.bdb
  1. the db files contain raw objects
  2. DB_CONFIG contains some tweaking parameters
  3. dn2id.bdb is a view containing a matching between distinguished names of objects and internal storage ID
  4. id2entry.bdb is a view containing the details of each object
  5. objectClass.bdb is the file for the index we defined in the configuration earlier
  6. log.0000000001 is the Berkeley DB transaction log file

<note important>OpenLDAP internal engines works as follow: When an operation is to be performed by the engine, slapd will treat it synchronously into the log journal file. Then, when it has some spare time, it will update the index and db file with the content of the journal. However, by default, reading will only be performed on committed values (ie. not in the journal).

Therefore, log isn't just some activity information, it's clearly the heart of the directory.</note>

If you want to delete a directory, you need to stop slapd and delete the db* , *.bdb and log* files.

samchiel:/var/lib/ldap# rm __db.00* *.bdb log.* 
dbconfig

This directive is used to tweak the database backend. The parameters are specific to each backend. In our case, the parameters have the following meaning:

  1. gigabytes of memory space allocated to the cache
  2. bytes of memory space allocated to the cache
  3. number of segments of cache

The first two arguments are summed up to determine the cache size. The third one should usually be 0 or 1, to have one segment only. The default value is very low on Debian: only 2MB. But since we have no data in the directory yet, it will be enough for now.

index

To speed up searches and lookup, you can define indexes on certain type of objects or attributes. An index will be stored into a separate datafile, such as objectclass.bdb as seen before, and be optimized for fast lookup.

index are very interesting for attributes that are used in searches. Thus, if you plan on using the uid attribute, or cn attribute, a lot, it's interesting to put an index on those.

The default index, on objectclass, is important because almost all searches will include something like objectclass=person or equivalent. Therefore, slapd will have to look for the corresponding objectclass everytime a search is performed, and you should want to speed this up.

As explained by Matt Butcher in his book (page 238), we can define two additional indexes as follow:

index           cn    eq,sub,pres,approx
index           uid   eq

The third column determine the type of index:

We then add those indexes to slapd.conf and manually launch the index command to create two new files in /var/lib/ldap:

<note warning>slapindex must be launched:

  1. while slapd is stopped
  2. under user openldap via sudo

</note>

samchiel:/var/lib/ldap# /etc/init.d/slapd stop
Stopping OpenLDAP: slapd.

samchiel:/var/lib/ldap# sudo -u openldap slapindex -q

samchiel:/var/lib/ldap# ls -l
total 468
-rw-r--r-- 1 openldap openldap     2048 2010-05-05 15:01 alock
-rw------- 1 openldap openldap     8192 2010-05-05 15:01 cn.bdb
-rw------- 1 openldap openldap    24576 2010-05-05 15:01 __db.001
-rw------- 1 openldap openldap   253952 2010-05-05 15:01 __db.002
-rw------- 1 openldap openldap  2629632 2010-05-05 15:01 __db.003
-rw-r--r-- 1 openldap openldap       96 2010-05-05 10:15 DB_CONFIG
-rw------- 1 openldap openldap     8192 2010-05-05 12:20 dn2id.bdb
-rw------- 1 openldap openldap    32768 2010-05-05 12:20 id2entry.bdb
-rw------- 1 openldap openldap 10485760 2010-05-05 14:59 log.0000000001
-rw------- 1 openldap openldap     8192 2010-05-05 12:20 objectClass.bdb
-rw------- 1 openldap openldap     8192 2010-05-05 15:01 uid.bdb

samchiel:/var/lib/ldap# /etc/init.d/slapd start
Starting OpenLDAP: slapd.

As you can see, we now have to new file: cn.bdb and uid.bdb.

lastmod

This function is an overlay. An overlay is some sort of module that slapd will call when necessary and that will perform some specification action before the action is operated by the directory. In the case of lastmod, the overlay simply adds 4 additional fields to an object:

dn: cn=Jean-Kevin Michel,ou=people,dc=linuxwall,dc=info
cn: Jean-Kevin Michel
[ ... ]
createtimestamp: 20100505095305Z
creatorsname: cn=admin,dc=linuxwall,dc=info

modifiersname: cn=admin,dc=linuxwall,dc=info
modifytimestamp: 20100505131013Z
checkpoint

As mentionned before, Slapd works asynchronously for the most part. It means that datas will not be committed to the static files right away, but might stay in memory for a little while (they are, however, committed to the log journal in a synchronous manner). checkpoint can be used to trigger a commit by looking at either the amount of data waiting for commit, or the time of the last commit. If either of these limits are reached, checkpoint will trigger a commit.

checkpoint <amount of data in kbytes> <time limit>

The default value (512kbytes max, or 30 minutes) is satisfying enough for our needs.

ACL

Access Control List are at the root of the security policy of the directory: it controls authorization. ACLs are used to define the acess to objects of the directory.

An ACL starts with the access directive. ACLs can be placed either in the database section, in which case they are specific to a database, or at the top of the configuration, in which case they apply globally, to all backends. The most specific ACL is always applied first (thus, the backends ACLs are preferred).

As defined in the Admin guide, an ACl has this form:

access to <what>
        [by <who> [<access>] [<control>] ]+

where the <what> part selects the entries and/or attributes to which the access applies, the <who> part specifies which entities are granted access, and the <access> part specifies the access granted. Multiple <who> <access> <control> triplets are supported, allowing many entities to be granted different access to the same set of entries and attributes.
access to attrs=userPassword
        by dn="cn=admin,dc=linuxwall,dc=info" write
        by anonymous auth
        by self write
        by * none

SASL

There are several ways to bind to a LDAP directory. One of them is using the SASL library, that provides secured method to transport the authentication from the client to the directory. <note tip>The SASL method is necessary to allow cyrus-imap to connect to the directory without passing through saslauthd and pam. See the Cyrus Imapd Chapter for more details</note>

Imagine that we want our users to be able to bind to the directory without using a TLS channel, but still without disclosing their passwords. SASL provides a solution to this, called DIGEST-MD5 (RFC 2831.

Authentication Algorithm

DIGEST-MD5 works as follow:

dc=linuxwall,dc=info....DIGEST-MD5
SASL(0):.successful.result:....nonce="TY/UgxW3BtNkYtWVIz/tOFD8knO1U+wXDESwooOJq9Y=",realm="samchiel.linuxwall.info",qop="auth,auth-int,auth-conf",cipher="rc4-40,rc4-56,rc4,des,3des",maxbuf=65536,charset=utf-8,algorithm=md5-sess

The format of the challenge sent by the server to the client follows the description of the RFC:

digest-challenge  =
         1#( realm | nonce | qop-options | stale | maxbuf | charset
               algorithm | cipher-opts | auth-param )
dc=linuxwall,dc=info...5..DIGEST-MD5...%username="julien",realm="samchiel.linuxwall.info",nonce="TY/UgxW3BtNkYtWVIz/tOFD8knO1U+wXDESwooOJq9Y=",cnonce="iI87tfR5Ac7xm58VwaKYZRGn28GnZlzA8Vr//1axUbE=",nc=00000001,qop=auth-conf,cipher=rc4,maxbuf=16777215,digest-uri="ldap/samchiel.linuxwall.info",response=d096305a248894991856767e3258d7ca

Once again, we can find the description of this string in the RFC:

   digest-response  = 1#( username | realm | nonce | cnonce |
                          nonce-count | qop | digest-uri | response |
                          maxbuf | charset | cipher | authzid |
                          auth-param )

The interesting value is response, the last one, that contains a MD5 hash of the given field plus the password. The response value is computed as follow:

MD5(A1:nonce:cnonce:A2) where:

  1. A1 = MD5( username:realm:password:nonce:cnonce:(authzid))
  2. A2 = (“AUTHENTICATE:”, digest-uri)

So, in the end, the response is composed of: MD5(MD5( username:realm:password:nonce:cnonce:(authzid)):nonce:cnonce:“AUTHENTICATE:”, digest-uri)

rspauth=89cb4dc5c63c4aab3336e9b01c175cd1

Set Up in OpenLDAP

LDAP supports SASL by default. However, SASL uses authentication string that are different than the LDAP strings. Therefore, Slapd must be configured to rewrite the string received from SASL into something it can understand.

Another important point is that, since Slapd needs to calculate the MD5 response in order to validate the challenge, it needs to have access to the clear version of the password of the user. Therefore, we have to store password in cleartext in the directory. That and important limitation of SASL, and some serious file level protection will have to be deployed in order to cover the risk of loosing the datafiles, with the passwords in clear.

SASL rewrite rule in Slapd

SASL string looks like the following: uid=toto,cn=linuxwall.info,cn=DIGEST-MD5,cn=auth <uid>,<realm>,<algorithm>,“auth”

Slapd needs to transform this into something LDAP friendly. To do that, we can add a regular expression into slapd that will take the UID value and put it into a LDAP URI. The rule below needs to be copied inside slapd.conf into the database section.

# SASL rewrite rule
authz-regexp "^uid=([^,]+).*,cn=[^,]*,cn=auth$"
             "ldap:///dc=linuxwall,dc=info??sub?(uid=$1)"

The resulting URI will be something like, for example, ldap:///dc=linuxwall,dc=info??sub?(uid=julien). This request will search the entire directory for an object that has the uid 'julien'. Feel free to adapt the scope of the research to your needs.

Passwords in clear

We will discuss the population of the directory later, but for now, we need to ensure that all the passwords are stored in cleartext. It means that, when changing the password of a user with some external tool (like phpldapadmin), you need to set 'CLEAR' passwords.

Also, in the database section of slapd.conf, we add the following:

password-hash   {CLEARTEXT}

Now, if you want to test this, restart your slapd server and try to perform a search using a SASL binding:

samchiel:/# ldapsearch -Y DIGEST-MD5 -U julien -R 'linuxwall.info' -H ldap://localhost -D 'dc=linuxwall,dc=info' -b 'ou=people,dc=linuxwall,dc=info' -LLL '(uid=julien)'

SASL/DIGEST-MD5 authentication started
Please enter your password: xxxxxxxxxxx

SASL username: julien
SASL SSF: 128
SASL data security layer installed.

dn: cn=Julien Vehent,ou=people,dc=linuxwall,dc=info
givenName: Julien
sn: Vehent
cn: Julien Vehent
uid: julien
objectClass: inetOrgPerson
objectClass: top
userPassword:: Rf6RT48hzznopP0

The binding works perfectly. You can also check your Slapd logs and you should see the following:

May  6 16:08:23 samchiel slapd[2675]: conn=1 fd=15 ACCEPT from IP=[::1]:35019 (IP=[::]:389)
May  6 16:08:23 samchiel slapd[2675]: conn=1 op=0 BIND dn="dc=linuxwall,dc=info" method=163
May  6 16:08:23 samchiel slapd[2675]: conn=1 op=0 RESULT tag=97 err=14 text=SASL(0): successful result:
May  6 16:08:26 samchiel slapd[2675]: conn=1 op=1 BIND dn="dc=linuxwall,dc=info" method=163
May  6 16:08:26 samchiel slapd[2675]: conn=1 op=1 BIND authcid="julien" authzid="julien"
May  6 16:08:26 samchiel slapd[2675]: conn=1 op=1 BIND dn="cn=julien vehent,ou=people,dc=linuxwall,dc=info" mech=DIGEST-MD5 sasl_ssf=128 ssf=128
May  6 16:08:26 samchiel slapd[2675]: conn=1 op=1 RESULT tag=97 err=0 text=
May  6 16:08:26 samchiel slapd[2675]: conn=1 op=2 SRCH base="ou=people,dc=linuxwall,dc=info" scope=2 deref=0 filter="(uid=julien)"
May  6 16:08:26 samchiel slapd[2675]: conn=1 op=2 SEARCH RESULT tag=101 err=0 nentries=1 text=
May  6 16:08:26 samchiel slapd[2675]: conn=1 op=3 UNBIND
May  6 16:08:26 samchiel slapd[2675]: conn=1 fd=15 closed
May  6 16:08:26 samchiel slapd[2675]: connection_read(15): no connection!

SASL Proxy Authorization

Slapd provides another interesting mechanisms. It allow for a user to act on behalf of another user. This is called 'Proxy Authorization' and is extremely useful, especially with cyrus-imap.

To illustrate this, let's take a look at the 'ldapwhoami' command. This command returns your current identity. It's pretty straightforward to use:

samchiel:/# ldapwhoami -U julien -Y DIGEST-MD5 -H ldap://localhost

SASL/DIGEST-MD5 authentication started
Please enter your password:
SASL username: julien
SASL SSF: 128
SASL data security layer installed.
dn:cn=julien vehent,ou=people,dc=linuxwall,dc=info

Now, there is an interesting option in this command: -X. The manpage of ldapwhoami says the following about it:

       -X authzid                                                                                                                 
              Specify the requested authorization ID for SASL bind.
              authzid must be one of the following formats: dn:<distinguished name>
              or u:<username> 

With this option, we can bind as a user JEANKEVIN using the credential of JULIEN. However, JULIEN needs to possess the good permissions. By default, slapd blocks that command:

samchiel:/# ldapwhoami -U julien -Y DIGEST-MD5 -X u:jeankevin -H ldap://localhost

SASL/DIGEST-MD5 authentication started
Please enter your password: xxxxxxx

ldap_sasl_interactive_bind_s: Insufficient access (50)

This permission is controlled by a directive called authzTo. We need to activate this directive at two levels:

  1. in slapd.conf, we need to add the authz policy
  2. in the object that represents JULIEN, we need to give the permission to proxy authorization
authz-policy in slapd.conf

Let's take a look at the explanation from the man page regarding the authz-policy directive

      authz-policy <policy> 
            Used to specify which rules to use for Proxy Authorization.
            Proxy authorization allows a client to authenticate to the server
            using one user's credentials, but specify a different identity to
            use for authorization and access control purposes. It essentially 
            allows user A to login as user B, using user A's password. The none
            flag disables proxy authorization. This is the default setting. The
            from flag will use rules in the authzFrom  attribute of the 
            authorization DN. The to flag will use rules in the authzTo
            attribute of the authentication DN. The any flag, an alias for the
            deprecated value of both, will allow any of the above, whatever
            succeeds first (checked in to, from sequence. The all  flag requires
            both authorizations to succeed. 

[...]

And the manpage continues with a description of the possible values and the format of the authzTo directive. In our slapd.conf file, we simply add the following directive in the database section:

authz-policy to

And then restart the server.

authzTo in entry attributes

In the definition of user JULIEN, we need to add an attribute. This attribute is called authzTo and takes as argument a research rule. This rule will return the list of objects for which the user can identify itself as.

Our directive will be the following:

authzTo: ldap:///dc=linuxwall,dc=info??sub?(objectclass=inetOrgPerson)

This rule will search for all of the 'inetOrgPerson' object withing the root DN. the 'sub' parameters allow the query to walk everywhere in the tree and look for all the objects. Obviously, this rule is way too permissive for production use, but it will serve us well to illustrate the mechanism.

So, use you preferate editor and add the previous attribute to user JULIEN.

Then, go back to the command line and try the ldapwhoami command again:

samchiel:/# ldapwhoami -U julien -Y DIGEST-MD5 -X u:jeankevin -H ldap://localhost

SASL/DIGEST-MD5 authentication started
Please enter your password: xxxxxxx

SASL username: u:jeankevin
SASL SSF: 128
SASL data security layer installed.

dn:cn=jean-kevin michel,ou=people,dc=linuxwall,dc=info

Authentication works fine. Now look at the last line that return the current identity. We are authenticated as “jean-kevin michel”. The proxy authentication worked !

LDAPS and TLS

By default, Slapd listen for queries coming from everything in clear. But it also ships a TLS support (with GNUTLS, if I'm not mistaken). To activate it, we need a server certificate.

The configuration takes places in slapd.conf and in /etc/default/slapd:

slapd.conf

Assuming you have your server certificate, private key and CA certificate in /etc/ldap/certs, you need to add the following to the global section of slapd.conf:

# TLS configuration for LDAPS
TLSCACertificateFile /etc/ldap/certs/ca-linuxwall.crt
TLSCertificateFile /etc/ldap/certs/ldap.linuxwall.info.pem
TLSCertificateKeyFile /etc/ldap/certs/ldap.linuxwall.info.key

<note warning>Do not forget to give ownership to 'openldap' user, otherwise the server will crash when trying to read the certificate.

# chown openldap /etc/ldap/certs/ -R

</note>

startup parameter

In /etc/default/slapd, we need to add a ldaps socket to listen for queries on the TLS port (ie. TCP/636).

SLAPD_SERVICES="ldap://127.0.0.1:389/ ldapi:/// ldaps:///"

Restart slapd and test using openssl:

# openssl s_client -connect localhost:636

CONNECTED(00000003)                                                                                                               
depth=1 /C=FR/ST=France/L=Paris/O=Linuxwall/CN=Linuxwall Certificate Authority Root Certificate/emailAddress=root@linuxwall.info  
verify error:num=19:self signed certificate in certificate chain                                                                  
verify return:0                                                                                                                   
---                                                                                                                               
Certificate chain                                                                                                                 
 0 s:/C=FR/ST=France/O=Linuxwall/CN=ldap.linuxwall.info                                                                           
   i:/C=FR/ST=France/L=Paris/O=Linuxwall/CN=Linuxwall Certificate Authority Root Certificate/emailAddress=root@linuxwall.info     
 1 s:/C=FR/ST=France/L=Paris/O=L

[... ]

Replication

Openldap 2.4 ships syncrepl, a mechanism to keep several directories synchronized. The slave directory will connect to the master on a regular basis and look for changes since the last time it replicated.

syncrepl allows for two methods of replication: refresh-only and refresh and persist. The difference between the two is that the second one will keep the connection to the master open so the master can inform the slave of any changes that happen to any entry, and that in real time.

In our infrastructure, we will stick to the first model of replication. The reasons are:

With that in mind, a simple setup with one master and as many slaves as you want is pretty straightforward to configure. We need:

  1. a replication account
  2. a module on the master
  3. a syncrepl directive on the slave

On the Master

slapd.conf needs a new overlay called “syncprov”. The 'syncprov-checkpoint' directive will ensure that ContextCSN datas will be committed often enough.

<note>The overlay creates a contextCSN attribute in the root entry of the database which is updated for every write operation performed against the database and only updated in memory.</note>

So, first, declares the overlay at the top of slapd.conf:

moduleload syncprov

Then, in the database section, configure the overlay:

# SYNCREPL configuration
overlay syncprov
syncprov-checkpoint 50 10

Since the replication will query the 'entryCSM' and 'entryUUID' attributes, we will also add indexes on those two entries. In slapd.conf, database section:

index           entryCSN,entryUUID      eq

Then regenerate the indexes.

root@samchiel:~# /etc/init.d/slapd stop
Stopping OpenLDAP: slapd.

root@samchiel:~# sudo -u openldap slapindex -q

root@samchiel:~# /etc/init.d/slapd start
Starting OpenLDAP: slapd.

Replication account

On the master, we need an entry that can read the whole directory.

dn: cn=syncrepl,ou=infrastructure,dc=linuxwall,dc=info
cn:  syncrepl
objectclass: inetOrgPerson
objectclass: top
sn: syncrepl
uid: syncrepl
userpassword: highsecuritypassword

The replication account will also need to replicate the 'userPassword' attribute, for which we have a special ACL on the master:

access to attrs=userPassword
        by dn="cn=admin,dc=linuxwall,dc=info" write
        by anonymous auth
        by self write
        by * none

By default, only 'admin' and 'self' can manipulate this attribute. But for the replication to work, we need to grant this access to 'syncrepl' as well. The following ACL does the job just fine:

access to attrs=userPassword
        by dn="cn=admin,dc=linuxwall,dc=info" write
        by dn="cn=syncrepl,ou=infrastructure,dc=linuxwall,dc=info" read
        by anonymous auth
        by self write
        by * none

<note tip>I noticed that, on some places, they recommand to give full write access on the directory to the 'syncrepl' account. I do not think this is necessary (but you're welcome to prove me wrong :) ).</note>

On the slave

On the slave side, the configuration of slapd.conf is very close to the one of the master. We can define the same indexes and ACLs (or define different ones). Since the replication only concerns the objects and attributes, for everything else you need manual replication of the parameters.

<note tip>This may give you a reason to look at the configuration backend, so you can replicate you configuration parameters like any other attribute.</note>

Flush your installation

If your slave is on Debian as well, the installation will create you a directory during the installation. You can flush these file and start over with an empty installation by deleting the data files in /var/lib/ldap/:

root@ptitchoun:/ # /etc/init.d/slapd stop
Stopping OpenLDAP: slapd.

root@ptitchoun:/ # cd /var/lib/ldap/

root@ptitchoun:/var/lib/ldap # ls
alock   __db.001  __db.003  __db.005   dn2id.bdb     log.0000000001   uid.bdb
cn.bdb  __db.002  __db.004  DB_CONFIG  id2entry.bdb  objectClass.bdb

root@ptitchoun:/var/lib/ldap # rm __db.00* *.bdb log.*

root@ptitchoun:/var/lib/ldap # ls
alock  DB_CONFIG

Do not restart slapd right away, we need to do some configuration before.

slapd.conf on the slave

Giving our previous configuration for the master, the slave's slapd.conf file looks like the following. Pay particular attention to the ACLs, they are more restrictives than on the master (no write access to anyone here).

include         /etc/ldap/schema/core.schema
include         /etc/ldap/schema/cosine.schema
include         /etc/ldap/schema/nis.schema
include         /etc/ldap/schema/inetorgperson.schema

pidfile         /var/run/slapd/slapd.pid
argsfile        /var/run/slapd/slapd.args

loglevel        stats sync ACL config filter

modulepath      /usr/lib/ldap
moduleload      back_hdb

sizelimit 500

tool-threads 1
 
backend         hdb
database        hdb
suffix          "dc=linuxwall,dc=info"
rootdn          "cn=admin,dc=linuxwall,dc=info"
directory       "/var/lib/ldap"
password-hash   {CLEARTEXT}

dbconfig set_cachesize 0 20971520 0
dbconfig set_lk_max_objects 1500
dbconfig set_lk_max_locks 1500
dbconfig set_lk_max_lockers 1500

index           objectClass eq
index           cn    eq,sub,pres,approx
index           uid   eq

lastmod         on

checkpoint      512 30

access to attrs=userPassword
        by anonymous auth
        by * none

access to dn.base="" by * read

access to *
        by * read

authz-regexp "^uid=([^,]+).*,cn=[^,]*,cn=auth$"
              "ldap:///dc=linuxwall,dc=info??sub?(uid=$1)"

authz-policy to
 
# SYNCREPL replication directives
syncrepl rid=001
   provider=ldaps://ldap.linuxwall.info
   type=refreshOnly
   interval=00:01:00:00
   searchbase="dc=linuxwall,dc=info"
   scope=sub
   binddn="cn=syncrepl,ou=infrastructure,dc=linuxwall,dc=info"
   credentials=highsecuritypassword

Most of the directive have already been described. The new one is the 'syncrepl' directive. It takes the following arguments:

<note important>If your directories are all on the same network, and bandwidth is not an issue for you, it is recommended to use 'refreshAndPersist'. In this case, the 'interval' argument is ignored.</note>

Connect from the slave to the master using TLS

Because, we use LDAPS to connect to the master, we also need to edit etc/ldap/ldap.conf to add the CA certificate, otherwise, the establishment of the TLS connection will fail. So, in ldap.conf, add the following line:

TLS_CACERT /etc/ldap/certs/ca-linuxwall.crt

An copy the 'ca-linuxwall.crt' file to the corresponding folder.

<note important>By default, when establishing a LDAPS connection, OpenLDAP requires that the CN fields of the returned certificate matches the URI. It means that, when using LDAPS, you master server must return a certificate named ldap.linuxwall.info and your client must target ldaps:/ldap.linuxwall.info.

The '-d8' switch can be used with ldapsearch to diagnose such problems:

# ldapsearch -LL -x -W -D 'cn=syncrepl,ou=infrastructure,dc=linuxwall,dc=info' -H ldaps://samchiel.linuxwall.info '(uid=syncrepl)' -d8

Enter LDAP Password:

TLS: hostname (samchiel.linuxwall.info) does not match common name in certificate (ldap.linuxwall.info).
ldap_sasl_bind(SIMPLE): Can't contact LDAP server (-1)

</note>

Test the replication

We can now restart slapd on the slave. The directory /var/lib/ldap/ should populate itself automatically:

root@ptitchoun:/var/lib/ldap # /etc/init.d/slapd start
Starting OpenLDAP: slapd.

root@ptitchoun:/var/lib/ldap # ls -l
total 220
-rw-r--r-- 1 openldap openldap    4096 mai 12 13:18 alock
-rw------- 1 openldap openldap    8192 mai 12 13:18 cn.bdb
-rw------- 1 openldap openldap    8192 mai 12 13:18 __db.001
-rw------- 1 openldap openldap 2629632 mai 12 13:18 __db.002
-rw------- 1 openldap openldap   98304 mai 12 13:18 __db.003
-rw------- 1 openldap openldap  565248 mai 12 13:18 __db.004
-rw------- 1 openldap openldap   24576 mai 12 13:18 __db.005
-rw-r--r-- 1 openldap openldap      96 mai 11 17:14 DB_CONFIG
-rw------- 1 openldap openldap    8192 mai 12 13:18 dn2id.bdb
-rw------- 1 openldap openldap   32768 mai 12 13:18 id2entry.bdb
-rw------- 1 openldap openldap   90208 mai 12 13:18 log.0000000001
-rw------- 1 openldap openldap    8192 mai 12 13:18 objectClass.bdb
-rw------- 1 openldap openldap    8192 mai 12 13:18 uid.bdb

Also, we can take a look at the log file, you should see things like this:

May 12 13:18:52 ptitchoun slapd[10486]: syncrepl_entry: rid=001 be_search (0)                                                     
May 12 13:18:52 ptitchoun slapd[10486]: syncrepl_entry: rid=001 cn=Julien Vehent,ou=people,dc=linuxwall,dc=info                   
May 12 13:18:52 ptitchoun slapd[10486]: => access_allowed: add access to "ou=people,dc=linuxwall,dc=info" "children" requested    
May 12 13:18:52 ptitchoun slapd[10486]: <= root access granted                                                                    
May 12 13:18:52 ptitchoun slapd[10486]: => access_allowed: add access granted by manage(=mwrscxd)                                 
May 12 13:18:52 ptitchoun slapd[10486]: => access_allowed: add access to "cn=Julien Vehent,ou=people,dc=linuxwall,dc=info" "entry"
 requested                                                                                                                        
May 12 13:18:52 ptitchoun slapd[10486]: <= root access granted                                                                    
May 12 13:18:52 ptitchoun slapd[10486]: => access_allowed: add access granted by manage(=mwrscxd)                                 
May 12 13:18:52 ptitchoun slapd[10486]: syncrepl_entry: rid=001 be_add (0)                                                        
May 12 13:18:52 ptitchoun slapd[10486]: syncrepl_entry: rid=001 LDAP_RES_SEARCH_ENTRY(LDAP_SYNC_ADD)                              
May 12 13:18:52 ptitchoun slapd[10486]: syncrepl_entry: rid=001 inserted UUID aaecf1d6-f15c-102e-9b7c-f1e0ff67341b                
May 12 13:18:52 ptitchoun slapd[10486]: => access_allowed: search access to "dc=linuxwall,dc=info" "entry" requested              
May 12 13:18:52 ptitchoun slapd[10486]: <= root access granted                                                                    
May 12 13:18:52 ptitchoun slapd[10486]: => access_allowed: search access granted by manage(=mwrscxd)                              
May 12 13:18:52 ptitchoun slapd[10486]: => bdb_filter_candidates                                                                  
May 12 13:18:52 ptitchoun slapd[10486]: #011AND                                                                                   
May 12 13:18:52 ptitchoun slapd[10486]: => bdb_list_candidates 0xa0                                                               
May 12 13:18:52 ptitchoun slapd[10486]: => bdb_filter_candidates                                                                  
May 12 13:18:52 ptitchoun slapd[10486]: #011EQUALITY                                                                              
May 12 13:18:52 ptitchoun slapd[10486]: <= bdb_equality_candidates: (entryUUID) not indexed                                       
May 12 13:18:52 ptitchoun slapd[10486]: <= bdb_filter_candidates: id=-1 first=1 last=6                                            
May 12 13:18:52 ptitchoun slapd[10486]: <= bdb_list_candidates: id=-1 first=1 last=6                                              
May 12 13:18:52 ptitchoun slapd[10486]: <= bdb_filter_candidates: id=-1 first=1 last=6                                            
May 12 13:18:52 ptitchoun slapd[10486]: => test_filter                                                                            
May 12 13:18:52 ptitchoun slapd[10486]:     EQUALITY                                                                          

This is how the slave and the master communicate.

On the slave, we can also test the connection of a user using its password, to make sure all the attributes are synchronized correctly:

root@ptitchoun:/var/lib/ldap # ldapsearch -U julien -R 'linuxwall.info' -H ldap://localhost -D 'dc=linuxwall,dc=info' -b 'ou=people,dc=linuxwall,dc=info' -LLL '(uid=julien)'

SASL/DIGEST-MD5 authentication started
Please enter your password: xxxxx
SASL username: julien
SASL SSF: 128
SASL data security layer installed.

dn: cn=Julien Vehent,ou=people,dc=linuxwall,dc=info
givenName: Julien
sn: Vehent
cn: Julien Vehent
uid: julien
mail: julien@linuxwall.info
objectClass: inetOrgPerson
l: Paris
postalCode: 75011
telephoneNumber: +33.6.23.86.58.73
street: 27 rue Keller

That's seems pretty alright to me :)

Add an account with ldapadd

ldapadd is the command line tool for adding account to the LDAP directory. It needs an LDIF file that describes the new account, and the information to bind to the directory. We can add an account with the following LDIF file :

dn: cn=Demo User,ou=people,dc=linuxwall,dc=info
givenName: Demo
sn: User
cn: Demo User
uid: demo
mail: demo@linuxwall.info
objectClass: inetOrgPerson
l: Trifouilly les Oies
postalCode: 27412
userPassword: iwhefoheiod8394f8293dhohd1092dfo

And then the following command :

# ldapadd -h 127.0.0.1 -p 389 -D "cn=admin,dc=linuxwall,dc=info" -W -f insert_demo.ldif
Enter LDAP Password:
adding new entry "cn=Demo User,ou=people,dc=linuxwall,dc=info"

Manipulating the attributes of an existing object

Similarly to ldapadd, ldapmodify can add/modify and delete attributes from an existing object. The syntax is identical to ldapadd, except for the argument '-a' that means “add” a new attribute, instead of modifying the existing one.

$ ldapmodify -a -h 127.0.0.1  -p 389 -D "cn=admin,dc=linuxwall,dc=info" -W <<EOF
dn: cn=Demo User,ou=people,dc=linuxwall,dc=info
changetype: modify
add: mail
mail: spam-demo@linuxwall.info
EOF

Enter LDAP Password:
modifying entry "cn=Demo User,ou=people,dc=linuxwall,dc=info"

<note tip>In fact, ldapadd is just a specific invocation of ldapmodify.</note>

Password Policy

OpenLDAP has the hability to apply password policies to users. This is performed by the ppolicy overlay and must be configured in slapd.conf. The default password policy will be stored has an object in the directory. We will create a new organisational unit called 'policies' that will contain this object. Then, the ppolicy configuration directives can be used to define the criterias to apply to the 'userPassword' attribute.

Creating the 'policies' OU

Using the command line, this is pretty straightforward :

# ldapadd -x -D cn=admin,dc=linuxwall,dc=info -W << EOF

dn: ou=policies,dc=linuxwall,dc=info
objectClass: organizationalUnit
objectClass: top
ou: policies
EOF


Enter LDAP Password:
adding new entry "ou=policies,dc=linuxwall,dc=info"

Configuring the overlay

The ppolicy schema must be included, as well as the ppolicy module. Then, in the database section, we can configure the path of the password policy object. The parameters of the password policy will be contained in the object directly.

The pwdPolicy has the following definition, from ppolicy.schema.

141 objectclass ( 1.3.6.1.4.1.42.2.27.8.2.1
142       NAME 'pwdPolicy'
143       SUP top
144       AUXILIARY
145       MUST ( pwdAttribute )
146       MAY ( pwdMinAge $ pwdMaxAge $ pwdInHistory $ pwdCheckQuality $
147       pwdMinLength $ pwdExpireWarning $ pwdGraceAuthNLimit $ pwdLockout
148       $ pwdLockoutDuration $ pwdMaxFailure $ pwdFailureCountInterval $
149       pwdMustChange $ pwdAllowUserChange $ pwdSafeModify ) )

So, we start by adding this new schema and module to slapd.conf:

include         /etc/ldap/schema/ppolicy.schema

[...]

moduleload      ppolicy

[...]

backend         hdb
database        hdb
suffix          "dc=linuxwall,dc=info"

[...]

# Password policy
overlay ppolicy
ppolicy_default "cn=defaultpwpolicy,ou=policies,dc=linuxwall,dc=info"

And then, we create the object :

# ldapadd -x -D cn=admin,dc=linuxwall,dc=info -W << EOF
dn: cn=defaultpwpolicy,ou=policies,dc=linuxwall,dc=info
cn: defaultpwpolicy
sn: Default Password Policy
objectClass: pwdPolicy
objectClass: top
objectClass: person
pwdAttribute: 2.5.4.35
pwdAllowUserChange: TRUE
pwdInHistory: 2
pwdMaxFailure: 10
pwdLockout: TRUE
pwdLockoutDuration: 1800
pwdMinLength: 6
EOF

<note warning>TO FIX: ERROR ldap_add: Invalid syntax (21)

      additional info: objectClass: value #0 invalid per syntax

</note>

TODO: INTEGRATE check_password.c

http://ltb-project.org/wiki/documentation/openldap-ppolicy-check-password