The Basics of OpenLDAP

When starting a business, or any general login service, LDAP is something that is often thought of but rarely really understood. OpenLDAP installation and configuration can be at best confusing and at worst frustrating and difficult to understand. With the next set of tutorials I hope to dispell some of the information about ldap that can make it difficult, help in the setup of OpenLDAP and getting it populated with meaningful data, how to take advantage of the data, and finally configuring different backends for OpenLDAP. As in my earlier tutorials, I’m going to assume that you are using some kind of Debian flavor Linux.

Why might I want to set up OpenLDAP?

There are a number of reasons to toss together an OpenLDAP server on Linux, the most important of which is unified logins that can be used for either internal or external tools (or both). Also, it offers a certain amount of safety in having a common place to limit user access when you lose an employee. You can set it up at home to manage various aspects of a multi-computer/user system. It has other uses, as I’ve seen various aspects of LDAP/HDB used for location matching in processing. Understanding how to use and configuration LDAP from scratch, though, with little information other than a few books and some limited documentation with straight forward how to’s without explaining what you are doing can leave you with a really daunting process for building out your data.

Installing and Running slapd

slapd, maintained at OpenLDAP.org, is the LDAP option of choice outside of AD. In Ubuntu 12.04 (and probably several versions back), getting it installed is as easy as running apt-get on the package slapd. Installing LDAP itself should be a relatively painless process, so I won’t be spending a lot of time on it. Debian Linux of various versions are easy and cheap to support, and if your business doesn’t allow/support it, I would suggest finding a new location to work.

When you do the new installation, it will ask you for a number of items. These include a hostname that it will use for the root object (example.org is common, I used home.paulsalcido.local), an administrative password, the backend database, and a few other things about creating directories for the database, etc. Keep a record of what you put in these options because they are key for later configuration of the system.

Default Configuration

When you installed slapd, a new directory was created under /etc/ called ldap. The default directory structure for this looks something like the following:

  • ldap
    • ldap.conf: the primary ldap client configuration file.
    • sasl2: Directory containing information for Simple Authentication Security Layer, empty.
    • slapd.d: The primary definition directory for how slapd actually works. Think of it like the data dictionary of a database.
      • cn=config.ldif: The very top level configuration for the running ldap server, sets log levels and run directories for pid files.
      • cn=config: directory for more specific configuration options for ldap.
        • cn=module{0}.ldif: Contains configuration for loading modules; the module directory path and module load directives.
        • cn=schema.ldif: The root object for schema loading under the directory cn=schema.
        • olcBackend={0}hdb.ldif: Sets up the back end for LDAP to be HDB, which is generally enough for small implementations.
        • olcDatabase={0}config.ldif: Sets up authorization access levels to the LDAP ‘config’ database.
        • olcDatabase={-1}frontend.ldif: Sets up authorization, access and settings for the ‘frontend’ database.
        • olcDatabase={1}hdb.ldif: Sets up authorization, access and settings for the ‘hdb’ database.
        • cn=schema: Directory containing additional schema directives, basically data definitions for various types, there should be several files under this, take a look at them for more information about the types.
    • schema: This contains a number of useful schema objects that can be added to the data dictionary (cn=schema) above. Many have requirements to load other schema objects before use, so read them carefully before adding them to your database.

Reading the olcDatabase={1}hdb.ldif file explains the location of the hdb database as being /var/lib/ldap. /var/ is the location of varying file data sizes, and so it’s typical that it would go here (for those with little knowledge of the Linux/Unix directory system). Going there will expose a lot of HDB data. HDB is a very fast data store with in memory indexing (the process builds indexes at startup time). So it’s great for services, but make sure that if you aren’t running a service and decide to use HDB, that you account for index creation in the process start up. LDAP also needs to account for this indexing, making restarts of very large databases problematic, but then again, you shouldn’t be restarting primary services all that much.

Using the default LDAP install

Now that you have a very basic understanding about how slapd knows what it’s doing, we need to start building an understanding of how to use LDAP. LDAP is a directory structure with nodes that can be used for authentication, certainly (and this is the most recognized use of LDAP, I’m guessing), but different LDAP clients (including any you might develop yourself), can have a multitude of uses for LDAP that haven’t even been considered, like service lists, host lists, computer directories, etc.

