March 24, 2004

SysAdmin to SysAdmin: Linux as an LDAP client

Author: Brian Jones

LDAP software vendors seem to spend most of their efforts focusing on the
functionality of the server. As a result, some client platforms leave a lot to
be desired in terms of functionality and ease of use. Luckily, Linux doesn't
suffer from many of the deficiencies of other operating systems where LDAP is concerned. In this article, we'll discuss how to make effective use of an LDAP server from your
Linux desktop. 

Linux is probably the easiest operating system to configure for
authentication against an LDAP server, thanks in large part to the good folks
over at PADL Software, Ltd., who distribute the
open source tools necessary to get Linux (and other Unix variants) to use LDAP
in a relatively seamless manner. The relevant tools available from PADL are
nss_ldap and pam_ldap.

The nss_ldap module adds LDAP support to the NSS (Name Service Switch)
subsystem. NSS abstracts tasks involving information retrieval from various
sources like NIS, LDAP, or even local files, so that application developers
don't have to write their own routines to do this (thereby causing massive
duplication of code). Another benefit, of course, is that administrators
centralize the configuration of said information retrieval into a single file:
the nsswitch.conf file.

The pam_ldap module adds LDAP support to the PAM subsystem, so that various
rules can be enforced by checking validity against an LDAP directory. PAM
stands for Pluggable Authentication Modules, and provides an abstraction layer
that, through the use of different PAM modules, can enforce various sorts of
security measures. Like the NSS system, this allows for centralized
administration, and less code-writing for developers who choose to use it.

If you had the option of installing LDAP client tools during your
distribution's installation routine, the PADL nss_ldap and pam_ldap libraries
should already be in place. One good way to tell if you have the PADL tools is
to check for the existence of an /etc/ldap.conf or /etc/nssldap.conf file.
Don't confuse this with /etc/openldap/ldap.conf -- this file is used only by
the OpenLDAP command line tools, like ldapsearch and
ldapmod.

If you don't have the PADL components installed and want to build from
source, the PADL documentation explains how. Either way, from here on, I'll
assume that you have both components installed. I'll also assume you have an
/etc/ldap.conf file, which is supplied by the nss_ldap tool. Note that on Red
Hat/Fedora systems, the pam_ldap and nss_ldap components are both supplied in
one RPM: nss_ldap.

Every Linux system I've ever seen uses an /etc/nsswitch.conf file that
tells the system the services to be used for things like uidnumber-to-name
mappings and mount map entry lookups. However, just putting a line like
passwd: files nis ldap into your nsswitch.conf file, which tells
your system to check for (in this case) passwd information using files, NIS, and
LDAP sources, in that order, isn't enough. In fact, it's pretty meaningless unless you
have libraries in place to support talking to these services.

On your Linux system, running the command locate libnss_ should
return a good number of libraries representing the different services available
to the system. To illustrate how these libraries are used, you might consider
running a command like strace -o traceout ls -l ~/.bashrc on a
Linux system and perusing the output file. What you'll see, if your head
doesn't explode trying to parse strace output, is that for each service in
nsswitch.conf, there is a corresponding library that gets invoked to carry out
the lookup. In the case where you have passwd: files ldap,
libnss_files gets called, a lookup takes place on the /etc/passwd file, and if
that doesn't return the data necessary to complete the operation, libnss_ldap
gets called to lookup the same information via LDAP.

The ldap.conf file

Once you have these tools installed, getting a Linux machine to authenticate against an LDAP server is pretty straightforward. If you're using an RPM-based distribution, take heart in knowing that these packages have very few dependencies. If you have OpenSSL and OpenLDAP client libraries installed, a simple rpm -ivh package will suffice on most machines. If you're building from source, your job is really no harder. These are well-behaved packages that, on Linux systems, put things in the proper place
without any fuss.

You must know only your LDAP server's hostname and the basedn of the
directory -- information which is stored in the /etc/ldap.conf file. Of course,
the more information you know about how your directory is configured and how
the data is laid out, the more you can do to optimize how lookups are performed
by the client. The default ldap.conf as distributed by the PADL folks is chock
full of useful commentary on all the changes you can make to the file.

Here's an example ldap.conf file (with comments removed) very similar to one
found on some of my test machines:


