Sunday, April 13, 2014

Initializing an OpenLDAP database with the LDIF configuration

Pretty much all host and network services have traditionally been configured using flat files in /etc.  Several also have databases which are stored in flat files, and sometimes even intermingled with the configuration proper.  ISC DNS and DHCP are two significant ones.  This has the advantage of making the configuration and data easy to edit and update manually.  The disadvantage is that it must be edited and updated manually and any change means either restarting the daemon or signalling it to reload the database.

The most common solution to the editing problem is to create templates and scripts to make changes and re-generate the config/database files.  This still requires kicking the daemon for each change.   The data is often stored in a back-end database which the scripts read to generate the new config files.

What many people don't know is that both ISC DNS and DHCP can use an LDAP database directly as the back-end.  Using the LDAP database, changes can be made programatically, using standard protocols and standard APIs that implement them.

In the next couple of posts I plan to show how to create an LDAP backed DHCP service, but I need a working LDAP service first.  This post will show how initialize the LDAP service on a Linux server using OpenLDAP.  I'm going to do most of the work on Fedora 20, but it should all translate simply to either Red Hat Linux or to Debian based Linux distributions.  Where I am aware of it I'll make notes on the differences for those.

Ingredients


  • LDAP database top level distinguished name (DN): dc=example,dc=com
    A domain object for DNS domain example.com
  • LDAP admin user: cn=Manager,dc=example,dc=com
  • Initial admin user password: make one up.

LDAP terminology 101


LDAP is actually not nearly as complicated as it has been made to seem.  It does have some rather arcane terminology and it helps to get that out of the way before starting.

LDAP is a hierarchical key/value database.  This means that each value has a unique name (the key) and that each key is composed of two parts.  The first part is the local name and the remaining part is the name of the "parent" object.  At the top is the "root object" which has is special in that it has no parent. The root object can have direct values and it can have children, other objects which have their own values.

In some ways you can think of an LDAP database in the same way as you think of a filesystem. There is a root path to the top directory.  Each directory can contain files and subdirectories which in turn can have their own subdirectories.  Unlike a filesystem each object (directory) has one or more "objectClass" definitions which define the set of acceptable values and types of children.

Unlike a filesystem you can't easily browse the directory tree.  You need to know the name of the value you want, though you can make queries using type and value patterns.

Here are the most important terms you need to know to get started with an LDAP database:
  • LDAP service
    The process which answers LDAP queries.  May contain more than one database
  • LDAP database
    A unit of related data contained within an LDAP service.  Each database has a "Base DN"
  • LDAP schema
    The definition of sets of related data objects.  The schema defines both the attributes of the objects and their relationships (if any)
  • LDAP Data Interchange Format (LDIF)
    A serialized text format which describes both the contents of a database and certain operations on the contents (add/modify/delete)
  • Distinguished Name (dn)
    A unique name for a data object within the database.  A DN is usually composed by prepending a Common Name onto the object's parents DN. 
  • Base DN
    The root of the data hierarchy within an LDAP database.
  • Common Name (cn)
    A potentially non-unique name for a data object.
  • Object Class (objectClass)
    An attribute of a data object which defines which other attributes and relationships the object can have.  An object may have multiple object classes.
  • Domain Component (dc)
    This indicates one part of a DNS domain name.  The parts normally separated by dots (.) This is only called out specially here because DNS domains are commonly used as the conventional RootDN for corporate LDAP databases.

Required Packages


The first step is to install the OpenLDAP software packages.

I work with two main Linux distribution families. I differentiate them by the packaging mechanism since that's the practical difference that I have to deal with.  It's not nearly the only difference.

Since I work at Red Hat (actually long before I worked and Red Hat) I use RPM based distributions like Fedora and Red Hat Enterprise Linux (RHEL).  The other major distribution family is the Debian based distributions which also include Ubuntu and its variants.  Each family tends to contain forks of one of the two "parent" distributions so that the locations and names of packages and the files they contain tend to fall into one of those two groups.

I'm going to refer primarily to the locations of files in the RPM based distributions. I'll call out the variations for Debian distributions when it matters.

If you're installing a new OpenLDAP service then the first thing you need to do is install the required packages.

RPM based systems (Fedora, RHEL)


  • openldap-servers
  • openldap-clients