LDAP is a database of objects placed under organizational units (directories, kind of). Most clients need to be able to select from this data, and that’s what we’re going to start doing with a few utilities, followed by adding things to the directories. First, we need to run an installation to get the package ldap-utils. This contains a series of utility functions, such as ldapadd, ldapsearch, ldapdelete, ldapmodify, etc. (In bash shell, you can type in ldap then hit tab, and it will give you a list of commands that start with ldap). Before we search, we need to add some objects to the directory structure, because it really doesn’t know much yet. We are going to do this the hard way first, then apply the easy way later.

Before we move much further, we need to discuss the concept of ‘binding’. Binding is the term that is used for authentication. We select who we are binding as, and it will request a password to authenticate the user, thus authorizing the client to act as the user. You will often see the term “bind dn”, or something similar, which basically means the person that you want to login as.

In the earlier step, where you input the hostname for the ldap root, when I put in home.paulsalcido.local, the admin DN is “cn=Admin,dc=home,dc=paulsalcido,dc=local”. In the case of the use of example.org, the admin dn would be “cn=Admin,dc=example,dc=org”. This user can be used for actual modification, with the admin password.

By the default configuration, it is possible to use the blank bind dn, or no user, for read only access to the LDAP server. This is not optimal, and future configuration explanations will aid in the understanding of how the entire system can be configured, but we’re worrying about use because it will bring understanding of storage and modifications for future activities. First, we want to view all objects that are in the database, and I’ll do a quick explanation of what everything means as I go. The following ldapsearch command will return everything in a database (so be very careful with it).

$ ldapsearch -D "" -b "dc=home,dc=paulsalcido,dc=local" "objectclass=*"
# extended LDIF
#
# LDAPv3
# base  with scope subtree
# filter: objectclass=*
# requesting: ALL
#

# home.paulsalcido.local
dn: dc=home,dc=paulsalcido,dc=local
objectClass: top
objectClass: dcObject
objectClass: organization
o: Paul Salcido Home Org
dc: home

# admin, home.paulsalcido.local
dn: cn=admin,dc=home,dc=paulsalcido,dc=local
objectClass: simpleSecurityObject
objectClass: organizationalRole
cn: admin
description: LDAP administrator

# search result
search: 2
result: 0 Success

# numResponses: 3
# numEntries: 2

You must include the base object (-b) to get anything. If you are working with an AD LDAP implementation, this is likely to be related to your work computer hostname. For instance, if you computer hostname is “paulsalcido.example.local”, then the base would probably be “dc=example,dc=local”. You might have to ask an administrator for more information.

This lists two objects. Each object starts with a ‘dn’ (Distinguished Name) representing what the unique name is for the current object. Following that are a list of classes that the current object represents, and then a list of attributes for the object. Based on the object classes that are selected, the definition for those objects might require certain attributes be set.

In the files mentioned in /etc/schema, there will be a objectClass definition with the NAME attribute of ‘top’. Currently this is commented out in the schemas, but it is still definitely part of the default installation. The commented out piece looks like this:

# system schema
#objectclass ( 2.5.6.0 NAME 'top'
#       DESC 'RFC2256: top of the superclass chain'
#       ABSTRACT
#       MUST objectClass )

What this says is that you can create an objectClass of type top, and it must have the column objectClass. But, it’s also ABSTRACT (and commented out, btw), so an object of this type can’t be created without another objectClass to go with it. Please note the first object in our search is not only of class top, but also of classes dcClass and organization. Here are the definitions for them from the /etc/ldap/schema/ directory (you might need to do some grepping to find them from the correct files. These are part of the ‘core’ schema):

objectclass ( 1.3.6.1.4.1.1466.344 NAME 'dcObject'
        DESC 'RFC2247: domain component object'
        SUP top AUXILIARY MUST dc )