host ldap.linuxlaboratory.org ldap2.linuxlaboratory.org
base dc=linuxlaboratory,dc=org
pam_filter objectclass=posixAccount
pam_groupdn cn=admins,ou=Group,dc=linuxlaboratory,dc=org
pam_member_attribute memberuid
nss_base_passwd ou=People,dc=linuxlaboratory,dc=org?one
nss_base_shadow ou=People,dc=linuxlaboratory,dc=org?one
nss_base_group ou=Group,dc=linuxlaboratory,dc=org?one
nss_base_netgroup ou=Netgroup,dc=linuxlaboratory,dc=org?one
ssl start_tls
pam_password md5

This ldap.conf file is fairly thorough. On a simple test machine, in early
stages of testing, the only lines required are the host and basedn lines. The
rest of the lines in this file either implement a security constraint or
optimize performance. Actually, only the pam_groupdn and
ssl keys are really security measures. pam_groupdn
checks to insure that anyone logging into the system using a service that does
LDAP lookups belongs to the admins group on the server. The
ssl line tells the client to use the start_tls() function call to
bind using TLS to port 389 on the LDAP server. The nss_base_*
lines all tell the client to use a basedn that's different from the default for
different types of searches. For example, the nss_base_passwd line
says that instead of starting at the default basedn (dc=linuxlaboratory,dc=org)
and searching down through the subtrees to find a particular user's entry, just
start searching under the People subtree, which we know ahead of time contains
the user entries. You'll notice ?one tagged on to the end of these
lines. This says to only search one level. That means if I later add subtrees
under "ou=People," they won't be searched for passwd entries.

More on page 2...

The nsswitch.conf file

The next file to edit is /etc/nsswitch.conf. All you need to do is add the
string "ldap" to the lines that represent data stored in your directory.
Assuming you're storing user and group information in your directory, you need
to change only three lines. An early test machine's nsswitch.conf file might
include these lines:


passwd: files ldap
shadow: files ldap
group: files ldap

This is a very simplistic example. It simply tells the system that for any
user- or group-related information, check files first, and fall back to LDAP as
a secondary information source. The important thing to remember here is that,
during testing, you'd like to be able to log into the test machine even if LDAP
proves to be (at first) a catastrophic failure. Leave yourself a backdoor by
either adding a local account you can use to log in (preferably something
other that root) or having a "known good" service act as LDAP's backup. For
example, if you know for a fact that your NIS data is good, you might put
files ldap nis in your nsswitch.conf file, just in case.

Checking your work

At this point, your system should be using LDAP to do simple user
information lookups. LDAP is not yet guaranteed to work for authentication yet,
so don't log out! Test that you can get information back about non-local
users from LDAP by running the following test commands:


[jonesy@beeker jonesy]$ getent passwd jonesy
jonesy:x:552:50:Brian K. Jones,rm232,x5432:/home/jonesy:/bin/bash

[jonesy@beeker jonesy]$ id jonesy
uid=30252(jonesy) gid=50(admins) groups=10(wheel),100(admin),10101(info),42(www)

You can also try a simple ls -l command in the /home directory.
All of the subdirectories under /home are owned by different people. If none of
them is local, and you see names and groups listed in this output (indicating
user and group ownership), you're good! If you only see numeric UID and GID
values, then your client failed to map the numbers to names -- your LDAP lookup
failed. The reason is likely to lie in one of two places:

  • The client isn't reaching the server. Check that the hostname is
    resolvable. The hostname used in your config files has to be resolvable without
    using LDAP, of course. It's a good idea to put entries in /etc/hosts for your
    LDAP servers. Also, try tailing the log for your LDAP server to see if requests
    are coming in.
  • The server refuses to respond. When I first started testing, it once took
    me longer than I'd like to admit to realize that the LDAP server daemon wasn't
    running. If the server is responding, then it's possible that there are
    security restrictions in place on your server that are keeping things from
    working. Since this is a simple test configuration, remove as many constraints
    as you can. We'll cover locking down a directory server in a future article --
    right now, we just want something that is functional, not something to be used
    in production.

Configuring pam_ldap and testing authentication

PAM implementations can vary. You might have an /etc/pam.conf file or an
/etc/pam.d directory that holds individual config files for each service that
uses PAM. Further, your pam.d directory may or may not contain a system-auth
file, which acts as a sort of default configuration that can effectively be
"included" into the individual service config files.

