====== 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:
* Installation and Configuration of Slapd 2.4 on Debian Squeeze
* Creation of an annuary for identity management, including groups and custom attributes
* Configuration of the SASL authentication
* Clustering with one master and two slaves
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 [[http://www.openldap.org/doc/admin24/|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:
{{:en:ressources:dossiers:openldap:ldappasswd.png|}}
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 [[http://www.ietf.org/rfc/rfc2256.txt|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).
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.
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:
- core: the main schema
- cosine: a schema definition standardized in 1991 (!) as a pilot for X500 directories
- nis: a schema for unix users, useful to define a login shell or a home directory
- 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
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**.
== 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.
Debian Squeeze ships rsyslog instead of the old syslogd by default.
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.
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. [[http://en.wikipedia.org/wiki/Berkeley_DB|source: wikipedia]]
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:
- I defines the type of backend to use for this directory. In our case, that would be HDB.
- 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
- the **db** files contain raw objects
- **DB_CONFIG** contains some tweaking parameters
- **dn2id.bdb** is a view containing a matching between distinguished names of objects and internal storage ID
- **id2entry.bdb** is a view containing the details of each object
- **objectClass.bdb** is the file for the index we defined in the configuration earlier
- **log.0000000001** is the Berkeley DB transaction log file
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.
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:
* set_cachesize: size of the cache. The HDB directory tries to keep as much of the directory in memory as possible. It will determine which entries to keep in memory, and it is definitely interesting for response times to have, at least, the most called objects in memory. This directive uses 3 arguments to determine the cache:
- gigabytes of memory space allocated to the cache
- bytes of memory space allocated to the cache
- 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.
* set_lk_max*: as commented in the file, those parameters concern the number of locks. Locks are used to ensure that only one threads in manipulating one data at a time, such preventing from inconsistencies. The default value of 1500 is enough for our tiny installation.
== 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:
* eq: match for the exact value
* sub: match for a subpart of the string/value
* pres: match for existence
* approx: kind of a regular expression with mixed value, etc...
We then add those indexes to **slapd.conf** and manually launch the index command to create two new files in **/var/lib/ldap**:
slapindex must be launched:
- while slapd is stopped
- under user **openldap** via sudo
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
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
[by [] [] ]+
where the part selects the entries and/or attributes to which the access applies, the part specifies which entities are granted access, and the part specifies the access granted. Multiple 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.
The SASL method is necessary to allow cyrus-imap to connect to the directory without passing through saslauthd and pam. See the [[http://wiki.linuxwall.info/doku.php/en:ressources:dossiers:cyrus:imapd|Cyrus Imapd Chapter]] for more details
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 ([[http://www.ietf.org/rfc/rfc2831.txt|RFC 2831]].
==== Authentication Algorithm ====
DIGEST-MD5 works as follow:
* 1. client declares itself to the server and announce it wants to use DIGEST-MD5 to bind to the realm linuxwall.info
dc=linuxwall,dc=info....DIGEST-MD5
* 2. server replies with a challenge
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 )
* 3. the client responds to the server
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:
- A1 = MD5( username:realm:password:nonce:cnonce:(authzid))
- 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)**
* 3. The server receives the response and validates it by recalculating the same response (this it has all the fields in clear text). If the client's response is good, then the server replies with the following string to validate the binding:
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**
,,,"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.
{{:en:ressources:dossiers:openldap:clearpwd.png|}}
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:
or u:
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:
- in **slapd.conf**, we need to add the authz policy
- 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
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
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
=== 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:
* the two directories are on different sites, thus keeping a connection open over the internet is expensive
* the master will have very few objects to manage, and changes won't happen that often
With that in mind, a simple setup with one master and as many slaves as you want is pretty straightforward to configure. We need:
- a replication account
- a module on the master
- 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.
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.
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
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 :) ).
==== 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.
This may give you a reason to look at the configuration backend, so you can replicate you configuration parameters like any other attribute.
=== 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:
* rid: and replication identification number. If you have several slaves replicating a directory, each slave must have its own rid, different from the others.
* provider: the URI of the master, using 'ldaps' triggers the use of TLS to contact the master
* type: the type of replication, to chose between 'refreshOnly' and 'refreshAndPersist'
* interval: how often does the slave contact the server, the format is "dd:hh:mm:ss"
* searchbase: where do we start the replication. can be used to replicate a sub-part of the master only
* binddn: the user to bind to the master, as seen earlier
* credentials: its password
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.
=== 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.
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)
=== 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 <In fact, **ldapadd** is just a specific invocation of **ldapmodify**.
===== 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
TO FIX: ERROR
ldap_add: Invalid syntax (21)
additional info: objectClass: value #0 invalid per syntax
==== TODO: INTEGRATE check_password.c ====
http://ltb-project.org/wiki/documentation/openldap-ppolicy-check-password