Revision 49 as of 2010-05-23 14:23:51

Clear message

/!\ This is a work in progress!

Introduction

The point of this document is to explain to sysadmins how to set up LDAP as a distributed naming and authentication service for a small network. There exist plenty of guides that purport to do this, but they're all just cargo-cult "copy this and run this command" without any explanation of what's actually going on or how to fix it when it breaks. Debian GNU: Setting up OpenLDAP is somewhat of an exception to the rule, so if you're really short on time check it out. This guide includes parts of it but also covers areas it doesn't. Some analogies to SQL will be made, as that's the dominant database metaphor at the moment.

LDAP can be used for much more than just naming and authentication, but this is by far the most common case and often the base for more advanced setups. (The second most common case is as an address book, which might be dealt with in a later supplement.) If you want a full-on introduction to LDAP, this guide is not it - there are several listed in the references.

This guide will focus on using the OpenLDAP server, as it's the premiere open source LDAP server and is widely packaged. Other LDAP servers can be used and much of the information will be applicable to them, but performance and OpenLDAP-specific overlays will not. You can use Debian lenny or Ubuntu 8.04 on the server, the instructions should work for both. If you want to use some other distribution or OS, find another guide or work out how to adapt what I say. Notably, Ubuntu 8.10 defaults to the slapd.d config format, which is theoretically better due to the configuration being editable over LDAP, however in practice is worse for the beginner as there are currently no high-level tools that use that. Also running OpenLDAP under Xen isn't recommended due to a lack of support for an assembly mutex, although this might be fixed now. On the client side, configuration for Linux, Solaris, OS X, Tru64 and Windows (using Samba) will be discussed.

LDAP Basics

So, what is LDAP? The acronym expands to "Lightweight Directory Access Protocol", and it was originally intended to provide IP (lightweight) access to X.500 servers, which required the use of the heavy OSI stack. Over time, directory servers that could be directly accessed by LDAP were written, cutting X.500 out of the loop. X.500 was a directory system designed by telcos in the 80s, back when the ISDN was the future of telephony. As such, it has several design decisions that presume a central delegating authority which have been inherited by pure LDAP servers. X.509, the security/certificate specification is one of these, and it lives on as the base of SSL/TLS, the internet PKI.

While LDAP strictly refers to the protocol, it is commonly used to refer to the database accessed via the protocol, which is more correctly called the Directory Information Tree (DIT). The DIT consists of a tree structure of entries, each of which belongs to a set of object classes and has a set of key/value attributes. An entry is the fundamental object of LDAP, and its type is determined by the values of its objectClass attribute. Common values include inetOrgPerson, posixAccount and organization.

Each objectClass is defined in a schema that lists the mandatory and optional attributes, which are themselves specified in schema. [2] For this document the important schema files are core.schema, inetorgperson.schema, cosine.schema <insert more>. An objectClass has one or more SUPerior objectClasses, from which it inherits the list of MUST and MAY attributes, and come in three types - ABSTRACT, STRUCTURAL and AUXILIARY. Every entry must have one structural objectClass, and any number of auxiliary objectClasses. Abstract objectClasses are only used to inherit from, and all objectClasses must derive from the abstract top objectClass. The limitation on an entry having only one structural objectClass can be worked around by creating a new structural objectClass that inherits from all the structural objectClasses required. This is getting into creating schema however and is beyond the scope of this document. While an entry can only have one structural objectClass, the superior objectClasses can be listed, style issue. Attribute inheritence

The tree is composed of entries, which can be both leaves and nodes. The path from an entry to the base of the tree identifies an object and is referred to as its distinguished name (DN), e.g. uid=trs80,ou=people,dc=example,dc=com. The components of the DN are the naming attribute? RDN? of each level of the tree, separated by commas. The naming attribute can be any attribute [3] but there are conventions detailed below, and it must be unique at that level of the tree, although it need not be globally unique. DNs and attribute names are generally case-insensitive, so UID=TRS80,ou=People,dc=Example,dc=COM is the same as the previous example.

The original X.500 specification envisioned a global directory service, divided by geographical regions and then companies, resulting in DNs like o=Widgets Inc.,st=Western Australia,c=AU. This doesn't map well to the internet, so the domain objectClass was defined in RFC ???? (and is included in cosine.schema) which MUST have a domainComponent (dc) attribute [4] that is by convention used as the RDN. So a domain like engineering.example.com is split at the periods to become the DN dc=engineering,dc=example,dc=com.

Administration domains. At this point you might be wondering who runs the dc=com domain and provides referrals. The answer is nobody - instead, PTR records in the DNS are used.

Filters

LDAP filters are the equivalent of the SELECT statement in SQL, but are formed of s-expressions. The full syntax is specified in RFCs ???? and ????, but the basic format is (attribute=value). Boolean operations are written like (&(a=b)(c=d)(e=f)) where & is the symbol for logical and, | is logical or, ! is logical not etc. Substring matching is allowed if the schema specifies it for that attribute...

LDIF

LDAP entries are transmitted over the wire in a binary ASN.1 format called BER, but there's an alternative text representation, the LDAP? Data Interchange Format (LDIF). This is specified in RFC ????, and there are several extensions, but the core of the format is <code>attributeName: value</code>. Here's the LDIF for an example entry:

<code> </code>

Binary attributes are base64 encoded, and the separator is change to be two colons (::) to indicate this. This also applies to ASCII characters outside a safe range.

LDAP as a name service

A name service is one of those infrastructure things that mostly just sits behind the scenes and works. More details.

To use LDAP as a naming service, entries need to be created for the accounts in the DIT. These entries typically have an objectClass of posixAccount as specified in RFC 2307, however this is an auxiliary objectClass so we still need a structural objectClass. For this document we'll use inetOrgPerson, as it contains a useful set of attributes that can be used for other things later on, such as mail, <insert more>.

Authentication

LDAP can be used as an authentication service in several ways. The easiest, and the one used here, is to perform a 'simple bind' operation where the password provided by the user is used to authenticate against the LDAP server. Other methods include SASL and client certificates. Kerberos binds can also be used, but are not much use as an authentication service since it requires an existing Kerberos authentication service. SASL offers the ability to use a variety of methods that ensure the password isn't passed in plaintext over the network, however most of these require that the password is stored in plaintext on the server. In my opinion, having everyone's password in plaintext is a much bigger risk (link to reddit) than sending it over the wire, particularly since SSL/TLS provides wire encryption these days. SASL/SRP doesn't suffer from the plaintext problem, but it's not implemented widely (or arguably, at all) and so is not an option. SASL/EXTERNAL uses client certificates, which if you're happy to deploy and maintain you have enough expertise to not require this guide.

