March 10, 2004

Installing an OpenLDAP server - Part II

Author: Brian Jones

If you've been following along with our LDAP series, then you should already
understand the motivations for moving to LDAP, and you should already have an OpenLDAP server up and running
(covered in Part
I
). This time, we'll discuss the LDAP data model, and how to apply it to
your environment.

Understanding LDAP Data

LDAP is a protocol for interacting with data. The way the LDAP views data is as
a hierarchical collection of objects, each of which has one or more
attributes. This view of the data works well in many environments,
because most environments store facts (or attributes) about people, places, or
things (objects). Furthermore, these objects generally fall under some sort of
top-level managerial or human resources domain, like "accounting," "legal," or
"inventory." But how do we map this theoretical babble to something that
can actually be understood by LDAP? How do we tell LDAP that "Tom" is a person,
and how do we tell our systems how to grab Tom's password attribute?

The answer lies in schemas. We touched on schema files in our
last article during the configuration of our OpenLDAP server. Schema files are
imported by the slapd process at startup, and they define which objects are
supported by the LDAP directory. The OpenLDAP tarball you unpacked in our last discussion includes many of the most popular and useful schemas, so you won't have to grep the entire Internet to find what you're looking for. Just cd /etc/openldap/schema and you can peruse some of the schemas available. No matter what problem you're trying to get LDAP to solve, there is probably already a schema
available tailored to the task.

To get a taste of how schemas work, let's
have a look at just one object definition. This one from the standard
nis.schema file, which comes with OpenLDAP:


objectclass ( 1.3.6.1.1.1.2.0 NAME 'posixAccount' SUP top AUXILIARY
DESC 'Abstraction of an account with POSIX attributes'
MUST ( cn $ uid $ uidNumber $ gidNumber $ homeDirectory )
MAY ( userPassword $ loginShell $ gecos $ description )
)

This is the object definition of the posixAccount object. If you include
nis.schema in your slapd.conf file, then you can define objects of this type
to store in your directory, which we'll do in a minute. First let's understand
what this object definition is telling us.

The DESC line is self explanatory, and sometimes isn't as helpful as you
might like. The MUST line
consists of a list (separated by dollar signs) of required attributes that every
posixAccount object must have associated with it. The MAY line is a similar list, but these attributes are all optional, or allowed.

Do not discount the part of this block that says SUP top AUXILIARY. This
is actually a crucial part of the object's definition, which we'll come back to
when we have a better context to put that information in. For now, we know that
if we use nis.schema and define a posixAccount object, that object must
have, for example, a homeDirectory. But how do we know what a homeDirectory
is supposed to look like? Well, we can look at the homeDirectory attribute
definition (also from the nis.schema file), which will give us a clue:


attributetype ( 1.3.6.1.1.1.1.3 NAME 'homeDirectory'
DESC 'The absolute path to the home directory'
EQUALITY caseExactIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )

The DESC line tells most *nix administrators all they need to know. A
homeDirectory attribute is in the form of an absolute path to the home
directory of that particular posixAccount.

Blowing the cover

The simple truth, in practice, is that the purpose of the posixAccount object
type is to store information about accounts that is typically found in an
/etc/passwd file, or a NIS passwd map. The two are very similar. If you
work with either of these account storage mechanisms, then most of these
objects and attributes mean exactly what you think they should mean.

For now, you should understand that each entry in an LDAP directory is
considered an object. Each object has one or more attributes. The objects and attributes that will be understood by your directory are defined in
schema files, which are simple text files created
to allow admins like us can get real work done with a minimum of hassle.

LDAP data migration: Laying the (hierarchical) foundation

Importing data from files or NIS to LDAP requires that you extract the data and
transform it into a format called LDAP Data Interchange Format that can be readily understood by your LDAP directory. LDIF is easy to
understand and work with, and there are tools available to automate the
transformation. In addition, it's easy enough to use that I generally script my
own transformation routines, and I'm not really known for my coding
abilities.

The first bit of LDIF we need to write and import into our directory server
should define some hierarchy for the rest of our objects to sit under. There is
more than one way to structure this, but the most popular method nowadays (at
least for new deployments) is the domainComponent model. In most cases, this
model maps the parts of your DNS domain (e.g. linuxlaboratory.org) to separate
domain components (e.g. dc=linuxlaboratory,dc=org). This new object becomes the
top-level of your directory server.

