OpenLDAP

Table of Contents (TOC)

Introduction
Installation
Initial Server Configuration
Creating the Database
Schema
Starting the Server
Adding Entries to the Database
Backing Up the Database
Editing Database Entries
Adding Entries to the Address Book
Authenticating Against an LDAP Server
Configuring the Client for Authentication
Access Control
TLS and SSL Security
Using phpldapadmin
Exporting a Thunderbird Address Book
Links

Introduction

There's a lot of documentation out there about using OpenLDAP. Much of it, in my opinion, is either overly complex or overly simplified. It is a complex subject, and it's difficult to find the balance between how and why. This page is an effort to document a relatively simple setup, that took far longer to research than I felt it should have, of an LDAP server that is used for authentication by Linux clients and also has an address book.

I made use of many tutorials while beginning to understand LDAP. There's a Links section at the end of this article. I did my best to properly mix the How to and Why aspects. Hopefully, the digressions from the How to cover the Why add to, rather than detract from, the reader's understanding. There are several tutorials around that just give the How part of it, some are mentioned in the Links section.

As of CentOS/RH/OLE/SL6, LDAP is apparently using an /etc/slap.d/ directory. Later versions of Fedora are also using sssd. I haven't used LDAP as a server on anything later than CentOS 5.x, so this page will be obsolete in parts. It is hoped that it will still be useful. When I've found them, I've added links to the changes.

There is much that is still somewhat obscure to me too--the selfish aspect of writing this article is the hope that as I explain it to you, it will become clearer to me. You will find that in parts of this article, I mention that I don't quite know why something is working and not working.

This only covers using CentOS as a server. No doubt, other distributions do things differently. It's my hope that the reader who uses something else will still find useful information here.

Unfortunately, it seems that updates from 5.4 to 5.5 have broken some things. I haven't yet tried LDAP on CentOS 5.5, but users are advised to view this thread (and the thread linked there), on CentOS forums.

It is, as mentioned in the threads, also covered on the wiki in the Known Issues section. RedHat doesn't seem to have bothered to document it. I'll cover it in a bit more detail below.

Fedora of course, is even worse at breaking things, and it seems that Fedora 13 (as in Friday, The) also has non-documented changes that break its LDAP. I've never run Fedora as an LDAP server, but those who do might find this thread helpful.

OpenLDAP has many things that can go wrong. The purpose of this article is to get you started, and hopefully, give you enough understanding so that you can then start investigating more detailed documentation. I've tried to present it in logical order, starting with the simpler things.

TOC

Installation

This is the easiest part.
yum install openldap{,-clients,-devel,-servers} nss_ldap

This will install the necesary programs and we can begin with the next step, configuring the server.

Note that as of Fedora 14, you probably want to include pam_ldap, which may be required, especially for authentication. So, for Fedora 14 and later
yum install openldap{,-clients,-devel,-servers} nss_ldap pam_ldap

TOC

Initial Server Configuration

OpenLDAP installs several files in /etc and other places. The slapd daemon's configuration file is called, oddly enough, slapd.conf and can be found in /etc/openldap. As part of that file's configuration, it will require an administrative password, so let's create that first.

slappasswd > pass.txt

It will ask you to create a password and ask you to confirm it. In this case, we're using > to redirect the output to a file called pass.txt. If we hadn't redirected it to a file, slappasswd would print the encrypted password to standard output.

Edit /etc/openldap/slapd.conf. We're looking for 5 entries, that of database, suffix, rootdn, rootpw, and directory. (Don't worry, we're still at the easy part.) It should look something like

database        bdb
suffix          "dc=example,dc=com"
rootdn          "cn=Manager,dc=example,dc=com"
# Cleartext passwords, especially for the rootdn, should
# be avoided.  See slappasswd(8) and slapd.conf(5) for details.
# Use of strong authentication encouraged.
# rootpw                secret

There may also be an example encrypted password there.

For this article, we'll say you have a company called mycompany.com. (The trouble with using the standard example.com is that the ldap files already use it, so this will avoid confusion.) The directory line is towards the end of the file, and will give the default directory for the database, /var/lib/ldap.

For the database, you can stick with the default bdb, which is the Berkeley Database.

The suffix will be your main domain or organization. So, for our example, we'll change it to read

suffix          "dc=mycompany,dc=com"

Next, the rootdn. This means root Distinguished Name. Every entry in an LDAP database has a distinguished name, we'll get into that later. THe default is Manager (and you'll see that it's not case sensitive--this has to do with the schema--see why it gets complicated?). We'll stick with the default for now but you could change this. If you wanted to use admin, or Bob, rather than manager, you can change that cn. Sticking with our example, however, it will now read
rootdn          "cn=Manager,dc=mycompany,dc=com"

