Configuring
Linux for LDAP Name Service and Authentication
John D'Emic
Securely distributing account information is a common challenge
faced by systems administrators. It is a tedious chore -- even in
a small environment -- to individually log into multiple servers
to change a single user's password. In the past, mechanisms such
as NIS and NIS+ were used to fill this role. These services allowed
the administrator to control user access from a single location,
allowing him to quickly add accounts, change passwords, manage groups,
and delete users. NIS and NIS+, however, have both fallen from favor
because of security issues, compatibility concerns, or supportability.
For Linux hosts, OpenLDAP is quickly emerging as the basis for centralized
name service. Coupled with PADL software's PAM and NSS libraries,
a complete open source suite of distributing user management is
available for Linux.
Overview
The term "name service" refers to abstracting flat data -- like
an /etc/hosts file or (more relevant to this discussion) an /etc/passwd
file -- onto a single facility that is able to share the data to
all entities that need it. The best and most successful example
of this is the Domain Name Service (DNS). Before DNS, administrators
had to manually keep the /etc/hosts file on all of their servers
up to date. Today, administrators just point their servers at a
DNS server and the system is able to perform hostname resolution.
We ultimately want to be able to do the same thing with /etc/passwd,
/etc/shadow, and /etc/group entries. The facilities that allow us
to do this are OpenLDAP, nss_ldap, PAM, and pam_ldap.
OpenLDAP is a free implementation of the LDAP protocol, including
libraries, utilities, clients, and servers. This article assumes
some familiarity with basic LDAP terminology and concepts. You can
refer to previous articles in Sys Admin magazine for more
details. I will present the information currently provided by /etc/passwd,
/etc/shadow, and /etc/group as LDAP directory entries served by
the LDAP server, called slapd, provided with OpenLDAP.
nss_ldap, provided by PADL software, is an open source name service
switching library that utilizes LDAP. nss_ldap allows you to configure
a system's name service switching (via /etc/nsswitch.conf) to look
up (among other things) passwd, group, and shadow data in an LDAP
directory.
PAM (Pluggable Authentication Modules) is a facility to provide
authentication mechanisms to different programs. pam_ldap, also
freely provided by PADL software, lets you use LDAP binding as an
authentication mechanism for such thing as system login, ssh logins,
and sudo.
In this article, I will describe how to configure slapd, the OpenLDAP
directory server, to serve authentication data. Then, by using nss_ldap
and pam_ldap, I will show how to configure clients to retrieve naming
and authentication information from the LDAP server without relying
on local files.
Installing the Software
The three major software packages needed to accomplish the above
tasks are OpenLDAP 2.1, pam_ldap, and nss_ldap. Your Linux distribution
likely comes shipped with some or all of the above, and you are
encouraged to use your distro's packaged versions of them. There
can be library dependencies and specific patches for your operating
system (especially for TLS and database-backened configuration)
that your distro may have already worked out for you. If you must
do it yourself, here's how:
Configuring OpenLDAP
Download the most recent stable sources from:
http://www.openldap.org
Unpack and configure as follows:
./configure --with-slapd \
--with-slurpd --without-ldapd --enable-local \
--disable-rlookups --with-tls --with-cyrus-sasl \
--enable-wrappers --enable-passwd --enable-shell \
--enable-cleartext --enable-crypt --enable-spasswd
You'll need to choose a backend database, which will likely be either
ldbm or bdb, depending on what is installed on your system. Assuming
you want to configure with LDBM and Berkeley DB support, add the following
configure lines to the above:
--enable-bdb --enable-ldbm
Once that's done:
make depend
make
make install
Use groupadd and useradd to add a group and an account
to the system to run slapd as:
addgroup ldap
useradd -g ldap -s /bin/false -d /var/run ldap
Configuring nss_ldap and pam_ldap
Again, you are encouraged to use your distro's packaged versions
of pam_ldap and nss_ldap, but if you must compile them on your own,
here's how:
Download nss_ldap.tgz from:
http://www.padl.com/OSS/nss_ldap.html
Unpack, then:
./configure --enable-rfc2307bis \
--with-ldap-lib=openldap \
--with-ldap-conf-file=/etc/libnss-ldap.conf \
--enable-schema-mapping \
--enable-ssl
make
make
install
Download pam_ldap.tgz from:
http://www.padl.com/OSS/pam_ldap.html
Then:
./configure --with-ldap-lib=openldap \
--with-ldap-conf-file=/etc/pam_ldap.conf \
--enable-ssl
make
make install
Planning the Directory
Planning the logical layout of the directory is an important first
step. While this article only covers LDAP from a naming service
and authentication perspective, LDAP can be used to store a myriad
of other data. You may be required eventually to store users' telephone
numbers, addresses, and mail delivery information in LDAP, for example,
and it's important to bear that in mind.
In particular, think carefully about how your organizational units
will be structured to make name space collisions (different objects
with the same name) less frequent. If your organization is relatively
small, it might be sufficient to have a single organizational unit
(ou) for all users (e.g., "ou=people,dc=example,dc=net"). However,
if you're setting up the directory for a large organization with
multiple departments, you may want to have an organizational unit
for each department. For example, if you were setting up authentication
for your engineering group, your sales group, and one of your customers,
you would probably have the following (see Figure 1):
"ou=engr,ou=emp,dc=example,dc=net"
"ou=sales,ou=emp,dc=example,dc=net"
"ou=acme,ou=cust,dc=example,dc=net"
In the following examples, we'll be looking at the imaginary "example.net"
domain. The base domain will be "dc=example,dc=net". To keep things
simple, we will create two organizational units -- one called "users"
and another called "group". All of the user data (the data currently
in /etc/passwd) will be stored under the "users" ou, and all of the
group data (the data currently in /etc/group) will be stored under
the "group" ou (see Figure 2):
"ou=users,dc=example,dc=net"
"ou=group,dc=example,dc=net"
Configuring slapd
As mentioned above, slapd is the directory server provided with
OpenLDAP. We'll need to configure this before we can populate our
user accounts. The server you choose to run this on should be as
secure as possible, as it will be housing user passwords. Although
the passwords are stored hashed, an attacker able to compromise
the directory could attempt to launch a brute force attack on the
hashes.
The configuration file for slapd is called slapd.conf. The default
location for it is usually /etc/openldap or /etc/ldap. A very minimal
slapd.conf file is provided in Listing 1.
The first four lines of the listing define the schema files needed
to represent user and group data in the directory. The "security"
keyword on line 6 defines the minimal security level we are allowing
for client binds. This line will guarantee 128-bit encryption for
anonymous binds between the client and the server. This will ensure
that pam_ldap will communicate to the server over an encrypted channel.
The next two lines define the TLS certificate and key files. You're
encouraged to put the certificate and key in the same file and call
it something like slapd.pem. chown the file to user/group ldap:ldap
and give it a permissions mask of 400. Guaranteeing encryption between
the clients and the server makes it harder for an attacker to snoop
authentication traffic.
By default, slapd is set up to allow anyone to read any entry
in the directory. This has obvious implications for authentication
data. Our goal is to mirror the filesystem permissions of traditional
passwd, group, and shadow files as ACLs in slapd.conf. The passwd
and group data should be readable by anybody. Shadow data (passwords)
should only be readable by the rootdn. The following is the minimum
set of ACLs that accomplish this:
access to attrs=userPassword
by dn="cn=Manager,dc=example,dc=net" write
by self write
by * auth
access to *
by * read
The above will restrict the userPassword attribute to the user who
owns the record and to the rootdn (so either can change the password)
and for everybody else to "auth" with. The latter means that a user
not bound to the LDAP server can compare a hash of a password against
the password hash in the LDAP directory. slapd returns a true value
if the hashes match. This is how the system determines whether the
password entered by a user is correct, without transmitting the cleartext
password over the wire. Because the ACLs in slapd.conf are evaluated
in a top-down manner, the second access stanza allows access to the
rest of the directory (i.e., passwd and group information).
The next two lines define the backend database that slapd will
use to store the directory records. Your backend database will likely
be either bdb or ldbm. If you (or your distribution) configured
OpenLDAP to use dynamic modules, you will need to uncomment the
"moduleload" line and have it point to the proper module to support
your backend database.
The "suffix" keyword indicates the base dn of the directory that
slapd will be serving. For our purposes, it is "dc=example,dc=net".
The rootdn is defined next. This is the equivalent of a superuser
account for the directory. You will be binding as this user later
to add accounts. The next line sets a password for the rootdn. You
can put a plaintext password here, but you're encouraged to use
the slappasswd command to generate a hashed one:
slappasswd -s "password"
You can then cut and paste the output into the file.
The directory line defines the path to the backend database files.
You want this directory to be user and group owned by user "ldap",
with a permission mask of 400. The last two lines indicate the indices
that need to be maintained.
At this point, your slapd configuration should be good to go.
You can start slapd either by running something like /sbin/service
ldap start or by manually doing /usr/bin/slapd -u ldap.
If the startup fails, you can invoke slapd with debugging mode
to see what is going on:
/usr/sbin/slapd -u ldap -d99 -h ldap:///
Once slapd is running, use ldapsearch to make sure you can
bind to it:
ldapsearch -Z -x -h ldap.example.net
You should get something back like "result: 32 No such object". This
is fine because the directory is empty. The -Z argument above
tells ldapsearch to use TLS to connect to the ldapserver. Let's
leave that out and see what happens:
ldapsearch -x -h ldap.example.net
If you've configured everything correctly, you should get "confidentiality
required" back from slapd. slapd is rejecting your bind because the
connection is not encrypted. Finally, the -x flag says you
want to do a simple bind to the directory server. Since we're not
specifying a user, this is called an anonymous bind.
Populating the Directory
Now that slapd is running, we can add entries to the directory.
We will set up the layout scheme discussed before, with our base
dn being "dc=example,dc=net" and one organizational unit called
"users":
ldapadd -Z -x -D "cn=Manager,dc=example,dc=net" -W -h
ldap.example.net Enter LDAP Password: <rootdn password typed here>
dn: dc=example,dc=net
objectClass: dcObject
objectClass: organization
o: example
dc: example
<cntrl-d>
dn: ou=users,dc=example,dc=net
objectClass: organizationalUnit
ou: users
<cntrl-d>
dn: ou=group,dc=example,dc=net
objectClass: organizationalUnit
ou: group
<cntrl-d>
<cntrl-c>
Note that you could have put the above LDIFs (a textual representation
of an ldap directory entry) into a file and passed that to ldapadd:
ldapadd -Z -x -D "cn=Manager,dc=example,dc=net -W -h ldap.example.net
-f ./my.ldif
The -D argument to ldapadd specifies the dn of the user we
want to bind as. Since we're modifying the directory, we used the
rootdn. Remember that we gave the rootdn a hard-coded password in
the slapd.conf file. We need to pass the -W flag to indicate
that we will interactively be entering that password. We could have
alternatively used -w "rootdn password here", specifying the
password on the command line. Once you've done the above, use ldapsearch
to confirm the entries were added.
Adding Users to the Directory
Now that the top-level organizational units are set up, we can
add accounts to the directory. I'll describe how to manually add
an account first, then look at batch adding accounts from existing
/etc/passwd entries.
Creating a user, whether in flat files or in LDAP, requires the
following pieces of data:
Username UID
GID
Homedirectory
Gecos Field
A hashed password
Next, we'll create an account for "Joe User". His username will
be "juser", he will have a uid of "503", his default group will
have a gid of "503", and his login shell will be "/bin/tcsh". His
home directory will be /home/juser, and his gecos field will be
"Joe User". We'll use the slappasswd command as before to
generate a password for him. This system supports MD5-hashed passwords,
so we'll pass that to slappasswd to get:
slappasswd -h "{MD5}" -s "mypassword"
That gives us:
{MD5}NIGde+6ruSYKXIVLyFs+RA==
for Joe User's password. (If your system does not support MD5 passwords,
replace MD5 with CRYPT above.)
Next, we want to represent the above information in LDIF. In this
example, I'll add the following to a file called juser.ldif, which
will define juser's account and group data:
dn: uid=juser,ou=users,dc=example,dc=net
uid: juser
cn: juser
objectClass: account
objectClass: posixAccount
objectClass: top
objectClass: shadowAccount
uidNumber: 503
gidNumber: 503
homeDirectory: /home/juser
gecos: Joe User
userPassword: {MD5}NIGde+6ruSYKXIVLyFs+RA==
dn: cn=juser,ou=group,dc=example,dc=net
objectClass: top
objectClass: posixGroup
cn: juser
gidNumber: 503
memberUid: juser
Once that's done, add the user to the directory:
ldapadd -Z -x -D "cn=Manager,dc=example,dc=net -W -h
ldap.example.net -f juser.ldif
Next, use ldapsearch to confirm Joe's account is there:
ldapsearch -Z -x "uid=juser"
Notice that Joe's hashed password is not being displayed. This is
because we restricted the userPassword attribute in our ACLs. If you
search again, but bind as the rootdn, you should see the password:
ldapsearch -Z -x -W -D "cn=Manager,dc=example,dc=net"
"uid=juser"
Batch Populating the Directory
You could manually add all your existing users and groups using
the method above, or you could batch populate the directory using
existing flat files. PADL software publishes a suite of Perl scripts
that make doing this fairly simple. They are available from:
http://www.padl.com/OSS/MigrationTools.html
We will use the migrate_passwd.pl and migrate_group.pl files to generate
an LDIF file that will then be used to populate the directory with
the /etc/passwd and /etc/group data. Once you've untar'ed the MigrationTools,
copy your /etc/passwd and /etc/group files to another directory. Edit
the copies and remove the users and groups that you want to maintain
in the flat files. Leave system accounts, such as root, daemon, sys,
etc., in the flat files so the system can still function properly
if it is in single-user mode or if the directory server cannot be
reached. Once this is done, back up and open the migrate_common.ph
file in an editor. Find the line that reads:
$NAMINGCONTEXT{'passwd'} = "ou=People";
Change "People" to reflect the ou under which we're placing our users.
In this example, the ou is called "users", so we'll change that line
to look like this:
$NAMINGCONTEXT{'passwd'} = "ou=users";
Next find the $DEFAULT_BASE variable and change it to reflect your
base dn:
$DEFAULT_BASE = "dc=example,dc=net"
Finally, run the Perl scripts against your edited copies of /etc/passwd
and /etc/group:
perl migrate_passwd.pl ./passwd_edited passwd.ldif perl
migrate_group.pl ./group_edited group.ldif
Your flat /etc/passwd and /etc/group data should now be in LDIF format
in the passwd.ldif and group.ldif files. Now we can populate the directory
with this data:
ldapadd -Z -x -D "cn=Manager,dc=example,dc=net" -W -h \
ldap.example.net -f ./passwd.ldif
ldapadd -Z -x -D \
"cn=Manager,dc=example,dc=net" -W -h ldap.example.net -f \
./group.ldif
Once your user data is in LDAP, you can remove the user and group
entries from /etc/passwd, /etc/shadow, and /etc/group. And, after
the directory is populated, you can start configuring clients.
Configuring the Client
We're now ready to set up pam_ldap and nss_ldap on our clients.
nsswitch.conf
This determines the methods and order that the system's resolver
libraries use to gather name service data. We want to append the
"ldap" keyword to the passwd, shadow, and group definitions as follows:
passwd: files ldap
shadow: files ldap
group: files ldap
(Your system may have "compat" instead of "files" -- this is fine.)
Listing 2 contains a sample /etc/nsswitch.conf file.
nss_ldap/pam_ldap
Depending on your Linux distribution, you may have separate config
files for pam_ldap and nss_ldap. On other systems, they both use
the same file. On Red Hat, for example, /etc/ldap.conf contains
the configuration for both pam_ldap and nss_ldap. On Debian, they
are broken out in /etc/pam_ldap.conf and /etc/libnss_ldap.conf.
In either case, the contents of the file are the same for our purposes.
Listing 3 contains the config file.
Now we can test name service switching. At this point, we should
be able to do finger juser and get back the user's account
information from ldap. Go ahead and try this. Also try fingering
the accounts that have been batch added. Once this is working, we
just need to finish the PAM configuration and we can call it a day.
Completing the PAM Configuration
Every service in the system that wants to take advantage of PAM
will have a conf file in /etc/pam.d. Some systems have a global
PAM conf file sourced by other files in this directory. Red Hat
is a good example of the latter; its PAM configuration is centralized
in /etc/pam.d/system-auth. Debian also uses this method, except
it sources the /etc/pam.d/common-* files. Listing 4 contains a working
/etc/pam.d/system-auth for Red Hat systems, and Listing 5 contains
working /etc/pam.d/common-auth, /etc/pam.d/common-account, /etc/pam.d/common-password,
and /etc/pam.d/common-session for Debian.
Once the PAM configurations are in place, you can test ssh:
ssh juser@localhost
This should log you into the server (if it doesn't work, you may need
to restart your ssh daemon). If you need to troubleshoot further,
you can put the "debug" keyword after the pam_ldap.so line in the
PAM configs as follows:
auth sufficient pam_ldap.so try_first_pass debug
Next, try changing the password of the "juser" account:
passwd juser Enter login(LDAP) password:
New password:
Re-enter new password:
LDAP password information changed
for juser passwd: password updated successfully
Try ssh'ing into the account to make sure the password change was
successful.
Conclusion
This article has provided an introduction to centralizing account
information on an LDAP directory. Once you're comfortable with your
configuration, you may also want to investigate directory replication
with slurpd, Kerberos integration, SASL binds, shadow extensions,
and other things that I was unable to cover here due to space constraints.
John D'Emic has been a systems administrator and on again,
off again programmer for the past 7 years. He has a computer science
degree from St. John's University and is currently employed by Opsource,
Inc, splitting his time between system engineering and coding. He
can be reached at: jdemic@opsource.net.
|