Differences between revisions 2 and 25 (spanning 23 versions)
Revision 2 as of 2008-06-11 18:54:22
Size: 9404
Comment: a bit of a cleanup
Revision 25 as of 2009-07-20 19:36:07
Size: 52038
Comment: pam.conf
Deletions are marked like this. Additions are marked like this.
Line 2: Line 2:

LDAP for the Lazy Sysadmin
Line 6: Line 4:
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. 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. 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.

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...
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. [http://techpubs.spinlocksolutions.com/dklar/ldap.html 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 [http://www.openldap.org/lists/openldap-software/200603/msg00214.html lack of support for an assembly mutex], although [http://www.openldap.org/lists/openldap-technical/200804/msg00057.html this might be fixed now]. On the client side, configuration for Linux, Solaris, OS X, Tru64 and Windows (using Samba) will be discussed.

[[TableOfContents]]

= LDAP B
asics =

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...
Line 91: Line 33:
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.

= OpenLDAP setup =
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 thses 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 =
Line 108: Line 55:
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
unfriendly to newbies. The [http://www.openldap.org/doc/admin24/ admin guide for 2.4] is good, but still requires a certain level of background knowledge. So I'll take the traditional method of
Line 112: Line 58:
<insert slapd.conf>

= LDAP as a naming service =

== What is a naming service? ==

A naming 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>.

The account entries are located in a subtree, typically called ou=People. Groups
are located in ou=Group. Together with the base entry, the DIT initially looks like
this:

dn: dc=example,dc=com
dc: example
objectClass: domain

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

dn: ou=Group,dc=example,dc=com
== 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 '''h'''db) 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 [http://www.openldap.org/doc/admin24/access-control.html 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 [http://www.openldap.org/doc/admin24/slapdconf2.html 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 [http://www.openldap.org/faq/data/cache/157.html 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 [http://www.openldap.org/doc/admin24/access-control.html#Controlling%20rootdn%20access 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.

There are many graphical LDAP clients about, but my recommendation is [http://directory.apache.org/studio/ 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
Line 143: Line 349:
= LDAP 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 thses 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.

== Linux ==

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 LDAP PAM module was also written by PADL,
and uses a similar file (in some cases the same file) as the LDAP NSS module.
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 [http://www.padl.com/OSS/MigrationTools.html MigrationTools] or [https://gna.org/projects/smbldap-tools/ 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:
{{{#!python
import pwd,spwd,grp,ldif,sys,socket
# Get user/groups range. If you have users outside this range, just set {fir,la}st[ug]id manually
adduser = file("/etc/adduser.conf",'r')
for line in adduser:
    if line[:9]=='FIRST_UID':
        firstuid=int(line[10:])
    if line[:8]=='LAST_UID':
        lastuid=int(line[9:])
    if line[:9]=='FIRST_GID':
        firstgid=int(line[10:])
    if line[:8]=='LAST_GID':
        lastgid=int(line[10:])
adduser.close()
# If this doesn't detect it correctly, check your /etc/hosts file or just set it manually
domain = socket.getfqdn().split('.')[1:]
# Set the base DN.
base = ','.join(['dc=%s' % i for i in domain])
domain = '.'.join(domain)
pwall = pwd.getpwall()
grpall = grp.getgrall()
ldifw = ldif.LDIFWriter(sys.stdout)

for user in pwall:
    if firstuid <= user.pw_uid <= lastuid:
        print user
        shadow = spwd.getspnam(user.pw_name)
        surname = ''
        given = ''
        # This ignores any GECOS data
        name = user.pw_gecos.split(',')[0]
        names = name.split()
        if len(names) > 1:
            surname = names[-1]
            given = ' '.join(names[:-1])
        dn = 'uid=%s,ou=People,%s' % (user.pw_name,base)
        entry = {
                'cn': [name],
                'objectClass': ['inetOrgPerson', 'posixAccount', 'shadowAccount', 'top'],
                'uid': [user.pw_name],
                'displayName': [name],
                'uidNumber': [str(user.pw_uid)],
                'gidNumber': [str(user.pw_gid)],
                'givenName': [given],
                'sn': [surname],
                'mail': ['%s@%s' % (user.pw_name,domain)],
                'gecos': [name],
                'homeDirectory': [user.pw_dir],
                'loginShell': [user.pw_shell],
                'shadowLastChange': [str(shadow.sp_lstchg)],
                'shadowMin': [str(shadow.sp_min)],
                'shadowMax': [str(shadow.sp_max)],
                'shadowWarning': [str(shadow.sp_warn)],
                'shadowInactive': [str(shadow.sp_inact)],
                'shadowExpire': [str(shadow.sp_expire)],
                'shadowFlag': [str(shadow.sp_flag)],
                'userPassword': [ '{CRYPT}' + shadow.sp_pwd] }
        # clean up whitespace
        for i in entry.keys():
            entry[i] = [t.strip() for t in entry[i]]
            entry[i] = [t for t in entry[i] if t]
        # sn and cn are required by schema
        if not entry.get('sn', None):
            entry['sn'] = ["No surname"]
        if not entry.get('cn', None):
            entry['cn'] = ["No name"]
        ldifw.unparse(dn, entry)

for group in grpall:
    if firstgid <= group.gr_gid <= lastgid:
        dn = 'cn=%s,ou=Group,%s' % (group.gr_name, base)
        entry = {
                'cn': [group.gr_name],
                'objectClass': ['posixGroup', 'top'],
                'gidNumber': [str(group.gr_gid)] }
        if group.gr_mem:
            entry['memberUid'] = group.gr_mem
        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 [http://www.openldap.org/faq/data/cache/344.html 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 [http://www.padl.com/OSS/nss_ldap.html 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 [http://www.kernel.org/pub/linux/libs/pam/ Linux-PAM] (pluggable authentication modules), as their authentication system. The [http://www.padl.com/OSS/pam_ldap.html 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 [http://www.kernel.org/pub/linux/libs/pam/Linux-PAM-html/sag-configuration-file.html PAM configuration file syntax] to learn the difference between 'requisite', 'required', 'suffficient' and 'optional'.

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 [http://www.openldap.org/doc/admin24/replication.html#Pull%20Based 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 unique identifier for replication, change it on each server if you have multiple slaves. ''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 [http://www.openldap.org/its/index.cgi/Software%20Bugs?id=5276 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 [http://www.openldap.org/doc/admin24/replication.html#N-Way%20Multi-Master 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; [http://www.zytrax.com/tech/survival/ssl.html 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 [http://openvpn.net/index.php/documentation/howto.html#pki 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, attachment:DUAConfigProfile.schema attachment:solaris.schema and put them in /etc/ldap/schema

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 attachment: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
[http://h30097.www3.hp.com/docs/base_doc/DOCUMENTATION/V51B_HTML/ARH95ETE/PPLDPXXX.HTM 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.

== 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 [http://bugs.debian.org/cgi-bin/bugreport.cgi?msg=85;filename=smbk5pwd.diff;att=1;bug=443073 patch] from [http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=443073 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 [http://ppa.launchpad.net/ian-sigma-uk/ppa/ubuntu PPA] mentioned in the [https://bugs.launchpad.net/ubuntu/+source/openldap/+bug/82853/ 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 trivially set up the PoPToP PPTP server so Windows users can VPN in using the same password.

= Adding users =

cpu-ldap

= Tuning and monitoring OpenLDAP =

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

= Getting Help =

There are several places you can get help if something isn't working. You could try #ldap or #openldap on [http://freenode.net/ Freenode] where I and others lurk. There's the [http://www.openldap.org/lists/ 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, Debian's ok.

= References =

 * [http://techpubs.spinlocksolutions.com/dklar/ldap.html Debian GNU: Setting up OpenLDAP]
 * [http://www.zytrax.com/books/ldap/ LDAP for Rocket Scientists]
 * [http://www.openldap.org/doc/admin24/ OpenLDAP Software 2.4 Administrator's Guide]
 * [https://wiki.ubuntu.com/AuthClientConfig AuthClientConfig]
 * [http://docs.sun.com/app/docs/doc/816-4556/ldapsetup-1?l=en&a=view Solaris 10 LDAP Naming Services Setup and Administration]
 * [http://developers.sun.com/identity/reference/techart/opends-namesvcs.html Setting Up OpenDS 1.0.0 as a Naming Service for the OpenSolaris OS, Part 1 of 2: Basic Steps]
Line 174: Line 804:
http://techpubs.spinlocksolutions.com/dklar/ldap.html
----
CategorySystemAdministration

/!\ 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. [http://techpubs.spinlocksolutions.com/dklar/ldap.html 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 [http://www.openldap.org/lists/openldap-software/200603/msg00214.html lack of support for an assembly mutex], although [http://www.openldap.org/lists/openldap-technical/200804/msg00057.html this might be fixed now]. On the client side, configuration for Linux, Solaris, OS X, Tru64 and Windows (using Samba) will be discussed.

TableOfContents

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 thses 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 [http://www.openldap.org/doc/admin24/ 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 [http://www.openldap.org/doc/admin24/access-control.html 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 [http://www.openldap.org/doc/admin24/slapdconf2.html 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 [http://www.openldap.org/faq/data/cache/157.html 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 [http://www.openldap.org/doc/admin24/access-control.html#Controlling%20rootdn%20access 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.

There are many graphical LDAP clients about, but my recommendation is [http://directory.apache.org/studio/ 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 stopslapindex 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 [http://www.padl.com/OSS/MigrationTools.html MigrationTools] or [https://gna.org/projects/smbldap-tools/ 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         print user
  26         shadow = spwd.getspnam(user.pw_name)
  27         surname = ''
  28         given = ''
  29         # This ignores any GECOS data
  30         name = user.pw_gecos.split(',')[0]
  31         names = name.split()
  32         if len(names) > 1:
  33             surname = names[-1]
  34             given = ' '.join(names[:-1])
  35         dn = 'uid=%s,ou=People,%s' % (user.pw_name,base)
  36         entry = {
  37                 'cn': [name],
  38                 'objectClass': ['inetOrgPerson', 'posixAccount', 'shadowAccount', 'top'],
  39                 'uid': [user.pw_name],
  40                 'displayName': [name],
  41                 'uidNumber': [str(user.pw_uid)],
  42                 'gidNumber': [str(user.pw_gid)],
  43                 'givenName': [given],
  44                 'sn': [surname],
  45                 'mail': ['%s@%s' % (user.pw_name,domain)],
  46                 'gecos': [name],
  47                 'homeDirectory': [user.pw_dir],
  48                 'loginShell': [user.pw_shell],
  49                 'shadowLastChange': [str(shadow.sp_lstchg)],
  50                 'shadowMin': [str(shadow.sp_min)],
  51                 'shadowMax': [str(shadow.sp_max)],
  52                 'shadowWarning': [str(shadow.sp_warn)],
  53                 'shadowInactive': [str(shadow.sp_inact)],
  54                 'shadowExpire': [str(shadow.sp_expire)],
  55                 'shadowFlag': [str(shadow.sp_flag)],
  56                 'userPassword': [ '{CRYPT}' + shadow.sp_pwd] }
  57         # clean up whitespace
  58         for i in entry.keys():
  59             entry[i] = [t.strip() for t in entry[i]]
  60             entry[i] = [t for t in entry[i] if t]
  61         # sn and cn are required by schema
  62         if not entry.get('sn', None):
  63             entry['sn'] = ["No surname"]
  64         if not entry.get('cn', None):
  65             entry['cn'] = ["No name"]
  66         ldifw.unparse(dn, entry)
  67 
  68 for group in grpall:
  69     if firstgid <= group.gr_gid <= lastgid:
  70         dn = 'cn=%s,ou=Group,%s' % (group.gr_name, base)
  71         entry = {
  72                 'cn': [group.gr_name],
  73                 'objectClass': ['posixGroup', 'top'],
  74                 'gidNumber': [str(group.gr_gid)] }
  75         if group.gr_mem:
  76             entry['memberUid'] = group.gr_mem
  77         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 [http://www.openldap.org/faq/data/cache/344.html 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 [http://www.padl.com/OSS/nss_ldap.html 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 [http://www.kernel.org/pub/linux/libs/pam/ Linux-PAM] (pluggable authentication modules), as their authentication system. The [http://www.padl.com/OSS/pam_ldap.html 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 [http://www.kernel.org/pub/linux/libs/pam/Linux-PAM-html/sag-configuration-file.html PAM configuration file syntax] to learn the difference between 'requisite', 'required', 'suffficient' and 'optional'.

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 [http://www.openldap.org/doc/admin24/replication.html#Pull%20Based 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 stopslapindex 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 unique identifier for replication, change it on each server if you have multiple slaves. 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 [http://www.openldap.org/its/index.cgi/Software%20Bugs?id=5276 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 [http://www.openldap.org/doc/admin24/replication.html#N-Way%20Multi-Master 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; [http://www.zytrax.com/tech/survival/ssl.html 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 [http://openvpn.net/index.php/documentation/howto.html#pki 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, attachment:DUAConfigProfile.schema attachment:solaris.schema and put them in /etc/ldap/schema

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 attachment: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 [http://h30097.www3.hp.com/docs/base_doc/DOCUMENTATION/V51B_HTML/ARH95ETE/PPLDPXXX.HTM 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.

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 [http://bugs.debian.org/cgi-bin/bugreport.cgi?msg=85;filename=smbk5pwd.diff;att=1;bug=443073 patch] from [http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=443073 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 [http://ppa.launchpad.net/ian-sigma-uk/ppa/ubuntu PPA] mentioned in the [https://bugs.launchpad.net/ubuntu/+source/openldap/+bug/82853/ 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 trivially set up the PoPToP PPTP server so Windows users can VPN in using the same password.

Adding users

cpu-ldap

Tuning and monitoring OpenLDAP

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

Getting Help

There are several places you can get help if something isn't working. You could try #ldap or #openldap on [http://freenode.net/ Freenode] where I and others lurk. There's the [http://www.openldap.org/lists/ 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, 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