The authentication process takes two steps - first, a search is conducted for the full DN using the provided username in the subtree specified in the configuration, then a bind is performed with the DN and the provided password.

OpenLDAP Setup

OpenLDAP is the best-performing and most powerful LDAP server available. Unfortunately it has historically been somewhat poorly documented and unfriendly to newbies. The admin guide for 2.4 is good, but still requires a certain level of background knowledge. So I'll take the traditional method of providing a slapd.conf, but unlike other guides I'll explain what's going on.

Installation

First, install the OpenLDAP server (commonly referred to as slapd):

apt-get install slapd ldap-utils

Debconf will ask some questions (I recommend you use pwgen to generate a random password):

Administrator password
PASSWORD
Confirm password
PASSWORD

The following questions that are only asked if you have debconf at low priority or are using Debian, otherwise they're set to the defaults below:

Omit OpenLDAP server configuration?
No
DNS domain name
$(hostname -d)
Organization name?
$(hostname -d)
Database backend to use
HDB
Do you want the database to be removed when slapd is purged?
No
Allow LDAPv2 protocol?
No

The slapd package creates a basic slapd.conf and populates the directory with two entries. First, let's look at /etc/ldap/slapd.conf:

# This is the main slapd configuration file. See slapd.conf(5) for more
# info on the configuration options.

#######################################################################
# Global Directives:

# Features to permit
#allow bind_v2

LDAPv2 is deprecated, and not needed unless you have Tru64 clients.

# Schema and objectClass definitions
include         /etc/ldap/schema/core.schema
include         /etc/ldap/schema/cosine.schema
include         /etc/ldap/schema/nis.schema
include         /etc/ldap/schema/inetorgperson.schema

These are the schema files that cover the most common LDAP use-cases.

# Where the pid file is put. The init.d script
# will not stop the server if you change this.
pidfile         /var/run/slapd/slapd.pid

# List of arguments that were passed to the server
argsfile        /var/run/slapd/slapd.args

# Read slapd.conf(5) for possible values
loglevel        none

Standard configuration items. I'm yet to work out what the best value for the loglevel is, but none config stats seems reasonable. slapd logs using the local4.debug facility, which by default ends up in /var/log/debug. To make it go elsewhere, add the following to /etc/syslog.conf: local4.debug            /var/log/slapd.log with tabs, not spaces.

# Where the dynamically loaded modules are stored
modulepath      /usr/lib/ldap
moduleload      back_hdb

back_hdb is a backend storage module. It uses Berkeley DB and is largely the same as back_bdb, except it stores entries in a hierarchical fashion (hence hdb) giving greater performance. The modules in /usr/lib/ldap include other backends (starting with back_) that can be used in backend/database statements (see below) and overlay modules (man pages start with slapo-) that can be used in overlay statements (none in this example file, but see replication). Overlay statements in particular are very sensitive to ordering, however I don't know of any documentation that lists a general order - people often resort to trial and error to get it right.

# The maximum number of entries that is returned for a search operation
sizelimit 500

If you have a large database you may want to tune this, or consider adding a larger limit for authenticated users.

# The tool-threads parameter sets the actual amount of cpu's that is used
# for indexing.
tool-threads 1

#######################################################################
# Specific Backend Directives for hdb:
# Backend specific directives apply to this backend until another
# 'backend' directive occurs
backend         hdb

All bdb options can be used for hdb as well.

#######################################################################
# Specific Backend Directives for 'other':
# Backend specific directives apply to this backend until another
# 'backend' directive occurs
#backend                <other>

#######################################################################
# Specific Directives for database #1, of type hdb:
# Database specific directives apply to this databasse until another
# 'database' directive occurs
database        hdb

Why is there another line saying hdb? slapd can handle multiple independent databases, eg an ISP might have a staff database ou=internal,dc=example,dc=com and several customer databases dc=example,dc=org and dc=example,dc=net served from a single slapd instance. Each database can also have its own set of overlays.

# The base of your directory in database #1
suffix          "dc=daa,dc=com,dc=au"

# rootdn directive for specifying a superuser on the database. This is needed
# for syncrepl.
# rootdn          "cn=admin,dc=daa,dc=com,dc=au"

# Where the database file are physically stored for database #1
directory       "/var/lib/ldap"

Each database is identified by its suffix, and needs its own directory to store files in. The rootdn identifies the superuser who can add, delete and modify any entry. You don't have to have one if your ACLs are sufficient (see below).

# The dbconfig settings are used to generate a DB_CONFIG file the first
# time slapd starts.  They do NOT override existing an existing DB_CONFIG
# file.  You should therefore change these settings in DB_CONFIG directly
# or remove DB_CONFIG and restart slapd for changes to take effect.

# For the Debian package we use 2MB as default but be sure to update this
# value if you have plenty of RAM
dbconfig set_cachesize 0 2097152 0

# Sven Hartge reported that he had to set this value incredibly high
# to get slapd running at all. See http://bugs.debian.org/303057 for more
# information.

# Number of objects that can be locked at the same time.
dbconfig set_lk_max_objects 1500
# Number of locks (both requested and granted)
dbconfig set_lk_max_locks 1500
# Number of lockers
dbconfig set_lk_max_lockers 1500

Tuning Berkeley DB is somewhat of a black art, see below for more details. slapd has several levels of caching, so tends to use a lot of memory.

# Indexing options for database #1
index           objectClass eq

Indexes are used for optimising searches, just like in SQL. As the comments note, these statements apply to a specific database, so you can tune them individually.

# Save the time that the entry gets modified, for database #1
lastmod         on

This adds four useful operational attributes, modifiersName, modifyTimestamp, creatorsName, and createTimestamp. It's also needed for replication.

# Checkpoint the BerkeleyDB database periodically in case of system
# failure and to speed slapd shutdown.
checkpoint      512 30

More BDB tuning.

# Where to store the replica logs for database #1
# replogfile    /var/lib/ldap/replog

