Linux as an LDAP Client

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.

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:

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: