====== 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