Here's the LDIF for my test directory's top-level object:


dn: dc=linuxlaboratory,dc=org
objectClass: top
objectClass: dcObject
objectClass: organization
o: LinuxLaboratory
dc: linuxlaboratory.org
description: Your Source for (more) Advanced Linux Knowledge

In the first line of this entry, "dn" stands for Distinguished Name. Every object in your
directory, no matter what type of object it is, is uniquely identified by
the value of this attribute. In fact, your LDAP directory will throw an error
if you try to import two objects that have the same value for dn.

Notice that this object has three
objectClass lines. That's because I want to take advantage of an attribute
I'm allowed to use with the "organization" object that I'm not
allowed to use with the "dcObject" object: namely, the "description" attribute.

It's okay to combine object types to take advantage of different attributes
allowed by each one, provided you follow the rules. Those rules harken back to the first line of the objectClass definitions we looked at earlier. Remember when I said not to discount the part that said SUP top AUXILIARY? Here's where that can make or break your directory design.

Aside from "AUXILIARY," an object can also be described as being "STRUCTURAL." There are other types as well, but these are the two most prevalent.
In addition, each object definition lists its superior, as noted by the "SUP
top" in the earlier posixAccount definition. "top" is the highest-level
object, but objects can have other objects as their superiors. For
every entry, there can be one and only one "SUP top STRUCTURAL" objectClass
used to define it. The rest must be AUXILIARY, or STRUCTURAL objects with a
different superior object. In this example, "organization" is the only
STRUCTURAL objectClass, and dcObject is AUXILIARY. But take a look at this
string of objectClasses, taken from an account entry we'll se a bit later:


objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: inetLocalMailRecipient
objectClass: shadowAccount

The objects organizationalPerson, person, and inetOrgPerson are all
STRUCTURAL. The reason this works is that, in short, this string forms a proper
"chain of superiors," as I like to call it. The person object's superior is the top-level "top," and it is STRUCTURAL.
organizationalPerson is STRUCTURAL, but its SUP is person. inetOrgPerson
is also STRUCTURAL, but its SUP is organizationalPerson. The rest of the
objectClasses listed are AUXILIARY (which I think of as supplementary). Since
I do not have any two STRUCTURAL objectClasses listed with the same SUP object,
the chain is never broken.

To be clear, if I went back and added another
objectClass to this list which was A) STRUCTURAL, and B) had the same SUP as
another already-listed STRUCTURAL object, I would break my design. This constraint
was not strictly enforced in earlier versions of OpenLDAP, but later versions,
as they strive to conform to the LDAPv3 spec, have begun throwing errors for
bad design. In the long run, good design saves more headaches than
conforming to good design causes.

More on page 2...

More data: Branching out

With my top-level entry out of the way, it's time to think about how I want to
divide up the entities within my organization. Without a doubt, I know that I
want to keep track of users and groups on my systems. You may decide you
want to keep track of hosts, mount maps, and NIS netgroups as well.
However, these entities have among them a logical separation, so it would
be nice to be able to look for, say, people under a People subtree, groups
under a Group subtree, and so on. This is traditionally done by creating what
are called organizationalUnit or "ou" objects, under which sit the subjects of
that organizationalUnit.

For now, I'm going to keep things simple and create only People and Groups trees, with an entry or two under each tree. Here's some more LDIF:


dn: ou=People,dc=linuxlaboratory,dc=org
ou: People
objectClass: top
objectClass: organizationalUnit
description: Parent object of all UNIX accounts

dn: ou=Groups,dc=linuxlaboratory,dc=org
ou: Groups
objectClass: top
objectClass: organizationalUnit
description: Parent object of all UNIX groups

dn: cn=jonesy,ou=People,dc=linuxlaboratory,dc=org
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: inetLocalMailRecipient
objectClass: shadowAccount
uid: jonesy
sn: Jones
givenName: Brian
cn: jonesy
userPassword:: p455\^/0rD
loginShell: /bin/bash
uidNumber: 12
gidNumber: 12
homeDirectory: /home/jonesy
gecos: Brian K. Jones,IT,434,x231
mail: jonesy@linuxlaboratory.org
roomNumber: 434
telephoneNumber: x231
labeledURI: http://www.linuxlaboratory.org
description: All-around good guy
homePostalAddress: NONE
displayName: Brian K. Jones
homePhone: 000-000-0000
title: Chief