Debian based systems (Debian, Ubuntu...)


  • slapd
  • ldap-utils

Debian systems in their misguided (though sometimes effective) attempt to make things easier for sysadmins attempts to configure and start new services when the packages are installed.  When you install the slapd package you will be prompted for the initial admin password for your LDAP service.  Have your initial password ready before you begin package installation. When the package finishes installing you will have a running, but not yet properly configured LDAP service. You will be able to skip several of the steps below.  Watch for the notes.

Initialize the LDAP server


The code samples below are from a Fedora 20 system.  You'll need to adjust file locations for the schema and configuration files if you're running on a Debian based system.

Once the OpenLDAP packages are installed it''s time to begin setting up the contents of the LDAP database.  If you're working on a Debian based system you can skip the next step as it is done for you when it starts the service.

Copy default DB_CONFIG (Fedora)


If you installed on Debian and it set the initial password and started the service for you, you can skip down to the next section.

OpenLDAP typically defaults to using one of two varieties of the Berkeley DB storage format.  The standard Berkeley DB format is indicated by "bdb".  A more recent version tuned for hierarchical databases like LDAP is known as "hdb".  When I looked recently both Debian and Fedora create an initial database with the hdb format.

The BDB derivatives are very tunable to a level  to which most people will not be interested.  The tuning it set in a file called DB_CONFIG which resides in the same directory as the database files (/var/lib/ldap). Both Debian and Fedora offer a default tuning file and I generally use it unchanged.

  • /usr/share/openldap-servers/DB_CONFIG.example

cp /usr/share/openldap-servers/DB_CONFIG.example /var/lib/ldap/DB_CONFIG

At this point I can start and enable the slapdservice on RPM based systems.

sudo systemctl start slapd
sudo systemctl enable slapd

Communicating with OpenLDAP (local)