This is cruft left over from the old replication method and can be deleted. The new replication method (syncrepl) is discussed below.

# The userPassword by default can be changed
# by the entry owning it if they are authenticated.
# Others should not be able to see it, except the
# admin entry below
# These access lines apply to database #1 only
access to attrs=userPassword,shadowLastChange
        by dn="cn=admin,dc=daa,dc=com,dc=au" write
        by anonymous auth
        by self write
        by * none

# Ensure read access to the base for things like
# supportedSASLMechanisms.  Without this you may
# have problems with SASL not knowing what
# mechanisms are available and the like.
# Note that this is covered by the 'access to *'
# ACL below too but if you change that as people
# are wont to do you'll still need this if you
# want SASL (and possible other things) to work
# happily.
access to dn.base="" by * read

# The admin dn has full write access, everyone else
# can read everything.
access to *
        by dn="cn=admin,dc=daa,dc=com,dc=au" write
        by * read

# For Netscape Roaming support, each user gets a roaming
# profile for which they have write access to
#access to dn=".*,ou=Roaming,o=morsnet"
#        by dn="cn=admin,dc=daa,dc=com,dc=au" write
#        by dnattr=owner write

Access control can form a large part of slapd.conf - it has an entire chapter in the administration guide. The rules here restrict access to passwords, so people can authenticate against their own, but can't see other people's. The second and third set up basic ACLs that you'll probably want to customise later on. Note the statements are wrapped over several lines - any line starting with whitespace is considered to be a continuation of the previous line.

ACLs are evaluated in the order specified in the file, with the first match being used.

#######################################################################
# Specific Directives for database #2, of type 'other' (can be hdb too):
# Database specific directives apply to this databasse until another
# 'database' directive occurs
#database        <other>

# The base of your directory for database #2
#suffix         "dc=debian,dc=org"

This is a stub to show how to set up a second database. You can read up on all the options available in the slapd.conf(5) and slapd-bdb(5) man pages. There's also another way of configuring slapd which uses ldif files in a directory called slapd.d and allows configuration over LDAP, which is covered in the administration guide, but since it's less obvious than a traditional config file I'm not going to cover it here.

Utilities

OpenLDAP comes with a variety of utilities, both client- and server-side. In general, commands starting with slap are server-side, operate on the database directly and so you have to be root or the openldap user on the same machine as slapd to use them, while commands starting with ldap are client-side, operate using the LDAP protocol and can be run as anyone including over the network, but will require authentication for some tasks.

Let's start with the server-side commands. As root, run slapcat and you should see something similar to the following:

dn: dc=daa,dc=com,dc=au
objectClass: top
objectClass: dcObject
objectClass: organization
o: daa.com.au
dc: daa
structuralObjectClass: organization
entryUUID: c2a8b180-ee60-102c-9e7b-914c836f2853
creatorsName:
createTimestamp: 20080725064340Z
entryCSN: 20080725064340.037379Z#000000#000#000000
modifiersName:
modifyTimestamp: 20080725064340Z

dn: cn=admin,dc=daa,dc=com,dc=au
objectClass: simpleSecurityObject
objectClass: organizationalRole
cn: admin
description: LDAP administrator
userPassword:: d2VsbCBkb25lLCB5b3UgY2FuIGJhc2U2NCBkZWNvZGU=
structuralObjectClass: organizationalRole
entryUUID: c2a95b62-ee60-102c-9e7c-914c836f2853
creatorsName:
createTimestamp: 20080725064340Z
entryCSN: 20080725064340.041861Z#000000#000#000000
modifiersName:
modifyTimestamp: 20080725064340Z

This is the LDIF for the two entries the slapd package created when it was installed. The first is the top of the LDAP tree, dc=daa,dc=com,dc=au. All other entries in the tree will be added below this entry. A common trick encountered when installing OpenLDAP without using the packages is that this entry must be created before you can add any other entries, otherwise you get the error ldap_add: No such object. The second entry is the database administrator. This object doesn't need to exist, you could just put the rootpw in slapd.conf, however if it's in the database you can use ACLs to limit which hosts the rootdn can authenticate from.

Note that creatorsName and modifiersName are empty. This is because they were added using slapadd, which bypasses slapd and directly writes to the database. This is a two-edged sword - it does allow you to set operational attributes, which are normally managed by the server, but it also allows you to fuck up your database if you're not careful. Generally slapadd is only used when restoring from backup (created using slapcat), particularly since it requires slapd to be stopped.

The client-side commands start with ldap and use the LDAP protocol to talk to the LDAP server. They use the libldap library, which has defaults configured via /etc/ldap/ldap.conf. Add the following lines to it so you don't have to enter them on the command line all the time:

BASE    dc=example,dc=com
URI   ldap://your.ldap.server/

See the ldap.conf(5) man page for other options that can be set.