dn: cn=cartman,ou=People,dc=linuxlaboratory,dc=org
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: inetLocalMailRecipient
objectClass: shadowAccount
uid: cartman
sn: Cartman
givenName: Eric
cn: cartman
userPassword:: c4r7m@n!
loginShell: /bin/pdksh
uidNumber: 13
gidNumber: 12
homeDirectory: /home/cartman
gecos: Eric Cartman,IT,433,x233
mail: cartman@linuxlaboratory.org
roomNumber: 433
telephoneNumber: x233
labeledURI: http://www.linuxlaboratory.org
description: Round
homePostalAddress: NONE
displayName: Eric Cartman
homePhone: 000-000-0000
title: worker bee

dn: cn=staff,ou=Groups,dc=linuxlaboratory,dc=org
objectClass: posixGroup
objectClass: top
cn: staff
gidNumber: 12
memberUid: jonesy
memberUid: cartman

At this point, though we haven't done an actual import yet, we have designed an LDAP directory that can
be drawn on paper, and it would look something like the figure in diagram
1
.

How'd you do that?

How did that mass of LDIF magically appear? I started (in the case of
the jonesy account) with an NIS map entry just like this one:


jonesy:p455\^/0rD:12:12:Brian K. Jones,IT,434,x231:/home/jonesy:/bin/bash

Then:

  • Map the NIS map fields to actual LDAP attributes
  • Figure out the objectclasses needed to support those attributes
  • Figure out any additional attributes you'd like to keep track of that your
    current scheme lacks, and which objectclasses are needed for those attributes
  • Write up a quick sample LDIF account entry, and test an import
  • Automate, automate, automate!

In the LDIF I've presented, all of the NIS map fields map to attributes of
the posixAccount object. Since I plan to employ some things in the future like password aging and
expiration, I've also employed the shadowAccount class. It's
currently unused, but this keeps me from having to add it to the users' account
entries later. This foresight can potentially save gobs of time.

Some of the other account attributes, like room, mail, and telephoneNumber, are attributes that come from one or more of the other objectclasses used to define the entry. They can be used by application developers creating an online
directory, and even by existing email clients like evolution or pine, which can be configured to perform email address auto-completion based on a search of the mail attributes under the People tree. Just remember to make sure your objectclasses don't form a clash.

Implementing the Design: Importation

Our final trick is to
import the LDIF we've created. At this point I assume you have a running slapd
server. Make sure that
your slapd.conf file includes the core, cosine, inetorgperson, misc, and nis schema files. For the purposes of this example, I'll also assume your LDIF is stored in a file called my.ldif in your current working directory. Last but not
least, I assume you've followed my slapd.conf example from our last article and
used cn=Manager as your administrative user in slapd.conf, and that you've assigned (and remember) the password for this user.

With this in mind, the command to import your data into a running LDAP server is:


[jonesy@livid ~]$ ldapadd -x -W -D'cn=Manager,dc=linuxlaboratory,dc=org' -f my.ldif -c

You can find a quick explanation of the command's flags by running ldapadd by itself. The ones I've used here are:

  • -c: This means don't die on every error; list errors,
    but continue and add those entries that did not contain errors
  • -x: use simple authentication
  • -W: prompt for the bind password
  • -f filename: get entries from filename
  • -D'binddn: Bind using binddn -- essentially a username
    expressed in LDAP's language, using the full dn. For admin functions, this will
    be the rootdn you specified in your slapd.conf file.

In closing

Don't get frustrated if you get errors on your first import. Take a deep breath. Check your slapd.conf file. Make sure you're importing the correct schemas. And make sure your rootdn and password are right. If you've made changes
to slapd.conf, remember that slapd needs to be restarted in order to see the changes. When all else fails, I've had tremendous success with simply pasting the errors into the Google search box and clicking Go. Usually, data import
errors are minor flubs in configuration or a typo in your LDIF.

In the next edition, we'll look at the automation end of things, and add some
really helpful tools to your directory administration toolbox. We'll also use
those tools to start poking at our directory server and have some fun.

Click Here!