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 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
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:///"
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.
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:
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>
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
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 ...]
This directive as two functions:
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
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
<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.*
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:
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.
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:
</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.
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
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.
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
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.
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:
So, in the end, the response is composed of: MD5(MD5( username:realm:password:nonce:cnonce:(authzid)):nonce:cnonce:“AUTHENTICATE:”, digest-uri)
rspauth=89cb4dc5c63c4aab3336e9b01c175cd1
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 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.
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!
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:
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.
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 !
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:
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>
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 [... ]
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:
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.
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 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>
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.
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>
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>
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 :)
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"
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>
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.
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"
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>