Now, as your regular user, run ldapsearch -x (if you hadn't edited ldap.conf you'd have to run ldapsearch -x -b dc=example,dc=com -H ldap://your.ldap.server/) and you should get something similar to the following:

# extended LDIF
#
# LDAPv3
# base <dc=daa,dc=com,dc=au> with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#

# daa.com.au
dn: dc=daa,dc=com,dc=au
objectClass: top
objectClass: dcObject
objectClass: organization
o: daa.com.au
dc: daa

# admin, daa.com.au
dn: cn=admin,dc=daa,dc=com,dc=au
objectClass: simpleSecurityObject
objectClass: organizationalRole
cn: admin
description: LDAP administrator

# search result
search: 2
result: 0 Success

# numResponses: 3
# numEntries: 2

As you can see, userPassword isn't shown, since anonymous users don't have permission to read it. Also the operational attributes were not returned. To see them, run ldapsearch -x + - + is an OpenLDAP extension to request all attributes. The syntax for ldapsearch is ldapsearch [options] [LDAP filter] [list of attributes]. All the options are listed in the ldapsearch(1) man page, but the most useful ones are -x for simple authentication instead of SASL (there's no way to default to using simple auth, so you'll always be using -x), -D binddn to authenticate as a user, -W to interactively enter a password (you could use -w password but someone could run ps and see it), and -b (base|one|sub|children)' which controls how deeply the tree is searched. If you want to have a poke around your LDAP server, run ldapsearch -x -b '' -s base which shows what features slapd supports.

There are various other OpenLDAP client utilities like ldapmodify, ldapadd (actually just a link to ldapmodify), ldapdelete, ldappasswd and ldapwhoami which are basic wrappers for the libldap library calls. One very useful tool that's not part of OpenLDAP is ldapvi (apt-get install ldapvi) which performs a search, opens the resulting LDIF in your favorite $EDITOR, then sends your changes (if any) back to the server. Another good one is shelldap (not yet in Debian, but will be soon) which is a shell-like interface for browsing and editing an LDAP tree as if it were a filesystem.

There are many graphical LDAP clients about, but my recommendation is Apache Directory Studio. Yes, it's written in Java and requires Eclipse, but is unreasonably good despite this. Unfortunately the latest release requires Eclipse 3.3, but the latest version in Debian and Ubuntu is 3.2, so you'll have to download the standalone version.

Importing existing users

First we need to create some structure for the users to go in. The account entries are located in a subtree, typically called ou=People. Groups are located in ou=Group. To create these entries, put the following a file, eg /tmp/ou.ldif:

dn: ou=People,dc=example,dc=com
ou: People
objectClass: organizationalUnit

dn: ou=Group,dc=example,dc=com
ou: Group
objectClass: organizationalUnit

Now add it to the server with this command: ldapadd -x -D cn=admin,dc=example,dc=com -W -f /tmp/ou.ldif.

Next, add some indexes to make searches go faster. Replace the index           objectClass eq with the following:

index           cn,sn,uid,displayName           pres,sub,eq
index           uidNumber,gidNumber             eq
index           objectClass                     pres,eq
index           memberUid                       eq
index           uniqueMember                    eq

then as root run

/etc/init.d/slapd stop
slapindex
chown -R openldap:openldap /var/lib/ldap
/etc/init.d/slapd start

to create these indexes.

The traditional way to import users is PADL's MigrationTools or smbldap-tools, which are both available in Debian/Ubuntu - migrationtools and smbldap-tools respectively. However both are somewhat icky perl and I don't really trust them (if I had to choose I'd use MigrationTools) since they often add system groups, so here's a simple python script to import existing users:

   1 import pwd,spwd,grp,ldif,sys,socket
   2 # Get user/groups range. If you have users outside this range, just set {fir,la}st[ug]id manually
   3 adduser = file("/etc/adduser.conf",'r')
   4 for line in adduser:
   5     if line[:9]=='FIRST_UID':
   6         firstuid=int(line[10:])
   7     if line[:8]=='LAST_UID':
   8         lastuid=int(line[9:])
   9     if line[:9]=='FIRST_GID':
  10         firstgid=int(line[10:])
  11     if line[:8]=='LAST_GID':
  12         lastgid=int(line[10:])
  13 adduser.close()
  14 # If this doesn't detect it correctly, check your /etc/hosts file or just set it manually
  15 domain = socket.getfqdn().split('.')[1:]
  16 # Set the base DN.
  17 base = ','.join(['dc=%s' % i for i in domain])
  18 domain = '.'.join(domain)
  19 pwall = pwd.getpwall()
  20 grpall = grp.getgrall()
  21 ldifw = ldif.LDIFWriter(sys.stdout)
  22 
  23 for user in pwall:
  24     if firstuid <= user.pw_uid <= lastuid:
  25         shadow = spwd.getspnam(user.pw_name)
  26         surname = ''
  27         given = ''
  28         # This ignores any GECOS data
  29         name = user.pw_gecos.split(',')[0]
  30         names = name.split()
  31         if len(names) > 1:
  32             surname = names[-1]
  33             given = ' '.join(names[:-1])
  34         dn = 'uid=%s,ou=People,%s' % (user.pw_name,base)
  35         entry = {
  36                 'cn': [name],
  37                 'objectClass': ['inetOrgPerson', 'posixAccount', 'shadowAccount', 'top'],
  38                 'uid': [user.pw_name],
  39                 'displayName': [name],
  40                 'uidNumber': [str(user.pw_uid)],
  41                 'gidNumber': [str(user.pw_gid)],
  42                 'givenName': [given],
  43                 'sn': [surname],
  44                 'mail': ['%s@%s' % (user.pw_name,domain)],
  45                 'gecos': [name],
  46                 'homeDirectory': [user.pw_dir],
  47                 'loginShell': [user.pw_shell],
  48                 'shadowLastChange': [str(shadow.sp_lstchg)],
  49                 'shadowMin': [str(shadow.sp_min)],
  50                 'shadowMax': [str(shadow.sp_max)],
  51                 'shadowWarning': [str(shadow.sp_warn)],
  52                 'shadowInactive': [str(shadow.sp_inact)],
  53                 'shadowExpire': [str(shadow.sp_expire)],
  54                 'shadowFlag': [str(shadow.sp_flag)],
  55                 'userPassword': [ '{CRYPT}' + shadow.sp_pwd] }
  56         # clean up whitespace
  57         for i in entry.keys():
  58             entry[i] = [t.strip() for t in entry[i]]
  59             entry[i] = [t for t in entry[i] if t]
  60         # sn and cn are required by schema
  61         if not entry.get('sn', None):
  62             entry['sn'] = ["No surname"]
  63         if not entry.get('cn', None):
  64             entry['cn'] = ["No name"]
  65         ldifw.unparse(dn, entry)
  66 
  67 for group in grpall:
  68     if firstgid <= group.gr_gid <= lastgid:
  69         dn = 'cn=%s,ou=Group,%s' % (group.gr_name, base)
  70         entry = {
  71                 'cn': [group.gr_name],
  72                 'objectClass': ['posixGroup', 'top'],
  73                 'gidNumber': [str(group.gr_gid)] }
  74         if group.gr_mem:
  75             entry['memberUid'] = group.gr_mem
  76         ldifw.unparse(dn,entry)

Some things to note about this script:

  • Although the password scheme is {CRYPT}, this uses the crypt(3) library function, which on Linux has support for salted MD5. Future password changes will use {SSHA}, as that's the slapd password-hash default - see slapd.conf(5) and the OpenLDAP Faq-O-Matic for more details.

  • User information added by chfn(1) (eg phone numbers) is silently dropped, but it's not too hard to modify this script to keep it.

  • The schema for inetOrgPerson requires cn and sn, so fake values are used if there's no name.

To use this script, run it and redirect the output to a file, review it and remove any system users that have crept in, then use ldapadd to import it:

./pw2ldap.py > /tmp/users.ldif
ldapadd -x -D cn=admin,dc=daa,dc=com,dc=au -W -f /tmp/users.ldif

Linux

Name resolution in Linux is performed by glibc, using the NSS (name service switch) facility which is configured using /etc/nsswitch.conf and loads /lib/libnss_* modules as required. The Linux nss_ldap module is written by PADL, and comes in the libnss-ldap package in Debian and Ubuntu.

Most Linux distributions (Slackware being the notable exception, but I doubt many lazy sysadmins use it) use Linux-PAM (pluggable authentication modules), as their authentication system. The pam_ldap module was also written by PADL, uses a similar file (in some cases the same file) as the LDAP NSS module, and comes in the libpam-ldap package.

Install them both, then answer the debconf questions:

apt-get install libnss-ldap
LDAP server Uniform Resource Identifier
ldap://quoll.daa.com.au
Distinguished name of the search base
dc=daa,dc=com,dc=au
LDAP version to use
3
Make local root Database admin
Yes
Does the LDAP database require login?
No
LDAP account for root
cn=admin,dc=daa,dc=com,dc=au
LDAP root account password
PASSWORD

This will write to /etc/libnss-ldap.conf and /etc/pam_ldap.conf under Debian, and /etc/ldap.conf under Ubuntu (since 7.10). I don't recommend keeping these files under debconf, so edit them and remove the first line. Note that /etc/ldap.conf and /etc/ldap/ldap.conf are different files - the first is for lib{nss,pam}-ldap, while the second is for all programs that link against libldap (which confusingly includes lib{nss,pam}-ldap, however /etc/ldap.conf takes precedence for them).

Now, while the NSS and PAM LDAP modules are installed, they're not yet being used. To activate them, run

auth-client-config -a -p ldap_example

under Ubuntu and it should update everything automagically. On Debian you need to update various files manually. Edit /etc/nsswitch.conf and replace the passwd and group lines with the following:

passwd:         files ldap
group:          files ldap

This means /etc/passwd and /etc/group will be checked for users and groups first, then LDAP will be searched.

Then edit the common-* files in /etc/pam.d and replace the required pam_unix.so with the following:

/etc/pam.d/common-account:

account sufficient      pam_unix.so
account required        pam_ldap.so

/etc/pam.d/common-auth:

auth [success=1 default=ignore] pam_unix.so nullok_secure
auth required pam_ldap.so try_first_pass
auth required pam_permit.so

/etc/pam.d/common-password:

password sufficient   pam_unix.so nullok obscure md5
password required     pam_ldap.so use_authtok

Only if you're not sharing home directories by NFS: /etc/pam.d/common-session:

session required        pam_unix.so
session required        pam_mkhomedir.so skel=/etc/skel/ umask=0022

PAM configuration files are very sensitive to variations in ordering and words - the examples above should work, but if not you should read the PAM configuration file syntax to learn the difference between 'requisite', 'required', 'suffficient' and 'optional'.

You may have noticed I haven't mentioned how to set up nscd - the name service caching daemon. That's because it's the devil and worse than useless for the sort of environment this guide is aimed at. All it will do is cause confusion and pain. If you are performing thousands of name service lookups per second, then nscd is something you should look at, but very few servers do anywhere near that amount.

Right now we have a basic LDAP setup working. However, it's missing two things - reliability and security.

Replication

Reliability is handled by replication of the database to another machine running slapd. Just about every LDAP client can handle being given more than one LDAP servers to use if the primary is down. The OpenLDAP Administrator's Guide chapter on replication is pretty good and goes into the background of how it works, so you should read it, but if you don't have time, here's the short version.

Syncrepl is the new hotness for replication, ignore any OpenLDAP guide that mentions slurpd, particularly since it's been removed in 2.4. You can do all kinds of cool replication setups with syncrepl, including multi-master and mirror-mode, but for simplicity I'll only cover basic replication to a read-only slave that forwards updates to the master.

First the server's slapd.conf needs a few extra items. Add moduleload     syncprov after moduleload    back_hdb, then add the following lines after the existing indexes:

index           entryUUID,entryCSN               eq

# replication
overlay         syncprov
syncprov-checkpoint 100 10
syncprov-sessionlog 100

and run

/etc/init.d/slapd stop
slapindex
chown -R openldap:openldap /var/lib/ldap
/etc/init.d/slapd start

to generate the new indexes. Further down, add these lines before the access to attrs=userPassword,shadowLastChange section:

# SASL auth for syncagent
authz-regexp
        uid=([^,]*),cn=digest-md5,cn=auth
        cn=$1,ou=profile,dc=ucc,dc=gu,dc=uwa,dc=edu,dc=au

# replication sync
access to *
     by dn.base="cn=syncagent,ou=profile,dc=daa,dc=com,dc=au" read
     by * break

limits dn.exact="cn=syncagent,ou=profile,dc=daa,dc=com,dc=au" size=unlimited time=unlimited

Since a rootdn is required for syncrepl, uncomment the rootdn line (just after the database and suffix lines in the default config) and delete all the by dn="cn=admin,dc=daa,dc=com,dc=au" write lines in the access to statements, otherwise you'll get warnings on startup.

Those of you who are reading closely will note that I'm using SASL to authenticate the synchronisation agent, despite my earlier statement I wouldn't cover it. In this case since the password is only used for synchronisation, not general authentication, there's no downside to using SASL. Add the user with the following LDIF (again, use pwgen to generate a password) :

dn: ou=profile,dc=daa,dc=com,dc=au
ou: profile
objectClass: top
objectClass: organizationalUnit

dn: cn=syncagent,ou=profile,dc=daa,dc=com,dc=au
cn: syncagent
objectClass: top
objectClass: person
sn: syncagent
userPassword: {CLEARTEXT}PASSWORD

Next, install slapd on the slave server. It doesn't matter what you answer to the debconf questions as we'll be replacing the config immediately. Stop slapd, delete all files in /var/lib/ldap and copy across the slapd.conf from the primary. We need to make a few changes - remove the syncproc and syncagent lines, and add the following in their place:

syncrepl rid=19
        provider=ldap://quoll.daa.com.au
        type=refreshAndPersist
        searchbase="dc=daa,dc=com,dc=au"
        bindmethod=sasl
        authcid=syncagent
        saslmech=DIGEST-MD5
        credentials=PASSWORD
        retry="10 3 300 +"

updateref ldap://quoll.daa.com.au

rid is a identifier for replication, which must be unique per slapd. refreshAndPersist means the slave will receive changes as soon as they are made on the server. Most of the other arguments are obvious, retry="10 3 300 +" means if an error occurs (eg the connection is lost), it will retry every 10 seconds the first three times, then every 300 seconds afterwards (the + means forever - see slapd.conf(5) for full details).

You should now be able to start slapd on the slave and it will sync across your entire database immediately. You can now replace any LDAP URIs (eg in NSS/PAM configs) with ldap://master.example.com/ ldap://slave.example.com/ and they will automatically fail over if the master goes down.

The main thing you have to watch out for with a replication setup is keeping the config files in sync. Replication will function fine even if ACLs, overlays and indexes differ, which can lead to poor performance, missing features or unintended data disclosure. For a small setup it's not too hard to do it by hand. Larger sites will probably want to use the include directive (warning: it's broken in <2.3.40 and <2.4.8) or use syncrepl on cn=config, but that's getting into serious voodoo so I won't cover it here - see the N-Way Multi-Master section of the administrator's guide for an example.

SSL/TLS

First, some background. X.509 a standard for public-key infrastructure, and is part of the X.500 series of standards which LDAP is descended from. SSL/TLS are protocols that use X.509 certificates to provide a secure transport layer for higher-level protocols, like LDAP and HTTP. SSL is the predecessor to TLS, and they are largely the same, however TLS offers a few more features. One of these is StartTLS, which allows you to open an unencrypted connection, then negotiate TLS and upgrade to a secure connection. The benefit of this is you only need one port open instead of two, but the disadvantage is it's much harder to guarantee all traffic is encrypted. When talking about LDAP, TLS is often used as a term for StartTLS, vs SSL which refers to LDAPS on a dedicated port (usually 636).

There's much more to SSL than that - it's a topic deserving of an entire HOWTO by itself. Unfortunately most suffer from cargo-cult syndrome; Survival Guide - SSL/TLS and X.509 Certificates is pretty good, apart from its use of CA.pl which is still a bit manual for my liking. Fortunately OpenVPN includes a set of scripts called easy-rsa that lives up to its name. By default it creates certificates valid for 10 years, by which time updating the PKI should be SEP.

First, install openvpn and copy the easy-rsa scripts under /etc/ssl:

apt-get install openvpn
cp -a /usr/share/doc/openvpn/examples/easy-rsa/2.0/ /etc/ssl/CA

Edit /etc/ssl/CA/vars and set the bottom five KEY_ variables, although these are mainly only aesthetically important. Initialise the environment and create the CA:

. vars
./clean-all
./build-ca --pass

You'll be asked for a password and then some details about the CA, defaulting to the values you just set in vars. The CA will then be created in the keys/ subdirectory, and consists of 4 files:

ca.crt
The CA certificate. Used by clients as an authority that identifies servers, and hence is public and copied around lots.
ca.key
The CA private key. Encrypted by the password, and should only be readable by admins.
index.txt
A list of signed keys. Not private, but doesn't need to be copied.
serial
The serial number used for signing the next key. Also not private but doesn't need to be copied.

Next, create a certificate for the LDAP server:

./build-key-server ldap

Again the fields are mostly unimportant, with the exception of the Common Name, which is vital - it must match the hostname used to access the LDAP server. If it doesn't match, then the TLS client will reject the certificate and not establish a connection.

This will have created several files in keys/, of which the pertinent ones are ldap.crt and ldap.key. Similarly to the CA, ldap.key is secret and ldap.crt is not. Now, copy the required files to /etc/ldap and set the appropriate permissions:

cp keys/{ca.crt,ldap.key,ldap.crt} /etc/ldap
chgrp openldap /etc/ldap/ldap.key
chmod 640 /etc/ldap/ldap.key

To tell the OpenLDAP server about the certificates and keys, edit /etc/ldap/slapd.conf and add the following lines at the bottom:

# SSL support
TLSCertificateFile /etc/ldap/ldap.crt
TLSCertificateKeyFile /etc/ldap/ldap.key
TLSCACertificateFile /etc/ldap/ca.crt

LDAP clients just need to know about the CA, which is done by adding this line to /etc/ldap/ldap.conf:

TLS_CACERT /etc/ldap/ca.crt

Finally, edit /etc/default/slapd and add (or uncomment) the following line:

SLAPD_SERVICES="ldap://127.0.0.1:389/ ldaps:/// ldapi:///"

This tells slapd to only accept unencrypted connections from localhost, and TLS-secured connections from anywhere. ldapi uses a Unix domain socket and won't be covered here.

OK, now everything should be set, /etc/init.d/slapd restart and test it like so:

ldapsearch -x -H ldaps://quoll.daa.com.au/

If it works, you can update /etc/ldap.conf//etc/libnss-ldap.conf//etc/pam_ldap.conf with the ldaps:// URI, and add (or uncomment) the following lines:

ssl on
tls_cacertfile /etc/ldap/ca.crt

(technically the second line isn't needed as they inherit TLS_CACERT from /etc/ldap.conf)

Also needing updating will be the config on the slave server - copy across ca.crt, edit the uris in slapd.conf but you'll need to generate a second server key with the appropriate common name to match the slave's hostname.

TODO: TLSCipherSuite HIGH:MEDIUM or however it's done with gnutls.

Access Control

http://www.openldap.org/doc/admin24/access-control.html

Solaris

Solaris' LDAP client is a bit special, and not in a good way. The standard "search for the username's DN anonymously, then bind using that DN" method doesn't work for arcane reasons, so you need a proxy agent to do the search.

The supplied idsconfig tool is only really for Sun's (originally Netscape's) LDAP server, and while there are patches around to make it work with OpenLDAP, they're outdated and not actually needed. What need to do is download the solaris specific schemas, DUAConfigProfile.schema solaris.schema and put them in /etc/ldap/schema.

Next, add the profiles and proxy account. Full details to be added, but for the moment you could do worse than use the appropriate ldif files at http://developers.sun.com/identity/reference/techart/opends-namesvcs.html#5

Once you have a profile set up, the rest is fairly easy. First, edit /etc/nsswitch.ldap and replace the hosts, ipnodes etc. lines with the following:

# Sun are fucking idiots for thinking hosts should go over LDAP
hosts:      files dns

# Note that IPv4 addresses are searched for in all of the ipnodes databases
# before searching the hosts databases.
ipnodes:    files dns

networks:   files ldap
protocols:  files ldap
rpc:        files ldap
ethers:     files ldap
netmasks:   files ldap
bootparams: files ldap
publickey:  files ldap

If you don't do this, Solaris will try and use LDAP for hosts, which is a fucking stupid idea given TLS requires hostnames.

Next, run ldapclient init -v -a profileName=default -a defaultServerList=ldap.daa.com.au -a domainName=daa.com.au -a proxyDN=cn=proxyagent,ou=profile,dc=daa,dc=com,dc=au which will ask for the bind password, then configure the system to use LDAP for NSS (including copying /etc/nsswitch.ldap to /etc/nsswitch.conf). The final step is to configure PAM, so edit /etc/pam.conf - either apply pam.conf.diff or make the following changes manually:

Old:

# login service (explicit because of pam_dial_auth)
#
login   auth requisite          pam_authtok_get.so.1
login   auth required           pam_dhkeys.so.1
login   auth required           pam_unix_cred.so.1
login   auth required           pam_unix_auth.so.1
login   auth required           pam_dial_auth.so.1

New:

# login service (explicit because of pam_dial_auth)
#
login   auth requisite          pam_authtok_get.so.1
login   auth required           pam_dhkeys.so.1
login   auth required           pam_unix_cred.so.1
login   auth binding            pam_unix_auth.so.1 server_policy
login   auth required           pam_dial_auth.so.1
login   auth required           pam_ldap.so.1

Old:

# Default definitions for Authentication management
# Used when service name is not explicitly mentioned for authentication
#
other   auth requisite          pam_authtok_get.so.1
other   auth required           pam_dhkeys.so.1
other   auth required           pam_unix_cred.so.1
other   auth required           pam_unix_auth.so.1

New:

# Default definitions for Authentication management
# Used when service name is not explicitly mentioned for authentication
#
other   auth requisite          pam_authtok_get.so.1
other   auth required           pam_dhkeys.so.1
other   auth required           pam_unix_cred.so.1
other   auth binding            pam_unix_auth.so.1 server_policy
other   auth required           pam_ldap.so.1

Old:

# Default definition for Account management
# Used when service name is not explicitly mentioned for account management
#
other   account requisite       pam_roles.so.1
other   account required        pam_unix_account.so.1

New:

# Default definition for Account management
# Used when service name is not explicitly mentioned for account management
#
other   account requisite       pam_roles.so.1
other   account binding         pam_unix_account.so.1 server_policy
other   account required        pam_ldap.so.1

Add this at the bottom:

# SSH publickey support:
# see http://lists.ucc.gu.uwa.edu.au/pipermail/tech/2007-February/003146.html
sshd-pubkey     account requisite       pam_roles.so.1
sshd-pubkey     account required        pam_unix_account.so.1

certutil

http://blog.maniac.nl/setting-up-ldap-with-openldap-server-solaris-10-aix-61-and-linux-clients/ http://docs.lucidinteractive.ca/index.php/Solaris_LDAP_client_with_OpenLDAP_server

Tru64

ldapcd, certutil Tru64 LDAP Documentation http://forums11.itrc.hp.com/service/forums/questionanswer.do?admit=109447626+1218017020582+28353475&threadId=1084988

OS X

Directory Utility or whatever it's called. screenshots.

Samba/Windows

This covers using Samba 3 as an NT4 Domain Controller using the same n*x accounts and passwords already stored in LDAP. If you want to do something else, like joining an existing Windows domain or Active Directory tree, or using Samba 4 (currently in alpha) to be an Active Directory domain controller, this is not the guide for you. Samba is pretty flexible and there's more than one way to do many things, including using winbind to authenticate n*x servers from it, but for this guide it's assumed n*x servers use plain LDAP and Samba is just for Windows clients.

/!\ To do.

smbk5pwd

smbk5pwd is a nifty little overlay that updates both the sambaLMPassword, sambaNTPassword and regular userPassword attributes. This means if you change your n*x password using passwd the Windows password is updated, and changing your password in Windows' Ctrl-Alt-Del dialog changes the n*x password.

Compiling it is a bit of a pain, if you're using Debian use the patch from Debian bug 443073 and rebuild slapd:

cd /usr/src
apt-get source slapd
apt-get build-dep slapd
wget "http://bugs.debian.org/cgi-bin/bugreport.cgi?msg=85;filename=smbk5pwd.diff;att=1;bug=443073" \
 -O smbk5pwd.diff
cd openldap-2.4.11
dch -i "Rebuild including smbk5pwd."
debuild -us -uc -b
cd ..
dpkg -i libldap-2.4-2_2.4.11-2_i386.deb ldap-utils_2.4.11-2_i386.deb slapd_2.4.11-2_i386.deb slapd-smbk5pwd_2.4.11-2_i386.deb

For Ubuntu, the easiest way is to use the packages from the PPA mentioned in the Ubuntu bug.

Once this is done, add moduleload      smbk5pwd and overlay         smbk5pwd in /etc/ldap/slapd.conf below the other moduleload and overlay directives, and if using the Ubuntu PPA, smbk5pwd-enable samba (as we don't want Heimdal support) and restart slapd. You don't need to do any of this on the slave, as all writes are directed to the master, including password changes. pam_ldap should already be configured to use the LDAP password change exteneded operation, check for pam_password exop in /etc/pam_ldap.conf (Debian) or /etc/ldap.conf (Ubuntu). For samba, set ldap passwd sync = only in /etc/samba/smb.conf.

/!\ Since I haven't written the Samba part yet, you'll need to install the Samba schema on both the master and slave:

apt-get install samba-doc
cd /etc/ldap/schema
zcat /usr/share/doc/samba-doc/examples/LDAP/samba.schema > samba.schema

and edit slapd.conf and add include         /etc/ldap/schema/samba.schema after the other schema include lines.

FreeRADIUS

Why bother setting up FreeRADIUS to authenticate from an LDAP server? Because if you have NTPasswords thanks to samba, you can set up the PoPToP PPTP server so Windows users can VPN in using the same password, or use an 802.1x authenticator to do WPA{,2}-Enterprise wireless authentication or port-based wired authentication.

RADIUS is yet another enormously complex protocol, again deserving of its own HOWTO, but the changes required to a stock FreeRADIUS install for PPTP and 802.11x are thankfully minor. Like OpenLDAP, the official FreeRADIUS documentation warns against using HOWTOs that are not maintained by the FreeRADIUS team, so YMMV. The official documentation on PPTP is a bit vague, unfortunately. The 802.1X Port-Based Authentication HOWTO is old, but good.

RADIUS deals with the three As: authentication, authorization and accounting. Only the first two will be dealt with here.

Authentication means determining who a user really is, and is the most obvious use of LDAP - as a password store. Beware trying to use LDAP as the Auth-Type module itself, as this will try and authenticate by binding to the LDAP server. This only works if you have cleartext passwords. Instead, what you want to do is instruct the RADIUS server to use LDAP to retrieve hashes for the real authentication module.

RADIUS defines a massive suite of authentication protocols, all of which can be layered on top of each other. For PPTP, you want to use EAP (extensible authentication protocol) with MS-CHAPv2. This means not using MS-CHAPv2 on its own - EAP provides the framework for the authentication conversation, and the actual password exchange protocol is MS-CHAPv2. On the other hand, 802.11x is even more complicated than this - the EAP-MSCHAPv2 combination is itself encapsulated in a TLS secure tunnel, forming PEAPv0/MS-CHAPv2. (Windows Vista and above clients can support PEAP/MSCHAPv2 on the PPTP link, but luckily there seems to be an acceptable degree of autonegotiation of encryption protocol.)

Authorization is checking whether a given user is allowed to connect.

The documentation for the RADIUS LDAP module is in the rlm_ldap(5) manpage.

  • Set up the LDAP connection first. In radius.conf, find the ldap block (part of the modules block), and set the variables as follows:

server = "ldapserver.your.domain"
identity = "admin_dn"
password = admin_password
basedn = "search_base_dn"

/!\ do these variables need explaining?

  • If you're using SSL as described above, you should also find the tls block for the ldap module, and set the appropriate certificate variables, usually just cacertfile.

  • While you're editing the ldap module, change the access_attr setting. This controls whether an LDAP object can be used for RADIUS lookups; the simplest way to enable access for all users is to set this to uid. There are more complex options - see the documentation if you really need that.

  • The final change in radius.conf is in the mschap section, which deals with general options for the MS-CHAP and MS-CHAPv2 modules. Enabling the use of MPPE attributes (the encryption parameters for PPTP connections) is the only change required here:

use_mppe = on
  • Next, you need to fix the attributes that the LDAP modules uses to get the Samba passwords. Make the following changes to the ldap.attrmap file:

From

checkItem     LM-Password                     lmPassword
checkItem     NT-Password                     ntPassword

to

checkItem      LM-Password                     sambaLMPassword
checkItem      NT-Password                     sambaNTPassword
  • Next, edit the eap.conf file, which is the behemoth that controls the EAP system. Luckily, you only need a simple change here: change the default_eap_type from md5 to mschapv2.

  • The above changes will allow the default configuration (sites-enabled/default) to authenticate against LDAP. You now need to turn on authorization using the access-allowed settings as described above, by editing sites-enabled/default and uncommenting the ldap option in the authorize module.

That's it for PPTP - you should now be able to point your PPTP server at the RADIUS server (doing so is beyond the scope of this document.)

For 802.1x and WPA-Enterprise, a few more changes are necessary.

cd /usr/src
apt-get source freeradius
cd freeradius-$version
# apply the changes in the page above
debuild -us -uc -b
dpkg -i {freeradius,freeradius-common,freeradius-ldap,freeradius-utils,libfreeradius2}-$version-$arch.deb
  • Set up server-side TLS certificates for the encrypted tunnel. You can probably just use the LDAP certificates you've already generated, or create some different ones using the process above. Edit eap.conf and find the tls block, then change the following variables as appropriate:

private_key_file = ${certdir}/radius.key
certificate_file = ${certdir}/radius.crt
CA_file = /etc/ssl/CA.crt
random_file = /dev/urandom
  • The default configuration uses a different "virtual server" for the EAP exchange in the TLS tunnel. Edit sites-enabled/inner-tunnel and make the same changes as you did to the default file - i.e. uncommenting the ldap option in the authorize module.

You should now configure your wireless access point or 802.1x-enabled switch to access the RADIUS server.

Adding users

cpu-ldap

Tuning and monitoring OpenLDAP

http://www.openldap.org/doc/admin24/monitoringslapd.html http://www.openldap.org/doc/admin24/tuning.html

Debugging

So, what do you do when things go wrong? For NSS, one of the easiest ways is strace id username. Understanding strace output can be difficult, but what you're looking for is connect() lines featuring the IP address of the LDAP server. If you don't see them, LDAP isn't being queried at all, so look for a local misconfiguration. For other LDAP clients, turn up their logging if possible or strace a running process then perform the LDAP operation. Again, you're looking for connect() calls to your LDAP server.

If the program is successfully connecting to the LDAP server, next is to look at the server logs. They're in /var/log/debug by default, or wherever you put them if you edited /etc/syslog.conf. You'll need stats in the loglevel line of /etc/ldap/slapd.conf, which will show the LDAP binds and searches. Check to see that the searches are what you expect, and if so, perform them yourself from the command line with ldapsearch(1).

Another approach is to use Wireshark or another packet sniffer to examine the operations over the wire, although this won't work for SSL encrypted connections. Finally, if you are running nscd, stop it while debugging otherwise you'll be confused by stale results.

Getting Help

There are several places you can get help if something isn't working. You could try #ldap or #openldap on Freenode where I and others lurk. There's the OpenLDAP mailing lists which I don't read, but the authors of OpenLDAP do. Don't email me privately, I won't answer your question. As for distribution support, Ubuntu is pretty abysmal eg making big changes in 9.10 but not updating the documentation, Debian's ok.

References

[2] discuss what can be specified in schema [3] including a binary attribute, say jpegPhoto, leading to a DN thousands of characters long [4] itself in core.schema - a common mistake is include core.schema but not cosine.schema leading to objectClass unknown errors.


CategorySystemAdministration