objectclass ( 2.5.6.4 NAME 'organization'
        DESC 'RFC2256: an organization'
        SUP top STRUCTURAL
        MUST o
        MAY ( userPassword $ searchGuide $ seeAlso $ businessCategory $
                x121Address $ registeredAddress $ destinationIndicator $
                preferredDeliveryMethod $ telexNumber $ teletexTerminalIdentifier $
                telephoneNumber $ internationaliSDNNumber $
                facsimileTelephoneNumber $ street $ postOfficeBox $ postalCode $
                postalAddress $ physicalDeliveryOfficeName $ st $ l $ description ) )

The organization class is a child class of top and is a structural object. It must have the attribute ‘o’ in order to be added. searching for the attribute ‘o’, I get the following:

attributetype ( 2.5.4.10 NAME ( 'o' 'organizationName' )
        DESC 'RFC2256: organization this object belongs to'
        SUP name )

Note the attribute has a super class of ‘name’, so it has all of its attribute definitions as well. One must also look up ‘ou’ to fully understand the required attributes for this class, but it is the same practice; you look up the items as you go. Now we are going to add a organization for my family. This will be an organization/organizationalUnit. I create the following LDIF file:

dn: ou=family,dc=home,dc=paulsalcido,dc=local
ou: family
objectClass: organizationalUnit

This might not be the best way. We can do multi class objects with additional object lines. Using the ldapadd command (assuming the file above is in /tmp/org.ldif), I can run:

$ ldapadd -D "cn=Admin,dc=home,dc=paulsalcido,dc=local" \
   -W -f /tmp/org.ldif
Enter LDAP Password: 
adding new entry "ou=family,dc=home,dc=paulsalcido,dc=local"

And a new object will be created. It can be found using search with the search parameter of “ou=family”:

$ ldapsearch -D "" -b "dc=home,dc=paulsalcido,dc=local" "ou=family" -LLL
dn: ou=family,dc=home,dc=paulsalcido,dc=local
ou: family
objectClass: organizationalUnit

Note the use of -LLL to reduce the output to just the objects themselves.

I’ll also add a new organization for pets, so that I can demonstrate the use of the directory structure for searching out different parts of the organization using search and the child parent relationship hierarchies. After I do that, I can run the same command and get:

$ ldapsearch -D "" -b "dc=home,dc=paulsalcido,dc=local" \
   "objectclass=organizationalUnit" -LLL
dn: ou=family,dc=home,dc=paulsalcido,dc=local
ou: family
objectClass: organizationalUnit

dn: ou=pets,dc=home,dc=paulsalcido,dc=local
ou: pets
objectClass: organizationalUnit

I would just like to note here that my pets are members of my family and this could probably be broken up under family into species, or something like that, but I digress.

Now we need to add some users to the directory. A bunch of schemas are automatically added by the default configuration load in Debian default installations, and these are often mentioned in other tutorials. The one that we are going to use for new users is the inetorgperson object class. The object and its super classes look like:

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

objectclass ( 2.5.6.7 NAME 'organizationalPerson'
        DESC 'RFC2256: an organizational person'
        SUP person STRUCTURAL
        MAY ( title $ x121Address $ registeredAddress $ destinationIndicator $
                preferredDeliveryMethod $ telexNumber $ teletexTerminalIdentifier $
                telephoneNumber $ internationaliSDNNumber $ 
                facsimileTelephoneNumber $ street $ postOfficeBox $ postalCode $
                postalAddress $ physicalDeliveryOfficeName $ ou $ st $ l ) )

objectclass     ( 2.16.840.1.113730.3.2.2
    NAME 'inetOrgPerson'
        DESC 'RFC2798: Internet Organizational Person'
    SUP organizationalPerson
    STRUCTURAL
        MAY (
                audio $ businessCategory $ carLicense $ departmentNumber $
                displayName $ employeeNumber $ employeeType $ givenName $
                homePhone $ homePostalAddress $ initials $ jpegPhoto $
                labeledURI $ mail $ manager $ mobile $ o $ pager $
                photo $ roomNumber $ secretary $ uid $ userCertificate $
                x500uniqueIdentifier $ preferredLanguage $
                userSMIMECertificate $ userPKCS12 )
        )

There are a lot of fields that are added with the inetorgperson that are not available to its parent classes. This is what we’ll use for our objects too. I’m going to start with myself, then my wife and a few of my pets, just for additional search demonstration. I’m going to create one file that will do all of the inserts, entitled ‘people.ldif’ in my /tmp/ directory:

# Users for family members.

dn: cn=psalcido,ou=family,dc=home,dc=paulsalcido,dc=local
objectClass: inetorgperson
cn: psalcido
displayName: Paul Salcido
uid: 1000
userPassword: {SHA}q6g3trXn5Y2T2ODhOFH7mFJ9UeA=
mail: psalcido@example.com
sn: Salcido

dn: cn=clogan,ou=family,dc=home,dc=paulsalcido,dc=local
objectClass: inetorgperson
cn: clogan
displayName: psalcido
uid: 1001
userPassword: {SHA}hlpxl8auS76L+ODB4Gfj8GFuzZY=
mail: clogan@example.com
sn: Logan

# Users for my pets.

dn: cn=maneki neko,ou=pets,dc=home,dc=paulsalcido,dc=local
objectClass: inetorgperson
cn: maneki neko
sn: Salcido

dn: cn=spartacus,ou=pets,dc=home,dc=paulsalcido,dc=local
objectClass: inetorgperson
cn: spartacus
sn: Salcido

The user passwords were created using the slappasswd command, using the user names as the secret:

$ slappasswd -h {SHA} -s psalcido

And pasted into the file. When I run the resulting ldif, I get the following:

$ ldapadd -D "cn=Admin,dc=home,dc=paulsalcido,dc=local" \
   -W -f /tmp/people.ldif
Enter LDAP Password: 
adding new entry "cn=psalcido,ou=family,dc=home,dc=paulsalcido,dc=local"

adding new entry "cn=clogan,ou=family,dc=home,dc=paulsalcido,dc=local"

adding new entry "cn=maneki neko,ou=pets,dc=home,dc=paulsalcido,dc=local"

adding new entry "cn=spartacus,ou=pets,dc=home,dc=paulsalcido,dc=local"

Now I can search for all family or pets using the ldapsearch command:

$ ldapsearch -D "" \
   -b "ou=family,dc=home,dc=paulsalcido,dc=local" \
   -s sub "objectClass=inetorgperson" -LLL
dn: cn=psalcido,ou=family,dc=home,dc=paulsalcido,dc=local
objectClass: inetOrgPerson
cn: psalcido
displayName: Paul Salcido
uid: 1000
mail: psalcido@example.com
sn: Salcido

dn: cn=clogan,ou=family,dc=home,dc=paulsalcido,dc=local
objectClass: inetOrgPerson
cn: clogan
displayName: Christa Logan
uid: 1001
mail: clogan@example.com
sn: Logan

Binding using the dn’s that I’ve created should now be possible, so I’ll test an LDAP search using one of the above users as the binding dn:

$ ldapsearch -D "cn=psalcido,ou=family,dc=home,dc=paulsalcido,dc=local" \
   -W -b "ou=family,dc=home,dc=paulsalcido,dc=local" \
   -s sub "objectClass=inetorgperson" -LLL
Enter LDAP Password: 
dn: cn=psalcido,ou=family,dc=home,dc=paulsalcido,dc=local
objectClass: inetOrgPerson
cn: psalcido
displayName: Paul Salcido
uid: 1000
userPassword:: e1NIQX1xNmczdHJYbjVZMlQyT0RoT0ZIN21GSjlVZUE9
mail: psalcido@example.com
sn: Salcido

dn: cn=clogan,ou=family,dc=home,dc=paulsalcido,dc=local
objectClass: inetOrgPerson
cn: clogan
displayName: Christa Logan
uid: 1001
mail: clogan@example.com
sn: Logan

Note that the user password for the binding dn that I have selected is now shown (due to permissions). The earlier calls with the anonymous or blank dn did not show the user password due to permissions. The method of changing these permissions are beyond the scope of this article though.

I hope this article has dispelled some of the particulars of LDAP and how it works. I hope that in later articles I’m able to describe configuration and advanced usage cases, as well as using it for various connectivity cases for authentication of various applications (which is an excellent use case for LDAP in general).

One comment

  1. […] The Basics of OpenLDAP When starting a business, or any general login service, LDAP is something that is often thought of but rarely really understood. OpenLDAP installation and configuration can be at […] […]

Leave a comment