The default initial configuration of OpenLDAP allows the root user to view and manage the database configuration using the LDAP client tools and commands expressed in the LDIF... format (yes, it's redundant, but colloquial).  The database will accept queries and changes from the system root user (UID=0,GID=0).  Since I'm a fan of doing things as a non-root user, you'll see most calls to LDAP client commands via sudo.

There's a special incantation to authenticate this way.  It has three parts and looks like this:


I'll show how this works in the next section.  For ldapsearch commands I'm also going to add -LLL.  This suppresses some formatting and comments that you probably want to see, but which is more verbose than is useful in a blog post.  You can safely leave it out of your queries if you want to see the complete output.

Loading the standard schema


An LDAP service is a database in one traditional sense.  Each of the data objects is defined in a schema which describes the attributes of the object.    The schema must be loaded into the configuration database before the objects they define can be used in the user database.

In the Fedora and Debian software packages, the standard schema are provided as LDIF files which can be loaded using the ldapadd command.  The  call is similar to the ldapsearch command above:

ldapadd -Q -Y -H ldapi:/// -f <filename>>

One Fedora systems, the stock schema files are located in /etc/openldap/schema.  Each one is offered in both the original LDAP schema form and in LDIF.  Most LDAP databases will use three standard schema to start:

  • core
  • cosine
  • inetorgperson

These three define the basic objects and attributes needed to describe a typical organization: people, groups, rooms etc. Loading these three would look like this.

ldapadd -Q -Y -H ldapi:/// -f /etc/openldap/schema/core.ldif
ldapadd -Q -Y -H ldapi:/// -f /etc/openldap/schema/cosine.ldif
ldapadd -Q -Y -H ldapi:/// -f /etc/openldap/schema/inetorgperson.ldif

Finding the database configuration object


As noted above, the LDAP service can contain multiple databases.  In fact, it must because on of the databases is the configuration database itself. Like all LDAP databases, the configuration database has a DN which defines the root of the database for queries.  The DN of the configuration database is cn=config. That is: Common Name = "config".

We can query and modify the OpenLDAP configuration using the ldapsearch, ldapadd and ldapmodify commands (or any other client mechanism which can use SASL external authentication). That is: we can configure LDAP using LDAP.

Now we won't want to store our data in the configuration database.  Each distribution includes a default database configuration object for a user database. Database configuration objects have the objectClass: olcDatabase. The user databases are indicated by the data storage back end (bdb|hdb). This means we can query for the list of databases and then find which one is the user database by looking at the DN.

sudo ldapsearch -Q -Y EXTERNAL -H ldapi:/// -LLL -b cn=config olcDatabase=\* dn
dn: olcDatabase={-1}frontend,cn=config

dn: olcDatabase={0}config,cn=config

dn: olcDatabase={1}monitor,cn=config

dn: olcDatabase={2}hdb,cn=config

Each LDAP search query has two parts.  The first is a filter which selects which records to report.  The second (optional) is a selector for which fields to report for each record.

The query above indicates to search within the base DN (-b) cn=config and search for all records with a key named 'olcDatabase' regardless of the value (olcDatabase=\*) and report the dn field.

The result shows that the LDAP service has four databases. The numbers {0} are essentially LDAP array indices.  The part after the index indicates the database back end.  We're only concerned with two of these right now.

We're working with the config database {0}config,cn=config. The database we want to configure is the hdb back end.  The DN for that is olcDatabase={2}hdb,cn=config. We'll base the rest of our search and change queries on that.  Now we can query the current database configuration object.

(In Debian systems you will likely not see the monitor database, and the index of the hdb database will be 1. Adjust accordingly)

sudo ldapsearch -Q -Y EXTERNAL -H ldapi:/// -LLL -b cn=config 'olcDatabase={2}hdb' 
dn: olcDatabase={2}hdb,cn=config
objectClass: olcDatabaseConfig
objectClass: olcHdbConfig
olcDatabase: {2}hdb
olcDbDirectory: /var/lib/ldap
olcRootDN: cn=Manager,dc=my-domain,dc=com
olcDbIndex: objectClass eq,pres
olcDbIndex: ou,cn,mail,surname,givenname eq,pres,sub
olcSuffix: dc=my-domain,dc=com


The olc prefix on the class and attribute names indicates that they are part of the OpenLDAP configuration schema.

The interesting values right now are the olcSuffix and olcRootDN attributes (as well as the absence of an olcRootPW). The default database on Fedora, seen here starts with a suffix of dc=my-domain,dc=com and the root user (aka RootDN) is cn=Manager,dc=my-domain,dc=com. These are perfectly valid but useless values. For a real database we want to define our own DB suffix and root user.

Set the Database Suffix


By loose convention the LDAP database suffix for corporate LDAP services is based on the DNS domain of the organization.  This also defines the top level object in the database which we will add later.

I'm going to replace one useless default convention with another because, well using real DNS names might mess people up if they cut-n-pasted stuff from this blog. I'm going to create a database for the mythical Example Company, Inc. Of course their domain name is example.com. Now I have to translate that into an LDAP dn:

dc=example,dc=com

A domain name is composed of a list of Domain Components.  See how that works?  So we want to replace the existing olcSuffix value with our new one. This will be the first change to the default database.

Changes made using ldapadd or ldapmodify are defined using LDIF in the same way that the output of ldapsearch is expressed in LDIF.  We have to craft a change query for the olcSuffix of olcDatabase={2}hdb,cn=config and replace the existing value with our new one. Here's what that looks like:

sudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// <<EOF
dn: olcDatabase={2}hdb,cn=config
changetype: modify
replace: olcSuffix
olcSuffix: dc=example,dc=com

EOF
modifying entry "olcDatabase={2}hdb,cn=config"

The ldapadd and ldapmodify commands expect a stream of LDIF on stdin unless an input file is indicated with the -f option. I provided the update stream as a shell HERE document indicated by the EOF markers.

If you run the ldapsearch query from the previous section you can verify that the olcSuffix value has been changed.

Set the Root DN


Now that we've set the suffix for our database we need to update the DN of the user who will be able to make changes (who is not the root user on the LDAP server host).

User names in LDAP are Distinguished Names of objects stored within the database, the same as any other record. We might want to keep the (common) name "Manager" but we need to place it within the proper hierarchy for our database. Since our database is now dc=example,dc=com then the manager really must be cn=Manager,dc=example,dc=com. We'll update that in the same way that we did the suffix.

sudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// <<EOF
dn: olcDatabase={2}hdb,cn=config
changetype: modify
replace: olcRootDN
olcRootDN: cn=Manager,dc=example,dc=com

EOF

modifying entry "olcDatabase={2}hdb,cn=config"

Set the root password


The final step of the stock LDAP service set up is to create a database user password which can be used to make queries and changes without requiring the system root user to do it. The attribute for this password is olcRootPW. (it goes with the olcRootDN set above).   If the RootPW is unset then the RootDN cannot log in.  When you add this attribute, you are opening up access to the database a bit, but securing the system by allowing the DB admin to work without needing system root access.

The OpenLDAP service can store passwords in clear text (BAD) or using one of several one-way hash algorithms. You can create a new password hash using the slappasswd command. The default hash is currently SHA1, which is better than all of the others but still could be improved.

slappasswd
New password: 
Re-enter new password: 
{SSHA}nottherealhashstringthiswontworkuseyourown

At the prompts, enter the password you want and confirm it. The last line is the hashed result. This will be placed as the value of the olcRootPW attribute. See the tricky thing I did to prevent you from cut-n-pasting that last bit and using a bad password?

sudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// <<EOF
dn: olcDatabase={2}hdb,cn=config
changetype: modify
add: olcRootPW
olcRootPW: {SSHA}nottherealhashstringthiswontworkuseyourown

EOF
modifying entry "olcDatabase={2}hdb,cn=config"

Create the top object in the database


Since LDAP is a hierarchical database, each object must have a parent.  Because it can't be "Turtles All The Way Up", there must be one special object which has no parent, but which is the parent of all of the other objects in the database. That's the object who's DN is the value of the database configuration olcSuffix.

Most organizations use their domain name as the pattern for the top DN and use an LDAP "organization" object for that top object. An organization object is a container. It is meant to have children of arbitrary types. This allows for the creation of any desired structure for the database. Because the suffix is a domain name, The object must also be a Domain Component object. Domain Components are not top level or container objects. They must have a parent. By combining the organization and domain component classes we create a top level object that can have the name we want.

We're going to create a very minimal organization object at the top of the database to contain the DHCP server (machine) objects and the DHCP service (content) objects.

Organization objects have only one required attribute. the o value is a string which is the organization's name. It may also have a description attribute.

ldapadd -x -w secret -D cn=Manager,dc=example,dc=com  -H ldapi:/// <<EOF
dn: dc=example,dc=com
objectClass: domain
dc: example
description: The Example Company of America

EOF

Summary

At this point I have a running LDAP service with a minimal database. The configuration database contains the minimal schema needed for a typical LDAP service. A single user database has been defined. It contains only the top object named with the shortest DN possible in the database: dc=example,dc=com. An administrative user account has been defined and a password set for it.

The database is ready to be populated and used.

References


  • OpenLDAP - http://www.openldap.org/
  • Configuring slapd; http://www.openldap.org/doc/admin24/slapdconf2.html
  • Another Config Guilde: http://www.zytrax.com/books/ldap/ch6/slapd-config.html

3 comments:

  1. This is a very nice combination of high-level explanation with technical specifics. I feel I understand what I am doing now a whole lot better for having read it.

    I do have a couple of questions to toss out here though. First, supposing I have been playing around on my new RHEL setup, having installed OpenLDAP 2.4 with the bdb database, trying to figure out how it all works. I have muddled my way through to about this point, and the system sort of functions. Now I would like to start over fresh, with a clean database, before loading production data. Is there any way to "reset" the bdb database back to the way it was before I started mucking around? I'm concerned there might be ill-comprehended junk left over that could spoil the load. Or worse yet, what if I deleted all the files in /var/lib/ldap, as an otherwise good tutorial on converting from slapd.conf to slapd.d advises?

    Second, I find I cannot use EXTERNAL or ldapi, though I can use an account and password with ldaps. Would this be because I chose not to allow plain ldap (port 389)? Should that be enabled?

    Thanks for an excellent blog!

    Rory

    ReplyDelete
    Replies
    1. I think I found the problem with EXTERNAL. It seems that the file slapd.d/cn=config/olcDatabase={Z}config.ldif was created with a conservative access control configuration, i.e.

      olcAccess: {0}to * by * none

      That should be liberalized to

      olcAccess: {0}to *
      by dn.base="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" manage
      by * none

      After adding that line, EXTERNAL commands seem to work fine for me now.

      Rory

      Delete

  2. Thank you for the post.

    Towards the end you talk about "the o value is a string which is the organization's name", but
    o is newer set. Is that OK or you miss it?

    ReplyDelete