The dc is for domainComponent. (The usual method of naming LDAP things is lowercaseUppercase, (sometimes called camelcase, due to, I assume, the shape of a camel's hump), however this can vary between distributions and is usually unimportant--that is, organizationalUnit and OrganizationalUnit should both work on most systems). The "mycompany" and "com" are components of the domain. If your domain consists of three components, for example, mycompany.com.uk, then you would use dc=mycompany,dc=com,dc=uk.

Next the password. We want an encrypted password, so we'll use that one we created earlier with slappasswd. Just replace secret with the pass.txt file that we created, so it looks something like

 rootpw                {SSHA}V09CickL11mw

Actually, there will be several more alpha numeric characters than the example above, but you get the idea.

You might wish to edit the directory line. The default directory for the LDAP databases is /var/lib/ldap. If you wish, you can make a directory /var/lib/ldap/mycompany. If you do create such a directory, then change the line (it's towards the end of the file) from /var/lib/ldap to

directory       /var/lib/ldap/mycompany

However, for this article, we're assuming that you will use the default /var/lib/ldap.

Now that we're done, run the command

slaptest -u

If you've made no mistakes, it should say config file testing succeeded.

The slaptest command just tests the slapd.conf file. The -u option means don't worry about whether or not there's an actual database there, just check the configuration. The command must be run as root or with root privilege.

Now it begins to get a bit tricky, we're going to begin to populate the database.

Lastly, Fedora has added a directory, /etc/openldap/slap.d. As I've not run an LDAP server on Fedora, I have no idea what it does, but I have seen threads on the Fedora forums where people, when trying to do something will get an error of ldap_bind Invalid credentials, that will be fixed by removing that directory. Do this at your own risk. I reiterate, I haven't tried to run an LDAP server on Fedora, so have no idea if it will break something.

As mentioned, CentOS 5.5 has also changed things. There is now an /etc/sysconfig/ldap file. By default it reads
LAPD_LDAP=yes
SLAPD_LDAPS=no
SLAPD_LDAPI=no

If not using encryption, you can leave it alone. If using SSL or TLS, you would change the SLAPD_LDAPS line from a no to a yes, so that the file would read
LAPD_LDAP=yes
SLAPD_LDAPS=yes
SLAPD_LDAPI=no

We'll leave it alone for the moment, while we get things started.

There is also an /etc/openldap/ldap.conf file. However, this is for clients, according to man(5) ldap.conf, so can be left alone at present.

TOC

Creating the Database

This is a long section, so prepare yourself.

The OpenLDAP rpms include a variety of migration scripts. We're not going to use them yet, however, we will start by editing them slightly.

As of RedHat 6, the migration scripts are no longer included. If using RHEL6 or any of its offshoots, such as CentOS, ScientificLinux, or Oracle Linux, you will also have to install the migrationtools package with yum install migrationtools.

As it is RedHat (Let's change something to add little or no functionality, break existing configurations, and then, not bother to document it), no doubt there are other undocumented changes that I haven't yet seen. Please see this thread on the CentOS forums, where some users have documented differences that they are discovering. One documented difference is that RH is now using /etc/openldap/slap.d rather than /etc/openldap/slapd.conf. They have notes on it here. At any rate, moving on....

Using your favorite text editor, start with /usr/share/openldap/migration/migrate_common.ph. Looking through it, you'll see a few references (4 to be exact, at least in the CentOS 5.3 version) referring to padl.com, a site used by the group that creates these migration tools. (ldap backwards, get it?). We're going to change those to from padl.com to mycompany.com. This way, when we do use the migration scripts, they will reflect our company, rather than padl.com.

As mentioned, there are 4 references. $DEFAULT_MAIL_DOMAIN and $DEFAULT_BASE are uncommented. The other two, a define line and $DEFAULT_MAIL_HOST are commented out. It's best to change all 4, however, since you may need it in the future.

If using vi, one can just hit escape to get to the command mode and type

:%s/padl/mycompany/

(For those unfamilar with vi, the % means do this on all lines where the pattern occurs, s is substitute.)

Next, copy /etc/openldap/DB_CONFIG.example to /var/lib/ldap/DB_CONFIG. If there is no DB_CONFIG file in your ldap directory, you will get a warning that you can expect poorer performance. If you created a custom subdirectory, that is, /var/lib/ldap/mycompany, then copy the DB_CONFIG.example file to /var/lib/ldap/mycompany, rather than /var/lib/ldap.

As there's a good chance you'll be redoing your ldap directory frequently while first setting up your DIT (Directory Information Tree), I usually find it easier to also copy the DB_CONFIG.example file to DB_CONFIG in the /etc/openldap directory. This way, when I redo the database, it saves me a few keystrokes, as I won't have to rename it while copying it.

Many howtos will now use the migration scripts to start populating the data base. However, this will give you something like 40 entries, most of which are unnecessary. I prefer to create a small base ldif file that will have the main domain and needed organizationalUnit entries.

Data can be entered into the database in various ways, but if doing it manually, one creates an ldif file. An ldif file is a text file following certain rules which we'll cover shortly. It stands for LDAP Data Interchange Format file, and aside from being used by OpenLDAP, can also be read by many other programs, such as a variety of email clients.

Our first ldif file will contain the domain and the organizationUnits of People, Group and addressbook. Using your favorite text editor, create a file reading

dn: dc=mycompany,dc=com
objectClass: top
objectClass: domain
dc: mycompany

dn: ou=People,dc=mycompany,dc=com
objectClass: top
objectClass: OrganizationalUnit
ou: People

dn: ou=Group,dc=mycompany,dc=com
objectClass: top
objectClass: OrganizationalUnit
ou: Group

dn: ou=addressbook,dc=mycompany,dc=com
objectClass: top
objectClass: OrganizationalUnit
ou: addressbook

The first entry is for the domain. Many prefer to use organization instead. If using organization, then the first entry would read

dn: dc=mycompany,dc=com
objectClass: dcObject
objectClass: organization
o: My Company
dc: mycompany

To see what you would get if you did use the migration scripts, you can, as root or with root privilege, run any of the scripts in /usr/share/openldap/migration and redirect it to an ldif file, rather than letting it populate the database. For example

/usr/share/openldap/migration/migrate_passwd.pl /etc/passwd >
passwords.ldif

As you'll see, this would wind up giving you a bunch of system users, such as bin, uutp, nobody, rpm, etc. The same thing will happen if you use, for example, one of the migrate_all shell scripts. This is why we're doing it this way. If you're familiar with perl and/or shell scripting, you can look at the individual scripts and figure out what they do. Even if you're not familar with either scripting language, glancing at the scripts can be useful.

Any of them can be redirected to an ldif file, which can then be edited. For example, if you need 20 users, all in /etc/passwd, you can run the migrate_passwd.pl script as mentioned above, then just delete the unneeded entries from the ldif file.

Back to our base.ldif, let's examine what we have.

dn: dc=mycompany,dc=com
objectClass: top
objectClass: domain
dc: mycompany

The first line is the dn, distinguished name. As mentioned, every entry in an LDAP database needs a unique name. The next lines refer to objectClasses.

A domain is an objectClass. Most objectClasses have a parent. That is, the objectClass domain requires the objectClass top. Just about all objectClasses require top as their parent. Because of this, one actually doesn't even need the line objectClass: top, because, by having an objectClass like domain, the "top" is implied. I'm putting it in these samples for clarity and to be sure you know about it, but in most cases, it can be left out--because its subordinate classes, such as a domain, couldn't exist unless the top objectClass exists, in the same way you couldn't exist in your present form if your parents had never existed.

As you play with OpenLDAP, this becomes a bit clearer. Certain objectClasses require other objectClasses--for example inetorgPerson requires Person, which requires top, and so on. Don't worry about it right now, just keep in mind that just about every objectClass requires top.

The dc has been explained previously, it's just giving the "mycompany" section of the domain component.

The rest of the entries are relatively straightfoward.

dn: ou=People,dc=mycompany,dc=com
objectClass: top
objectClass: OrganizationalUnit
ou: People

An OrganizationalUnit is an arbitrary container. You can call them what you want. Many companies put their addressbooks in an ou called People, but in this case, we're going to be using People for authorization, so we create another ou called addressbook. The two things to note are that an ou requires top (and therefore, once again, we could have left it out of this entry), and that you can call them whatever you want. They follow the format shown--once you declare the objectClass to be an OrganizationalUnit, you will need the ou: whatever entry, it's required by that objectClass.

An objectClass has several attributes. In this case, ou is an attribute of OrganizationalUnit. For an objectClass, there will be some required attributes (for example, surname or sn is required for person) and several optional attributes. The last thing to note is the format. It's entry, colon and a space after the colon. There should be a blank line above each dn: entry, otherwise, LDAP will think it's part of the previous entry. (This doesn't apply to the very first entry.)

Also, each line of any entry must start at the beginning of the line, otherwise, LDAP will consider it one line. In other words

objectClass: top
  objectClass: OrganizationalUnit

would cause an error, because LDAP would think that the second objectClass entry is part of the first one, reading it as

objectClass: top objectClass: OrganizationalUnit

TOC

Schema

As mentioned above, each objectClass has various attributes, some required, the majority optional. These are defined in the various schemas.

If you look at /etc/openldap/slapd.conf, the first uncommented lines have instructions to include various schema. On RH based systems, these live in /etc/openldap/schemas.

If you look at these files, you'll see that various objectClasses and attributes are listed. There are also various numeric identifiers, which is what OpenLDAP actually uses. The objectClass will have entries like SUP, MUST and MAY.

SUP indicates the parent. (Note that that a parent may have parents too. So, for example, inetOrgPerson has a SUP of organizationalPerson, which has a SUP of person.)

MUST is an attribute that is necessary. For example, Person, as mentioned, requires sn, surname. This won't be mentioned in the inetOrgPerson because it's implicit in the fact that inetOrgPerson's SUP's SUP requires it. That is, its SUP is organizationalPerson with a SUP of person, which is where the MUST sn is defined.

The MAY section gives a long list of attributes that can be used with inetOrgPerson, but don't have to be.

There's another section that will tell OpenLDAP to ignore case. This is also standard, and can be handy. In practice it means that objectClass, ObjectClass and objectclass are all treated the same way.

There's an excellent online schema viewer at ldap.akbkhome. You can click an objectclass or attribute and see what requires what.

TOC

Starting the Server

You might have noticed that we haven't yet started up the server. Now that we have a few things to add to the database, it's time.

You might remember we copied over DB_CONFIG.example to /var/lib/ldap. The entire /var/lib/ldap directory (or subdirectory if you chose to make one) has to be owned by user and group ldap. If not, the slapd daemon will fail to start on CentOS 5.4--this seems to have changed on 5.5, but the entire directory should still be owned by user and group ldap. If you do a ls -l on /var/lib/ldap, you'll see that root owns DB_CONFIG. Before starting the daemon, fix that.

chown -R ldap:ldap /var/lib/ldap

On RH based systems, though the daemon is called slapd, the script in /etc/init.d is called ldap. (This has changed in Fedora 12. Fedora 12's init scripts now call it slapd. If using Fedora 12 or later, use slapd as the service name, rather than ldap.) To start the program, on a system prior to Fedora 12

service ldap start

Make sure that it's actually running.

pgrep slapd

If you get back a PID number, it's running, otherwise, you will have to figure out what went wrong.

If it fails to start, check permissions on all files in /var/lib/ldap (as well as the directory itself.) We're assuming that you ran slaptest -u already and that everything checked out.

/var/log/messages may give some hints as to why it failed. However, at this early point, the odds are that it should start without a problem.

There should be a bunch of new files in /var/lib/ldap, most of them having a bdb suffix.

Assuming that you want the service to start each time the system boots, add it to startup services with
chkconfig ldap on

(Again, in Fedora 12 and later, use chkconfig slapd on).

TOC

Adding Entries to the Database

We'll add our little base.ldif file by using the command ldapadd. This command has a great many options, but we'll keep it fairly simple. In general, commands beginning with ldap (ldapadd, ldapdelete, etc.,) are used when the daemon is running. Commands beginning with slap (slapcat, slapadd, and so on) are generally used when the server is not running.

Assuming that you're in the directory where you created the base.ldif file, the command is

ldapadd -x -D 'cn=manager,dc=mycompany,dc=com' -f base.ldif -W

You will be asked for the password you created for ldap, that is the one that you put into slapd.conf. You should then see a message that it is adding the entries.

The -x indicates that you're using simple authentication, rather than SASL, the -D refers to the bind DN, that is the manager account that we originally created. The -W means ask for the password. You can also, rather than using -W, use -w and type out the password. For example, if the password you created is 1234pass, the command would be

ldapadd -x -D 'cn=manager,dc=mycompany,dc=com' -f base.ldif -w 1234pass

If you get an error message, it is probably because of a typo--as mentioned, the format is objectClass or attribute, colon, space and the entry. The first line for each entry should be the dn. There should be no white space at the beginning of each line, and each entry must be separated by at least one empty line from the entry above it as in our example, ou=group, blank line, then the next entry for the addressbook organizationlUnit. You can put a comment line above the dn line, but there should still be a blank line after the previous entry. For example

dn: ou=Group,dc=mycompany,dc=com
objectClass: top
objectClass: OrganizationalUnit
ou: Group

#Address book Unit
dn: ou=addressbook, dc=mycompany,dc=com

If you do get an error, double check your syntax and spacing. Trailing white space can also sometimes be a problem, and I've often fixed errors by simply deleting and retyping the offending entry.

As mentioned, OpenLDAP treats white space (an empty space or spaces) at the beginning of a line as meaning that the line is a continuation of the line above it. This would cause it to fail because each entry requires its own line.

Assuming it seems to have been entered, you can look at what you now have with the ldapsearch command.

ldapsearch -x -b 'dc=mycompany,dc=com' 'objectclass=*'

This is a handy command. In this case, we're looking at the entire dateabase. The -x means simple authentication, just as it does with ldapadd. The -b is for the base that we're searching and the objectclass=* means we want to see all object classes. If, later on, you just want to see who you have in your address book, you could change the -b entry to ou=addressbook,dc=mycompany,dc=com. If you only wanted to see which inetOrgPerson objectclasses you've defined, you could change the objectclass part. You can also search for something with a particular attribute instead. In that case, instead of objectclass=whatever, you could just put something like 'cn=john' to find anyone with a common name entry of John. (Remember, it's not case sensitive.) It should show you the various objectClasses that you've created.

TOC

Backing Up the Database

The simplest way to back up the database is to temporarily stop the server and use the slapcat command. This will dump the database to a file in a format to be used with slapcat's partner, slapadd.
service ldap stop
slapcat > slapcat.ldif

This file is human readable, but has various command specific entries, such as timestamps. If something happened and you had to replace the database, you would again stop the server. Remove the corrupted database. (Hopefully, there are no new needed entries since you last ran slapcat.)

service ldap stop
rm -rf /var/lib/ldap/*
slapadd -l slapcat.ldif

This should replace your database. (You might have to once again copy over /etc/openldap/DB_CONFIG.example to /var/lib/ldap/DB_CONFIG as well as once again run chmod -R ldap:ldap on /var/lib/ldap.)

This is a very simple scheme. It has the disadvantage of needing to stop the slapd daemon. I plan to add to this section as I learn more.

TOC

Editing Database Entries

Often, it's necessary to edit or delete an entry. Deleting is relatively easy as long as you know the object's DN which can be found with ldapsearch. For example, to delete John Smith in people
ldapdelete -x -D 'cn=manager,dc=company,dc=com' 'uid=jmith,ou=People,dc=company,dc=com' -W

To modify an entry, one has to create a new ldif file. One can either use ldapadd or ldapmodify in this case, either one works. The file should look something like (This example is taken almost directly from the ldapmodify man page)

dn: cn=John Smith,ou=addressbook,dc=mycompany,dc=com
changeType: modify 
replace: mail
mail: newemail@mycompany.com
- 
changetype: modify
delete: cn
cn: Smitty

Different modifications should be separated by a single hyphen as shown above. Judging from the man page, you shouldn't always have to use changeType: modify in each entry, but I've found that if I leave it out in something like the previous example, it doesn't work properly. One can also use add. Using the previous example

dn: cn=John Smith,ou=addressbook,dc=mycompany,dc=com
changeType: modify 
replace: mail
mail: newemail@mycompany.com
- 
changeType: modify
delete: cn
cn: Smitty
-
changeType: modify
add: cn
cn: Johnny

The basic types are add, replace and delete.

TOC

Adding Entries to the Address Book

One of the more common uses of OpenLDAP is to create a company address book and/or white pages. There are various objectClasses that can be used for this--inetOrgPerson is a popular choice, as it easily lends itself to an entry for either one.

If you take a look at the online schema viewer mentioned earlier, you'll see that inetOrgPerson is below top, person, and organizationalPerson. From Person, it inherits two necessary attributes, cn and sn for common name and surname. This means that you could get by with an ldif entry which was as simple as (don't use this, it's missing a couple of things we'll need)


dn: cn=John Smith,dc=mycompany,dc=com
objectClass: inetOrgPerson
cn: John Smith
sn: Smith

However, we need to make this a little more detailed. Firstly, we're putting John Smith into the addressbook ou. This way, if we need another entry for John Smith, in a different ou, it's not a problem. They will be two different dn's. This one will have the dn of cn=John Smith, ou=addressbook,dc=company,dc=com. We might also want him to go in a new ou called sales, but keep the two entries separated. (For example, perhaps the sales ou will be used for various methods of authentication, while the addressbook will only be used for a company address book.) As this is an address book, we want his email address. The mail attribute is one of the many optional attributes that may be given to the objectClass inetOrgPerson, but don't have to be. In other words, defined with MAY in the schema.

There are several others, as you'll see if you looked at the schema link mentioned above, such as uid, userPassword, pager, telephoneNumber, etc.

Lastly, we can use several cn entries, so that people can find him by a search for John, Johnny, Smith, Smitty, and if we want, tall guy. For example

dn: cn=John Smith, ou=addressbook,dc=mycompany,dc=com
objectClass: inetOrgPerson
cn: John Smith
cn: Smitty
cn: Johnny
cn: tall guy
sn: Smith
mail: jsmith@mycompany.com

There are several points covered here. As you've noticed, the cn attribute can have spaces. We didn't have to use the cn as part of the dn--we could have used another attribute, such as mail. We could have used cn=tall guy, or any of the other cn entries that we've specified.

Whatever entry we use in the distinguished name, the top dn entry, MUST be defined in the entry itself. That is, if I'm using the common name of John Smith, then I must have a cn: John Smith in the entry. Otherwise, when trying to add it, I would get an invalid syntax error and it wouldn't be added successfully. I can have other common name attributes, but whichever one I choose for the dn must be specifically mentioned. It isn't case sensitive--had I typed in either the dn entry or the entry giving the cn, something like johN smIth it would still work. I chose the cn of John Smith for the dn entry because that's one way it's often done and it also makes it easier in case you later need to add John Doe. If you used the cn John for the first entry, then hire another person with the first name of John, you'll have to distinguish its, errm, distinguished name.

Many howtos will make a much longer entry for each person, including objectClass: top, objectClass: person, objectClass: organizationalPerson. As mentioned previously, these aren't necessary--if you've stated that the objectClass is inetOrgPerson, that means that the objectClasses which are inetOrgPerson's parents are also being used, in this case, top, Person and organizationalPerson. There's nothing wrong with having them in the entry, but not using them can save a lot of typing.

Once our ldif file is created with all our addresses, we can add it to the database by using the ldapadd command.

Just about every mail client around can use an ldap directory. As things stand, to use the one we've created, all you would need to tell your client is the LDAP server's name or IP address, and the basedn of dc=mycompany,dc=com. We haven't yet secured this (we cover this later on) nor does it use TLS. The bind dn entry that most mail clients require isn't needed in this case, because anyone can, at present, read our address book. Laster, when we cover ACL's you'll see how to make it so that only authorized users can read the address book, but at this point, it isn't necessary.

Even the textbased email client mutt can, with the aid of various other programs, use an LDAP address book. I cover that on my mutt page.

After setting up your client to use LDAP--aside from mutt, most of them will have entries in the help section, telling you how to set it up--you can try, if you used the above entry, doing a search for tall. You should get the jsmith@mycompany.com back.

TOC

Authenticating Against an LDAP Server

Another very common use of an LDAP server is for authentication. One can store the accounts on an LDAP server, then users can authenticate on a client machine with the LDAP account.

We have already created the People and Group ou. We'll use them for authentication. We'll also use the migration scripts. This can be distribution specific. On other distributions, such as ArchLinux, it will have to be done manually, but as stated at the beginning, this particular article is CentOS centric.

We already edited migration.ph, to replace padl with mycompany. In the /usr/share/openldap/migration directory, there are many migration scripts. Some howtos recommend using migrate_all, some recommend using migrate_all but editing some of the scripts. For our needs though, we only want specific users, so we're going to start by doing one user. We will create an ldif for the user as well as for the group, and add that into the database. Much of this is taken from the Linuxhomenetworking.com article which is referenced in the Links section.

The easiest way to do this is to add the user to the LDAP server using the normal tools of adduser and passwd. Then we can migrate the user information to an ldif file. After that is done, the user can be removed from the server's /etc/passwd if that's desired. (Don't do it right away though, we're going to use the Linuxhomenetworking.com's method of testing, which will assume the user exists on the server.)

We don't want conflicting user ID's. So, on the client, check /etc/password and /etc/group for the highest UID and make sure that the new user on the server has a higher one. For example, on the client, the following command will work

cat /etc/passwd|cut -d : -f 3 |sort -n

This will print out the contents of /etc/passwd (cat), then use the cut command, choosing a colon as field separator, and print the third field, which is the UID number. Then we pipe it to the sort command, using the -n option to sort it numerically.

On most Linux systems, the majority of UIDs are in the 500 range. So, to create the new test user on the server, choose a number above the highest number in /etc/passwd. If it's a small home system, 600 should be fine. (If you're in a corporate environment, it can be a different story, but the concept is the same.).

As RH based systems also create a group for each user, with the same UID, run the same command on /etc/group. Create a user on the server, we'll call it testuser. We'll use the -u flag to specify its UID.

adduser -u 600 testuser
passwd testuser
(In other words, the testuser account should get a password. Otherwise, once you go through the next step, the password field would be blank.

So, you should now, on the server, have a user called testuser with a UID of 600, as well as a new group called testuser with a GID of 600.

Next grab the relevant lines from /etc/passwd and /etc/group and put them in two files. (The Linuxhomenetworking site has a nice script to automate this. However, the idea is that you also understand what you're doing, so we'll use this somewhat kludgy method for the moment.)

grep testuser /etc/passwd > passwd.txt
grep testuser /etc/group > group.txt

The next group of commands need to be run as root or with root privilege. Even using tab completion, typing the entire path to the migration script commands can be a nuisance, so I usually temporarily add their directory to my $PATH environment variable.

PATH=$PATH:/usr/share/openldap/migration
migrate_passwd.pl passwd.txt passwd.ldif
migrate_group.pl group.txt group.ldif

The two migration scripts convert the password and group entries into ldif files. If you look at the ldif files you will see several objectClasses, including the ubiquitous top, as well as various attributes. You'll also see an encrypted entry for the password in the passwd.ldif.

In this case, I've never tested which, if any, of these objectClasses can be eliminated. Some cursory googling indicates that they may all be neeeded for things to work properly.

Add the entries to ldap with the ldapadd command, as described above.

ldapadd -x -D 'cn=manager,dc=mycompany,dc=com' -f passwd.ldif -W

Do the same with the group.ldif file.

You can use the migration scripts on the entire /etc/passwd and /etc/group files, deleting entries you don't want in ldap, such as root. As mentioned above, one site has a script to automate the process. At any rate, at this point, we now have a user and group, both called testuser, in the LDAP database. The user is in the People ou and the group, oddly enough is in the Group ou. This is why we named these two ou's as we did--this way they work with the migration scripts by default.

The other advantage of having testuser in the People ou is because this is one of the neat things about LDAP. You can have a user in People, and the same person in the addressbook, and a third version of the same account in say, radiususers. They're all separate things, so if you tell your radius server to look at the base of ou=radiususers, it won't even look at the People ou.

TOC

Configuring the Client for Authentication

Now we go to the client. I've only tested this on Fedora and CentOS clients. As you'll see shortly, RH based systems have a handy tool to make this a bit easier, I'm not sure about other systems. In addition, Fedora makes frequent changes. Sometimes they are documented, but if so, they are usually buried in the release notes. I try to keep mention them as I find out about them, but my use of LDAP with Fedora is fairly limited, using it as a client. There have been some changes with Fedora 14, so if using that or a later version of Fedora, there will be some changes, mentioned below.

The client will need most of the programs we installed on the server.

yum install openldap{,-clients,-devel,} nss_ldap

The easiest way to do this on RH based systems, as well as any other system that has a similar tool, is to use the automated tool. I haven't used the GUI one, I think it's called system-config-auth or something similar. There is also a curses based tool. (It uses ncurses, meaning it will give you a similar dialog to what you see during RH installations if you choose text mode.)

authconfig-tui

For those unfamilar with RH systems, a tui suffix indicates a text user interface. It will open up a textlike dialog box that can be navigated with arrow and tab keys, using the space bar to make a selection. (It tells you this at the bottom of the dialog, though it doesn't mention the arrow keys).

Note that the man page tells you authconfig-tui is deprecated and that one should either use the GUI or command line tools. It is limited when compared to the GUI system-config-authorization. One can only imagine that RH, seeing the success of Windows and Apple, is trying to imitate them. The result is that more and more, especially as it follows Fedora's (you're too stupid to log in as root) lead, that the text interfaces will become more and more limited. For example, one cannot set it to SHA512 encryption in authconfig-tui, one has to use system-config-authorization, which requires X to be running, which one doesn't necessarily want to do on a server. It's a pity, but it's the way most Linux distributions, seem to be going.

Choose Use LDAP, Use LDAP Authentication, and Local Authorization is sufficent. If you've gone with a more or less default installation, Use Shadow Passwords and Use MD5 Passwords are already checked. Leave them. If you uncheck them, you'll find that there is no more /etc/shadow, though there will be an /etc/shadow-, and that you'll be using DES encryption. (If you want SHA256 or SHA512 encryption, you will have to edit several files manually or use the gui system-config-authentication.)

Go to Next. It will give you the option of using TLS. Leave that unchecked. (We cover that below. Then put in the IP address of the server, for example 192.168.1.70, if that's the IP address of your LDAP server, and for basedn use dc=mycompany,dc=com.

This tool will automate editing of /etc/ldap.conf, /etc/openldap/ldap.conf, /etc/nsswitch.conf and /etc/pam.d/system-auth-ac. You can, if you prefer, edit all of this manually, but authconfig-tui is definitely a time saver.

As mentioned, on Fedora, things constantly change. Even on the theoretically conservative RH/CentOS systems, as mentioned, there have been undocumented changes, but Fedora is far worse about this.

In Fedora 14, rather than an /etc/ldap.conf, there is now an /etc/pamd_ldap.conf and /etc/nss_ldap.conf, with several duplicate options. Sometimes, an error message will make it clear which file has to be changed, other times, one has to guess.

For example, if you setup TLS, and then try authorizing, using the password from the LDAP server account, it will fail, but then, on the second attempt, work. To fix this, you have to take the commented tls_checkpeer yes in /etc/pam_ldap.conf (but not nss_ldap.conf, where the identical entry lies), uncomment it, and change it to no. Looking at logs gives the hint, as the error you'll see on the first attempt is
pam_ldap: ldap_starttls_s: Connect error
Once you do that, it will successfully authenticate on the first try.

There is also sss, which might become a factor. I haven't used sss at all. You can see the Fedora page for more information, but according to the man page for sssd-ldap
If you want to authenticate against an LDAP server then TLS/SSL is
required. sssd does not support authentication over an unencrypted
channel. If the LDAP server is used only as an identify provider, an
encrypted channel is not needed.

I'm not sure exactly how this works in practice, but at least one person on Fedora forums found that upgrading their Fedora and using the automated tools broke their unencrypted LDAP.

Therefore, one may want to go over these files once running the authconfig-tui tool.

The authconfig-tui tool may also make changes to /etc/pam.d/system-auth. If this does present a problem for your needs, look at the file and change any instances of pam_sss.so to pam_ldap.so. (Note the cautions and safety nets about editing pam.d below.)

For a little more information on this, you can look at this thread on Fedora forums. Note that apparently, this is not default behavior and didn't happen to me on F14.

I ran into another oddity with Fedora 14. I was using authentication with TLS. This worked for, example, having user john who had an account on the Fedora client, with a password of 1234 and also had a password on the LDAP server of 4567. I could use either password on the Fedora client. However, I couldn't ssh in as a different user--one who was showing up in getent passwd but didn't have an account on the Fedora client. Nor could I su to that user on the Fedora client. The user existed only on the server--I'd created a /home directory for them, etc. As mentioned, getent passwd showed the user.

In testing, I temporarily disabled TLS. At that point, everything worked. I then re-enabled TLS and everything continued to work. I am not sure what happened, and only mention this in case it helps someone with a similar problem.

Back to authconfig-tui, in /etc/ldap.conf and /etc/openldap/ldap.conf it will configure the entries for BASE, HOST, and URI. It will do the same in /etc/ldap.conf, but the words base, host, and uri are in lower case. I haven't figured out if editing that file is even necessary, but I let the tool do its thing. (Also, the bind_policy line mentioned below is in /etc/ldap.conf, not /etc/openldap.conf)

Note that once again, in Fedora 14 at least, /etc/ldap.conf has been done away with in favor of nss_ldap.conf and pam_ldap.conf. The authconfig-tui tool makes changes to those files now. Whether editing the URI manually or with authconfig-tui, if using a non-standard port--for example, Sun Directory Server 6, if the LDAP instance is not run by root, uses port 1389 rather than 389--the port is specified by servername:port, such as
URI     ldap://192.168.1.5:1389

One line that should be manually changed in /etc/ldap.conf (rather than /etc/openldap/ldap.conf) is is bind_policy. The default, hard, is commented out. In Fedora 14 the change should probably be made in nss_ldap.conf, but so far, I haven't tested which one (pam or nss) is really necessary, and have just changed both on the only Fedora machine I have that authenticates against LDAP. I change this in /etc/ldap.conf or in /etc/nss_ldap.conf and /etc/pam_ldap.conf in later Fedora versions, to
bind_policy soft

The reason for this is that if left at the default, and there is no LDAP server available, the client may time out, or at best, keep trying to reach an LDAP server. See the next few paragraphs about nsswitch.conf.

PLEASE NOTE: Some folks on the CentOS mailing list found that this didn't work for them. Instead, they added the following to /etc/ldap.conf. (Not sure where you would add it in Fedora at this point.)
nss_initgroups_ignoreusers root,ldap,named,avahi,haldaemon,dbus

In Fedora 13 this seems to be the default--that is, having bind_policy with a default of soft. However, there is one thing that doesn't seem to be getting changed in Fedora 13's /etc/openldap/ldap.conf. If you're going to use TLS or SSL, you need to have a line
TLS_REQCERT allow

In at least two installs of Fedora 13, this didn't get put in there after running authconfig-tui, and it was causing LDAP authentication with TLS (or SSL) to fail.

Fedora 14 seems to have gone back to the default of bind_policy hard.

The authconfig-tui program will also change a few lines in /etc/nsswitch.conf. That file has the lines

passwd:     files
shadow:     files
group:      files

It will change this to read

passwd:     files ldap
shadow:     files ldap
group:      files ldap

The word files refers to the local machine's /etc/passwd, /etc/shadow and /etc/group. This change is telling the machine to also check the LDAP server for passwords and groups. (For those unfamiliar with Linux authentication, if you look in /etc/passwd, there is no password there, there's just an x as placeholder. The encrypted password is actually stored in /etc/shadow.)

There is a bug in RH based systems. (This is why I suggest changing the bind_policy from hard to soft.) It was reported 5 years ago and hasn't been fixed yet. In theory, "files ldap" in nsswitch.conf means that it should check the local system files first and only then go to an ldap server. Although it does seem to do this, the bug is that if an LDAP server is not available, the system will hang, and possibly refuse to authenticate--if it does authenticate, it may take 20 minutes or more. This is one of those things which I don't quite understand--that is, why the bind_policy should even be necessary if it's told to check local files first, however, as many other articles on LDAP will mention, the documentation is often hard to find.

The odd thing about this, from my experience, is that as long as an LDAP server is available, everything is fine, irrespective of whether the account in question is on the LDAP server or not or whether bind_policy is set to hard or soft. That is, if I have user john, who is NOT on the ldap server, as long as the server is running and the local machine can reach it, john can log in normally. It doesn't have to use the LDAP server for authentication--it just seems to want to know there *is* an LDAP server, as if it needs reassurance.

Therefore, this can be a problem if the LDAP server is taken off the network. To repeat, the way to avoid this seems to be change the bind_policy line from hard to soft in /etc/ldap.conf. (Also to repeat, that isn't a typo, change /etc/ldap.conf, NOT /etc/openldap/ldap.conf.)

As for the edits to /etc/pam.d/authconfig, I haven't done them by hand. It adds several entries for ldap into the file. While I usually dislike letting automated tools do this sort of thing for me, in this case I take the easy way, and allow it to do so. For an idea of the type of edits you will otherwise do manually, you can look at the ArchLinux wiki article, listed in the Links.

The quick way to check if this is working is to, on the client, run the getent command. This is short for get entries. See if the "testuser" account shows up. Make sure you don't have an account testuser in the client's /etc/passwd.

grep testuser /etc/passwd

That should just return you to a command prompt. Now try getent which will, if this has been done right, also check the ldap server.

getent passwd

You should see testuser.

You should also, at this point, run the ldapsearch command mentioned above to make sure it can read the server.

ldapsearch -x -b 'dc=mycompany,dc=com' 'objectclass=*'

You should get back the same listing of all entries that you would get on the server.

One can either create a home directory for testuser or add another line to pam so that the directory will automatically be created. If adding a line to PAM, one howto recommends the following. At the end of the authconfig file you will see lines beginning with the word session such as

session     optional      pam_keyinit.so revoke
session     required      pam_limits.so
session     [success=1 default=ignore] pam_succeed_if.so service in
crond quiet use_uid
session     required      pam_unix.so
session     optional      pam_ldap.so

Above it add

session    required pam_mkhomedir.so

One howto recommends using pam_mkhomedir.so skel=/etc/skel umask=0022. I've found that if I do this, the first attempt at login fails, with a message that the home directory can't be created. However, the second attempt succeeds. (After the first attempt, if you look at /home on the client, you'll see that a directory called testuser has been created.) Also, if I use the umask=0022, I also find that the skel files don't get created, there is no .bashrc, .bash_profile, etc. If I leave out the skel=/etc/skel and just put umask=0022, then the session simply fails on the first attempt, succeeding on the second attempt. If I use the line as I have it, with no mention of umask or /etc/skel, a directory is created with the typical files imported from /etc/skel, including the dot files, that is, .bashrc and company.

The other way is to leave pam alone and create the home directory in advance. This is simple enough, as root, create a directory, giving it the usual home directory permissions of 700. (Owner can read, write, execute, but no one else can.) Copy over the dot files from /etc/skel. You'll get a few messages that some files are being omitted, which can safely be ignored.

cd /home
mkdir -m 700 testuser
cp /etc/skel/.* testuser/
chown -R testuser:testuser testuser

If testuser showed up in getent passwd, the chown should proceed without errors. If you get some sort of error like no such user, then something isn't working correctly, and it's not looking at the LDAP server for usernames.

Regardless of whether you create the home directory in advance or edited pam to let it be created on the fly, now test it. You might as well use the server, since you created an account called testuser as a prelude to adding testuser to LDAP.

From the server, log in as testuser. Then try to ssh to the client. As mentioned, if you use the umask line in pam, it will fail the first time, but succeed the second time. If you already created the home directory, the login should succeed on the first try.

TOC

Access Control

We now have a machine that provides an address book as well as allowing other Linux machines to use it for authentication. This is probably fine for your home.

However, many people only want authorized users to view an address book. Fortunately, OpenLDAP has access control.

If you look at /etc/openldap/slapd.conf, you'll see a section called Sample security restrictions. They show a few typical examples, all commented out. They also mention afterwards that as it stands, access is access to * by * read.

This means that anyone can use all lists. If one uses their example, (shown with the lines commented out, as it is in the default file--to use it, one would remove the # in front of each line)

#access to *
#       by self write
#       by users read
#       by anonymous auth

this will actually work for the address book. You can then use, as the bind dn in a mail client, the name of any authorized user in the LDAP database. However, as our addressbook ou doesn't, at present, have passwords, you'll have to pick someone from the People ou, where we have given them passwords.

There's another problem too. If you use this access list as it stands, the other thing we're doing, using the server for authorization, won't work. This is because if there are any rules specified, there is then an implied final rule of access to * by * none. So, when trying to use LDAP for authentication, what will happen is this--the client machine will want to read the server's database, but will be forbidden.

Access lists can be tricky and often not do what you want them to do. There are various privileges, write, read, search, compare, auth, and none. Those are listed in order from highest to lowest. If you have read, you automatically have search, compare and auth. If you have search, you can do auth and compare.

I'm not quite sure of the practical difference between search and compare. I think that with compare, you have to actually have the entry, and it can then be compared to the LDAP database--with search, you can look through the database for the entry you want. I've never needed to make use of such fine grained differences, so I can't give a better explanation.

The auth permission allows you to provide a bind dn and password to the database and then authorize yourself. However, it doesn't allow you to search the directory or read it. The best way to get a clear picture of this is to stop the ldap service, then run

slapd -d 128

This runs slapd in the foreground. The -d is for debug and we're using level 128 which covers access lists. If you look at man slapd.conf, it gives the different debugging levels and what they cover. Play around with your access lists a little, and watch why they don't do what you expect. For example, while it seems that anonymous auth should probably allow authentication from one machine to another, it turns out that it won't allow anonymous to read and get the password. This will really become much clearer if you play around with the various permissions and possibilities, and after each edit, run slapd -d 128 and watch what happens.

So, while I'm not entirely clear on Access Lists--the basic premise is straightforward, but they can surprise you--here is a group of rules that will give us what we're discussing here. Authenticating from client Linux machines will work and a user has to have a bind DN to see the address book.

access to dn.subtree="ou=People,dc=mycompany,dc=com"
        by self write
        by * read
        by anonymous auth


access to dn.subtree="ou=Group,dc=mycompany,dc=com"
        by self write
        by * read
        by anonymous auth

access to dn.subtree="ou=addressbook,dc=mycompany,dc=com"
        by self write
        by users read
        by anonymous auth

An access rule can be written on one line, but as a rule, it is broken up as shown above. The white space before all the "by" lines (no pun intended), is necessary. Remember, to OpenLDAP, white space at the beginning of a line indicates that the line is a continuation of the line above it. So a simple rule could be written as

access to *
        by self write

or as

access to * by self write 

Note that we have to allow all to read Group and People for using the server for authentication. When we get to the address book, we can tighten it up a litte, since you are connecting with a bind DN.

Access rules are read top to bottom, with the first match being used. In this case, if accessing the address book, the system would pass over the first two subtrees and then get to the address book.

I'm not sure that "subtree" is necessary or should even be used with ou's, but that's the only way this has worked properly for me.

One common use of ACL's is to allow a user to change their own password. This usually takes a separate rule--remember, first match wins, so put it above anything that blocks users from writing. In this example, we'll use the ou of People.

access to dn.subtree="ou=People,dc=mycompany,dc=com"
  attr=userPassword
  by self write
  by * auth

If using access lists, often, to use it with phpldadpmin, one may also need

access to dn="cn=subschema"
        by * read

This is another one where I haven't quite figured it out. Using it doesn't open up everything to reading by everybody. It will, however, allow phpldapadmin to work with your database, whereas sometimes, without it, depending upon the nature of the ACLs, phpldapadmin won't be able to access the database. Depending upon the nature of your ACLs, you may have to change some lines in phpldapadmin's configuration file as well. This is covered in the phpldapadmin section below.

Also, if you use access control, the ldapsearch command given above won't work. We have used

ldapsearch -x -b 'dc=mycompany,dc=com' 'objectclass=*'  

It will probably seem as if it's working, but come back with 0 entries. We need to now use the bind DN.

ldapsearch -x -D 'cn=manager,dc=mycompany,dc=com' -b
'dc=mycompany,dc=com' 'objectclass=*' -W
As explained earlier, the -D indicates using the bind DN and the -W means ask for the password. As in ldapadd, one can also use a lowercase -w and just type the password at the end.

TOC

TLS and SSL Security

So far, we've been doing things in plain text. If you run tcpdump while performing LDAP authentication, using the -A option, you'll be able to read most of what's going on. This is seldom desireable. LDAP can use either TLS or SSL. Though the procedure is similar, there are various differences. The official view seems to be that SSL has been deprecated in favor of TLS. The interested reader can do more research on their own, but to greatly over simplify matters, TLS makes contact in clear text, then begins to encrypt the transaction. It runs over the same port, 389 by default, that OpenLDAP uses for normal plain text transactions.

SSL encrypts the entire transaction, and uses its own port, 636 by default. For purposes of this article, it's enough to understand that you're either using one or the other. To further confuse matters, the terms are often used interchangeably, and most applications that use one or the other can use either. TSL is based on SSL.

Regardless of which you use, the server must have a certificate, and clients have to have a copy of part of it. There are various ways to do this. One can order a certificate from a certificate authority such as Verisign. These are expensive, often $200 a year or, for Verisign, much more. One can use CAcert, which gives free signed certificates, but may not be recognized as such by many programs. The most common and quickest way for testing is to use self-signed certs, which will make Firefox balk, and the latest Thunderbird betas to simply not work without various configuration changes. (At time of writing, I'm not sure if they're considering it a bug that will be fixed or a good thing that won't be fixed though no other mail client has this trouble.)

We're going to cover self-signed certificates here, because the chances are, if you're reading this article, you just want it for your home network.

CentOS actually provides a certificate for you if you install LDAP. However, you might want to create your own anyway, to make sure that you get your hostname correct, (VERY important) and customize a few more details. The procedure is to create a self-signed certificate, put it where OpenLDAP can find it, and give clients the certificate part of it.

The included default certificate lists hostname as localhost.localdomain, so could only be used if you're using your own machine as both server and client. The CentOS (and possibly any distribution's default certificate--look at your slapd.conf to see), is kept at /etc/pki/tls/certs/slapd.pem. To view its contents in readable format you can use the command
openssl x509 -in /etc/pki/tls/certs/slapd.pem -noout -text

Unless you're just beginning, and doing all this on one machine--that is, with both server and client on the localhost, this certificate won't work, so we'll create our own.

I like to make a directory called cert, and work in there. This keeps the various files we'll create in one place.

So, first we create the certificate with the openssl req command.
openssl req -newkey rsa:1024 -x509 -nodes -out myserver.pem -keyout
server.pem -days 1025

That should be on one line. One can look at man(1) openssl and man(1) req for more information, but basically, what we've done is this.

The openssl command has many sub commands, req being one of them. The req command is for requesting a certificate. The newkey creates a certificate request and a new private key. The rsa:1024 means use rsa and 1024 is the number of bits wanted.

If you are creating this as non-root user (and there's no reason to be root to actually create the certificate) you might get an error message like "unable to write 'random state'. This will be caused by a file created, called .rnd, usually in the user's home directory--for example, if you run the command in /home/john/certs the .rnd file might be in /home/john. If you get that error, find the .rnd file and change its permissions so that the user running the command is allowed to write to it. I'm not sure how this varies between distributions--but I've only gotten the error on CentOS when generating the certificate as non root user.

The x509 generates a self-signed certificate. Using -nodes means don't ask for a password--if a password were generated, it would be requested each time the server started.

The -out and -keyout are both giving the argument of myserver.pem. In some cases, one uses two different files. (The example in man(1) req does it that way.) However, we are going to want one .pem file with both the key and the certificate in it.

There are other ways of doing this. Some tutorials have you generate a key, request and sign the certificate in several steps, but this takes care of the entire process with one command.

Another common way to do it is to make a separate file for the certificate and the key. Then one moves both files in /etc/openldap/cacerts. If doing it that way, the command would read -keyout key.pem and -out cert.pem (or something similar. Keep in mind my comment about adding the server name to the file.) Either method is good--the only changes from the instructions I'm giving are that in slapd.conf one would point the KeyFile line to the key file and rather than create a new pem file to give clients (as I'll detail below), one just gives them the cert.pem file.

Once you run the command, you'll see it generate the key and say it's writing it to myserver.pem. (You can call the file whatever you wish, but it should have a .pem extension.) I find it helpful to name the pem file after the server, since there may be more than one pem file in /etc/openldap/cacerts. In this case, we'll say the server's name is myserver.mycompany.com. After this, a dialog will begin, asking various information. You can input whatever you wish, the important thing is the server name. It will ask for country, (as in US), state or province, locality, organization, organizational unit, common name, (with an explanation saying e.g., your name or the server name), and email address. The important thing is to give it a servername. For example, if you just hit enter to accept defaults, save for the common name section, putting in myserver.mycompany.com, and then run the aforementioned openssl x509 -in myserver.pem -noout -text, you will see
  DirName:/C=GB/ST=Berkshire/L=Newbury/O=My Company
Ltd/CN=myserver.mycompany.com

There's a lot more to the file, but that is the part we're trying to see. (One assumes the program's creator lives in Berkshire.)

The myserver.pem file can be viewed with any text editor and will look soemthing like
-----BEGIN RSA PRIVATE KEY-----
MIICXwIBAAKBgQDK83j4ewhELIEfzeiGJP5sUxGy6PLmqkEy/slh4z7fXYoA4Yj0
kZnPGI9Oo1oVHmHmo4ADnOcDbwAZt6TL929WHGtFqLPM6LL9kMNcXyX5lNVzF6T7
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIDKzCCApSgAwIBAgIJAMaAFDJxIC9oMA0GCSqGSIb3DQEBBQUAMG0xCzAJBgNV
BAYTAkdCMRIwEAYDVQQIEwlCZXJrc2hpcmUxEDAOBgNVBAcTB05ld2J1cnkxFzAV
itfke9aK2MwqYbUEpEJQ0Tqv99DHOvrvVH0=
-----END CERTIFICATE-----

It will have far more lines than that, but you get the idea. Copy the myserver.pem file to /etc/openldap/cacerts.

Each client will need a copy of the certificate part of the file. As mentioned, many applications will (rightfully) complain that it's a self-signed certificate, but for testing or internal use, it doesn't matter. Create a client pem file. I name them after the server, as a client may have more than one server's pem file, with a cl in front of the name to indicate that it's a client.pem
grep -A 50 CERT myserver.pem > myservercl.pem

The -A argument, as can be seen from man(1) grep, prints the number of lines, in this case 50, that follow the selected pattern. So, we're selecting the pattern CERT and the next 50 lines. That should select the line beginning with ---BEGIN CERTIFICATE--- and the rest of the file. (Even with a 2048 bit key, that is, running the original command with rsa:2048 instead of 1024, there are usually only 25 lines or so.) Make sure you have gotten the entire certificate part--the myservercl.pem file is also viewable with any text editor and should start with the --BEGIN CERTIFICATE--line and end with the ---END CERTIFICATE-- line. If for some reason, it didn't get that whole section, run the command again, using -A 100. (I've never known that to happen, but one never knows.)

Copy the myservercl.pem file to your Linux clients. We'll deal with them shortly, but there are a few things still to be done on the server.

Edit /etc/openldap/slapd.conf. With CentOS 5.3, you'll see the following lines, commented out with a #, in the original file.
# TLSCACertificateFile /etc/pki/tls/certs/ca-bundle.crt
# TLSCertificateFile /etc/pki/tls/certs/slapd.pem
# TLSCertificateKeyFile /etc/pki/tls/certs/slapd.pem

We'll uncomment them and point them to our myserver.pem file
 TLSCACertificateFile /etc/openldap/cacerts/myserver.pem
 TLSCertificateFile /etc/openldap/cacerts/myserver.pem
 TLSCertificateKeyFile /etc/openldap/cacerts/myserver.pem

(Make sure that you also remove the leading white space after removing the # sign--otherwise, it will be read as a continuation of the previous line.) This file has to be readable by group ldap,
chmod 640 /etc/openldap/cacerts/myserver.pem
chown :ldap /etc/openldap/cacerts/myserver.pem

We've changed the permissions to having the owner be able to read and write the file and the group to be able to read it. (We don't want this readable by anyone else, and we don't want anyone but root and ldap to read it.) We then changed the group ownership to the ldap group. (Doing chmod :groupname just changes the group, leaving the ownership, in this case root, alone.)

Make sure it's been done properly. Doing ls -l /etc/openldap/cacerts/myserver.pem should show
-rw-r----- 1 root ldap

as the permissions. It can also be owned by ldap. The thing to worry about is if you moved it over using the sudo command, the owner might still be the user, rather than root or ldap. The group ownership, however, must be ldap--if it's root, then when trying to start slapd, it will fail, saying ldap is unable to read it.

As mentioned in the introduction, CentOS 5.5 now also requires the line in /etc/sysconfig/ldap, that reads SLAPD_LDAPS=no to be changed to read yes.

Try restarting ldap to make sure it is working properly.
service ldap restart

If it fails, it can be useful to run the slapd daemon from command line with -d 256 (for debugging level).
/usr/sbin/slapd -d 256

However, we'll assume that it started successfully. One way to check is from any other machine that has the openssl command (including OS X), run the command openssl s_client. Assume your LDAP server's IP address is 192.168.1.50, from another machine run
openssl s_client -connect 192.168.1.50:636

You should see a lot of information (that will probably scroll off your terminal) including the certificate. It should end with
Verify return code: 18 (self signed certificate)

Now, onto to the client. This is pretty easy. Copy over the myservercl.pem file to the client's /etc/openldap/cacerts file. That is the file we created that should just begin and end with the CERTIFICATE lines (including the lines saying BEGIN CERTIFICATE and END CERTIFICATE).

Now, once again, run authconfig-tui, but this time choose to use TLS. This will add a line of ssl start_tls to /etc/openldap.conf.

One can use SSL instead. In that case, do NOT check off TLS, as it will start SSL twice, and cause issues. Instead, put ldaps:// in front of the server address. That is, at present, the server line in authconfig-tui would read something like ldap://myserver.mycompany.com. (You didn't have to write in the ldap:// when you specified the server, authconfig-tui does that for you. You won't see it when you first configure it, but if you go back and look--now, for instance, when you're reconfiguring it--you'll see that ldap:// has been put in front of the server name.

If using TLS, do NOT change ldap to ldaps--again, it will start SSL twice and cause issues. As mentioned, TLS will work on port 389.

If for some reason, you are using a non-default port, that gets specified in the server line. For example, if using port 1389
ldap://myserver.mycompany.com:1389

After this, if using LDAP as, for example, an address book, you can choose to either use SSL or TLS. It should handle both. Doing netstat -a |grep ldap should show both ldap and ldaps. (Assuming that you've made the proper change in /etc/sysconfig/ldap).

This is irrespective of whether your *client* is using TLS or SSL. Once you've put in the certificate and uncommented the lines in the server's slapd.conf, both should be available.

Note, as mentioned, that many mail clients (and browsers) will balk at your self-signed certificate. However, just about all mail clients enable you to add an exception.

Note that RHEL6.x (from the bug, as of 6.1), has apparently introduced a new bug in ldap. I haven't run into this, not yet having upgraded to RH, SL, CentOS or OLE 6.x, but the issue seems to be that one has to be careful about having an empty cert directory. I'm not sure if this also causes failure if you're not using TLS or SSL. At any rate, be aware of it.

Thunderbird 3, in a decision that was either excellent to help security, or moronic in making things more difficult, makes this a bit complicated. A bug was filed, but it's considered a feature.

To import a self-signed certificate in Thunderbird, one goes to Options, Advanced and chooses Certificates. Click View Certificates, then choose Servers. Choose Add Exception and you will see a textbox with https:// in it. Put in the server address and the port, usually 636. So, if your LDAP server is at 192.168.1.50, the textbox should read
https://192.168.1.50:636

It should then fetch the certificate, give you some dire warnings, and allow you to add the exception.

At times, on Linux machines, it will say that it can't get any information. If that's the case, go through the procedure of adding an LDAP server, (the Thunderbird user is hopefully familiar with that, it's beyond the scope of this article), check the box for SSL, put in the bind dn, then click the offline tab. Try to download the information for offline use. It will fail, telling you the certificate is invalid. Then, once again, go through the procedure of adding an exception, and this time, when you type in the address with the https:// prefix, it should fetch the certificate. In my experience (I didn't investigate this thoroughly), simply importing the client.pem doesn't work--the only way to get it to work is to go through the abovementioned procedure to add the exception.

TOC

Using phpldapadmin with OpenLDAP

There are many graphic LDAP management tools. My personal favorite is phpldapadmin. It's pretty easy to setup and use, but has a few little quirks, at least with CentOS.

At times, it has been available through the EPEL testing repo but that doesn't seem to be the case at time of writing. It can be found at fr.rpmfind.net. Download the rpm. it requires /bin/sh, hpttd, php and php-ldap. One can either install it with rpm -ivh or with yum localinstall. At time of writing, the package is 1.0.1-1.el5.noarch.rpm so the command would be
rpm -ivh phpldapadmin-1.0.1-1.el5.noarch.rpm

or with yum
yum localinstall --nogpgcheck phpldapadmin-1.0.1-1.el5.noarch.rpm

It puts a directory, phpldapadmin, in /etc. In there, you'll find a file called config.php.example. Copy it to config.php.

There's only a few lines that have to be changed. If you've left access control in slapd.conf alone, all that has to be changed is the line

#ldapservers->SetValue($i,'login','dn','cn=Manager,dc=example,d c=com');

to your own domain. If you haven't gone with the default of manager as the LDAP administrative user name, change that as well. (Also, remove the comment sign.) So, using our mycompany.com, it would be changed to

ldapservers->SetValue($i,'login','dn','cn=Manager,dc=mycompany,dc=com');

Assuming that you don't wish anyone to be able to browse and use it, change the line (commented, in this case, with //)

// $ldapservers->SetValue($i,'login','anon_bind',true);

to make anonymous reading false.

$ldapservers->SetValue($i,'login','anon_bind',false);
(Note we've removed the // comment marks).

By default, it only allows you to view it with a web browser on the localhost. If you wish to administer it from other machines, edit the file /etc/httpd/conf.d/phpldapadmin.conf. It has a line reading

Allow from 127.0.0.1

You can change that to allow from other specific hosts or from any host in the network. For example, if your network is the typical 192.168.1.0/24 network (with a netmask of 255.255.255.0), change that line to read

Allow from 192.168.1

With some access list configurations, you might get a message, after logging in, similar to

Could not determine the root of your LDAP tree.
It appears that the LDAP server has been configured to not reveal its
root.
Please specify it in config.php

In config.php, the lines in question read, by default

/* Array of base DNs of your LDAP server. Leave this blank to have
 * phpLDAPadmin
auto-detect it for you. */
// $ldapservers->SetValue($i,'server','base',array(''));

If you get that error about not revealing root, then change that to read (once again, removing the // comment

$ldapservers->SetValue($i,'server','base',array('dc=mycompany,dc=com'));

As mentioned in the ACL section, you may also have to add an access list rule of

access to dn="cn=subschema"
by * read

for phpldapadmin to work properly.

TOC

Exporting a Thunderbird Address Book

If you use Thunderbird, you've probably accumulated a bunch of addresses. If you want to share them on a network, it's relatively easy to export it and put it into an OpenLDAP database. (As Thunderbird can import from either Outlook or OutlookExpress, this can also be used to convert an address book from either of those two programs as well. Import it into Thunderbird, then export it.)

Mozilla has its own schema for addressbooks, however, it can cause issues with existing schemas. For example, while they may use the objectClass of inetOrgPerson, their schema doesn't require a surname whereas OpenLDAP's inetOrgPerson does.

First, to avoid aggravation, it's probably a good idea to export Thunderbird's address book as a csv. Click on the address book icon, and when it opens, go to Tools. You'll see an option to export it. The default is to save it as an ldif file, however, if you click the dropdown arrow you should also have an option to save it as Comma Separated, which is a csv file. Do so.

Open that file in any spreadsheet program, e.g., OpenOffice, Gnumeric or Excel if you're on Windows, and make sure that every entry has something in the Last Name column. For example, if you have a mailing list in your address book, it probably has no entry in that column. For every entry that is missing a Last Name entry, to use it with LDAP, you'll have to fill something in. A single digit, such as 0, will do, but it has to have something in the Last Name column, as we will require the sn (surname) attribute. As you have to open each entry in Thunderbird's address book to add the Last Name, if there are more than a few, you might find it easier to make the entries on the spreadsheet, then import it back into Thunderbird.

Once this is done, you can export the address book again, this time in its default ldif form. A typical entry will look like

dn: cn=John Smith,mail=jsmith@mycompany.com
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
objectclass: mozillaAbPersonAlpha
givenName: John
sn: Smith
cn: John Smith
mail: jsmith@mycompany.com
modifytimestamp: 0Z

We're going to make a few changes. For example, if trying to import that entry into OpenLDAP, you would get errors about the mozillaAbPersonAlpha and modifytimestamp entries. Also, we want this in our organizationalUnit, so we're going to change the dn as well. We'll assume that you're going to use the addressbook ou that we created earlier. Backup the file first, in case you make mistakes. We're going to use sed with the -i, for in place, option. (The other method would be to use sed and write the output to a new file, but once you're comfortable with this, using the -i option is easier.)

cp tbird.ldif tbird.ldif.bak

This is assuming that we called the exported addresses tbird.ldif. I either use .bak or .orig for copies, you can, of course, call it what you want.

Now we'll use sed to delete the lines for mozillaAbPersonAlpha and the timestamp.

sed -i '/mozilla/d;/timestamp/d' tbird.ldif

For those unfamiliar with sed, it can do many things, including deleting and substituting lines in a file. In this case, as explained, the -i means in place, rather than outputting to the terminal or another file. The /mozilla/d and /timestamp/d means that it's deleting lines that contain the string of mozilla and the string timestamp. The semicolon is one way to have it run two separate commands at one time. We could probably also delete the top, person and organizationalPerson lines if we wanted, as they're all implied by the objectClass of inetOrgPerson.

Next, we'll change the dn from mail=whatever to our addressbook ou.

sed -i 's/mail=.*/ou=addressbook,dc=mycompany,dc=com/' tbird.ldif

The s/ means to substitute. In this case, we're substituting the mail=<email_address> with our ou and domain. Note the period before the *, it's necessary for this to work correctly. (This is because of the nature of regular expressions in Linux, a whole subject in itself.)

Our entry should now look like

dn: cn=John Smith,ou=addressbook,dc=company,dc=com
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
givenName: John
sn: Smith
cn: John Smith
mail: jsmith@mycompany.com

There's one last thing before we import the file. We want to make sure there are no duplicate DN's, as that will also cause the import to fail. I've found that the easiest way to do this is

grep dn tbird.ldif > dn.txt
uniq -di dn.txt

The first command will print out all the lines containing dn and put them in a file called dn.txt. The uniq command goes through the file. The -d flag means print only duplicates and the -i means ignore case. Remember, LDAP ignores upper and lower case when looking through the database, so if there is a duplicate dn, including a john smith and John Smith, the import will fail.

If you find any duplicate DN's, edit them. Perhaps you have a jsmith@mycompany.com and also john.smith@hishomeemail.com, but both have the common name (cn) of John Smith. If it prints out a duplicate. edit the file and change one of them. For example, make the home address line

dn: cn=John Smith (Home),ou=addressbook,dc=mycompany,dc=com

If you do this, you also have to change the cn line in the entry to match the dn. So change the cn=John Smith to

cn=John Smith (Home)
Now, with luck the import will succeed.  
ldapadd -x -D 'cn=manager,dc=mycompany,dc=com' -f tbird.ldif -W

After giving your password, you should see that it is being imported. It will list each entry as it imports it.

If it fails at a particular point, note which name was the problem. Then you'll have to figure out the problem and fix it. The most common issues that I've seen are simple mistakes when editing the file, either missing a surname or common name, or perhaps, changing the dn and forgetting to change the cn entry to match.

Assuming it didn't have an error with one of the first few names, the easiest thing to do is take the tbird.ldif file and delete all entries up to the mistake that you've fixed. In other words, if it errored out at adding John Smith, open the file, delete all the entries prior to John Smith, fix the John Smith entry and try again. The reason for this is that if you simply try to add the file again, it will error out at the first name, saying it already exists.

Hopefully, after the fix, the rest of the names will be successfully imported.

Links

These are various links that I've found quite useful.
From linuxhomenetworking.com, there's an article on using LDAP for authentication. It's a bit dated by now, and I wouldn't use the migrate_all script that they suggest, but it's a very clearly written introduction.
The linsec.ca site has an article on using LDAP for authentication, as well as a another article about using LDAP as an address book.
grennan.com has a mini howto.
The defindit.com site also has a howto.
O'Reilly's ONLamp.com has an article on using LDAP as an address book.
The server-world.info site has a brief article. It only gives the steps, without explanation, but is a good, RH based system specific, quickstart guide.
The ArchLinux wiki has an article on using OpenLDAP for authentication.
The LinuxQuestions blog section has an article by member anonmie, containing several perl scripts for some LDAP related tasks.
Lastly the good people at Zytrax, who have several excellent online documents, have an LDAP for Rocket Scientists online guide.

TOC