You may not even have a PAM-aware system. Slackware, however, is the only
Linux distribution that I know of that doesn't include PAM support. This
doesn't mean you can't use PAM on a Slackware system, only that you have to
install it yourself.

I'm going to assume that you're running a distribution with an /etc/pam.d
directory. Red Hat/Fedora, Mandrake, SUSE, Debian, and probably a host of other
distributions all fit in this category.

For this exercise, we'll configure a single service. Once you have a single
service that can successfully use LDAP for authentication it's simple to apply
the same change to other services.

Let's take a look at the /etc/pam.d/sshd file. We'll pick this one instead
of, say, login, because, in the event that you make a horrible error, the standard
login routine will still let you in.

Here's some code taken straight from the example files that come with
pam_ldap:


auth required /lib/security/pam_nologin.so
auth sufficient /lib/security/pam_ldap.so
auth required /lib/security/pam_unix_auth.so try_first_pass
account sufficient /lib/security/pam_ldap.so
account required /lib/security/pam_unix_acct.so
password required /lib/security/pam_cracklib.so
password sufficient /lib/security/pam_ldap.so
password required /lib/security/pam_pwdb.so use_first_pass
session required /lib/security/pam_unix_session.so

As you can see in this simplified configuration, adding LDAP to your sshd
PAM config file is as simple as adding a line to each realm (auth, account,
password, and session) of the config file. The ordering of the realms
themselves is irrelevant, but the ordering of the lines in each realm is
extremely significant. The logic behind declaring the module as "sufficient"
and listing it above a "required" module in the same realm is that if a
sufficient module fails, it allows us to move forward and try the pam_unix_auth
module as well, which allows a local-only account that pam_ldap couldn't find
on the directory to log in. If either one succeeds, the login attempt succeeds.
If pam_ldap were required, local-only accounts would fail. For a rundown on how
this all works, have a look at my earlier article on Understanding
PAM
.

On Red Hat and Fedora systems, most of this configuration sits in the
/etc/pam.d/system-auth file, and is called via the pam_stack module in the
individual service config files under /etc/pam.d/. The authconfig utility on these systems (an ncurses-based administration tool) allows you to go through three screens and
fill in your LDAP information, press OK, and have your nsswitch.conf,
system-auth, and even ldap.conf files automagically reconfigured. I've found
this tool to work quite reliably. In cases where a glitch comes up, the fix is
usually a simple one, such that it's still quicker to use autoconfig than to do
the entire configuration by hand. The danger in using this file for early
testing is that it is referenced by every PAM-enabled service on the system. As
a result, if you mess up one service, you essentially mess up all of them!

The only thing left to do now is make sure your /etc/ssh/sshd_config is set
up to use PAM. The "UsePAM" option should be set to yes, and the
"PasswordAuthentication" option should be set to no. Restart and test -- log into
your shiny new test SSH server from a remote host, using a username that does
not exist in the /etc/passwd file on the SSH server, and see if it succeeds.
If you come across trouble, here's a quick list of places to look for
clues:

  • On the client, keep a terminal session running tail -f
    /var/log/messages
    , to make sure that your SSH client process isn't
    acting erratically. In the event that you can't log in using SSH, you should
    see messages in that file stating that an SSH login was attempted, and failed
    due to a PAM authentication failure. If PAM isn't mentioned, check your
    sshd_config to make sure 'UsePAM' is set to 'yes'.
  • Make sure that, on the system you're trying to log into, you don't have a
    firewall, hosts.allow/hosts.deny settings, or other utilities foiling your
    attempts. An iptables firewall may not be logging to /var/log/messages!
  • In my experience, the quickest way to troubleshoot problems is to start at
    the LDAP server. Keep a constant tail -f display running on
    whatever file your LDAP server logs to. Set your server's loglevel to 127. This
    should tell you everything you could ever want to know, and more, about what's
    happening.
  • If all else fails, Google whatever errors you find. There's a good chance
    someone else has had to overcome the initial hurdles you're struggling
    with.

Brian Jones is the founder of linuxlaboratory.org, and has worked as a *nix
systems, network, and database administrator for the past six years. He
currently works for the Computer Science department at Princeton University.