Using OpenLDAP For Authentication; Revision 2

Revision/Modified: May 6, 2003
Author: Vincent Danen

[NOTE: This is a revision of the previous LDAP authentication article. Testing has been done with Corporate Server 2.1 as the server (ie. Mandrake Linux 9.0), and clients including Mandrake Linux 8.2 through 9.1. This revision updates and corrects the information contained in the previous version. The previous version is still available due to the many excellent comments associated with it; however all the information here should supersede the information in the previous article.]

User authentication for logins is generally a no brainer. You setup users on the local system and off you go... nothing to it. However, if you're on a LAN and you want to have a centralized "repository" of users, you will likely be looking at some method of distributing user information across the LAN. This has a few distinct advantages, the primary being all user authentication is centralized. This means that users have the same password on each system in the LAN, and if they change their password, the password is seamlessly changed everywhere. This provides the advantage of giving consistency to user authentication on the LAN. Users retain the same userid, groupid, password, and other information. This can be problematic if you assign users different levels of access on different machines, but if you permit the same access on all systems, this is an easy way to do it. Regardless, with sudo, you can fine-tune privileged access on a host-by-host basis as well.

Traditionally, NIS (Network Information Services, aka YP (Yellow Pages)) was used to provide this sort of information. NIS is an RPC-based protocol similar to NFS. And while NIS may work well enough in most cases, it doesn't work well in all cases (personal experience here has shown NIS to be anything but reliable). However, there is another choice, and that choice is LDAP. Mandrake Linux provides OpenLDAP and this is the starting block of what is required for a distributed authentication system.

There are few tutorials on how to accomplish using LDAP for authentication, and I found them to be difficult to understand or incomplete, and as a result some research and testing was done to setup LDAP-based authentication on Mandrake Linux. This was originally done using Mandrake Linux 8.2, and all later versions of Mandrake Linux operate in the same way. With other distributions or vendors, you may have to tweak a few things (this particular article is specific to Mandrake Linux). The information here should be enough to get you started, if not help you finish everything off. With the latest OpenLDAP update (MDKA-2003:009), you should be able to follow this tutorial completely and have a working authentication system. Users of other distributions may have to patch their OpenLDAP packages in order to get the same results.

Pre-requisites

The first things you need to do is ensure that OpenLDAP is properly installed, along with a few optional packages that will tie our system together. Obviously, the first step is to install OpenLDAP. The packages we need to have installed (on a Mandrake Linux system) are:

  • libldap2
  • openldap
  • openldap-clients
  • openldap-migration
  • openldap-servers
  • nss_ldap
  • pam_ldap

The openldap-servers and openldap-migration packages are only required on the system that will be your authentication server. They are not required on the "client" systems.

The pam_ldap and nss_ldap packages are required for PAM authentication and for NSS information (ie. retrieving group, user, host, etc. information from the LDAP server). Once you have all of these packages installed, you can begin to configure your LDAP server.

Configuring the OpenLDAP Server

The first step in configuring your server is to edit the /etc/openldap/slapd.conf file. There are a few fields you will need to configure. In this paper, we will assume that your domain name to use on the LAN is "dc=mylan,dc=net" and will illustrate our configuration accordingly.

database        ldbm
suffix          "dc=mylan,dc=net"
rootdn          "cn=root,dc=mylan,dc=net"
rootpw          {MD5}zYgLcm4KDb1CN/ENGdpG9A==
directory	/var/lib/ldap
index		objectClass,uid,uidNumber,gidNumber eq
index		cn,mail,surname,givenname	    eq,subinitial
password-hash	{crypt}
password-crypt-salt-format	"$1$%.8s"

There is much more in your slapd.conf file, but we'll only change these options to begin with. Here you are setting your domain, along with the root user and root's password. You are also setting up some indexes so that queries to the LDAP server do not take so long. These are the default index settings for slapd.conf but you should make note of them. They should be sufficient for your system. Finally, you are setting the default password format for the LDAP server. This tells the LDAP server to use the "{crypt}" (or crypt(3)) hash, with the noted salt format, which translates to a standard crypt(3) MD5 hash. This will allow a user to authenticate to the operating system and the LDAP server with the same password (and same password format).

To obtain the value for the rootpw keyword, use the slappasswd utility like this:

[root@ldap]# slappasswd -h {MD5}

You will be asked for a password and then slappasswd will spit out the MD5 string that corresponds to your chosen password. Cut and paste this string into your file as the rootpw string. There are a few different types of passwords you can use, but I prefer to use MD5 passwords. You can also use SSHA (the default), CRYPT, SMD5, or SHA by specifying the name to the -h parameter, which is the hash parameter. For instance, if you wanted to use crypt passwords you would use "-h {CRYPT}".

Before configuring your basic ACLs, let's start slapd and make sure it works.

[root@ldap]# service ldap start

Once you have started the slapd server, you can test it by executing the following query:

[root@ldap]# ldapsearch -x -b '' -s base '(objectclass=*)' namingContexts
version: 2

#
# filter: (objectclass=*)
# requesting: namingContexts 
#

#
dn:
namingContexts: dc=mylan,dc=net

# search result
search: 2
result: 0 Success

# numResponses: 2
# numEntries: 1

If you see something similar to the above, ldap is installed and working properly. If not, then go back and ensure you haven't tinkered too much yet. We currently have the LDAP server running, but not populated. To have slapd start on boot each time, execute:

[root@ldap]# chkconfig ldap on

Configuring Server ACLs

The final step on the server before we begin migrating data is to set the basic ACLs (Access Control Lists) for the LDAP server. This will ensure that people only have access to what they need to have access to, and will allow users to update their passwords, see their passwords, but prevent others from seeing the same.

Once again you need to edit the /etc/openldap/slapd.conf file. If you look in your slapd.conf file, you will see the following near the beginning:

# Define global ACLs to disable default read access.
include /etc/openldap/slapd.access.conf

The /etc/openldap/slapd.access.conf file is as good as any to place your ACLs in. You can either append your ACL rules to the end of the slapd.conf file, or insert them into slapd.access.conf. The choice is entirely up to you. If you do choose to use the access file, remove the example ACLs at the end of the slapd.conf file. Let's begin with some basic ACLs:

# This is a good place to put slapd access-control directives

access to dn=".*,dc=mylan,dc=net" attr=userPassword
	by dn="cn=root,dc=mylan,dc=net" write
	by self write
	by * auth

access to dn=".*,dc=mylan,dc=net" attr=mail
	by dn="cn=root,dc=mylan,dc=net" write
	by self write
	by * read

access to dn=".*,ou=People,dc=mylan,dc=net"
	by * read

access to dn=".*,dc=mylan,dc=net"
	by self write
	by * read

What this does is restrict access to the userPassword attribute of any entry; that is, any dn in dc=mylan,dc=net. The owner of the entry can modify it, and the owner is defined by someone binding to the server using that dn and it's associated password. Otherwise, it can only be accessed for authentication/binding purposes, but cannot be viewed. The second entry allows the user to modify their mail attribute (ie. email address). The third entry specifies that any dn in ou=People,dc=mylan,dc=net must be read-only. This is where we protect the system from users deciding to change their username, gid or uid numbers, home directory, and so forth. Because the ACLs are read top down in a "first match wins" order, we have effectively given users access to change their own password and their own email address, but they are unable to touch any other information on their account. Everything else is read-only... to the world, and the user. Finally, the last entry is a catch-all for other parts of the database. This will allow users to make changes to their own address books, for example. If you will not be allowing users to use their own address books on this LDAP server, feel free to remove the "by self write" ACL of the last entry. This will still allow users to read group and hosts information. If you like, you can duplicate the second entry to allow users to modify their loginShell attribute so they can select what shell they wish to use, but I wouldn't recommend it.

To have the server use the new ACLs, be sure to restart it (service ldap restart).

Migrating Data

The next step is to begin migrating your data into your LDAP server. This is where things start to get interesting, and also where the openldap-migration package is necessary. Change to the /usr/share/openldap/migration directory and edit the migrate_common.ph file. You will need to modify the following variables to match your system:

$DEFAULT_MAIL_DOMAIN = "mylan.net";
$DEFAULT_BASE = "dc=mylan,dc=net";
$DEFAULT_MAIL_HOST = "mail.mylan.net";
$EXTENDED_SCHEMA = 1;

This sets some defaults for the migrated data. Here we set the default mail domain, in this case "mylan.net" which will assign all users a default email address of "user@mylan.net". The default base is "dc=mylan,dc=net" which should be identical to the suffix defined in slapd.conf. The default mail host is the SMTP server used to send mail, in this case "mail.mylan.net". The extended schema is set to 1 to support more general object classes.

Now you have two choices. You can migrate everything on the current system into the LDAP database, including hosts, groups, users, networks, services, etc. A lot of this is relatively unchanging and, in my opinion, should not be included in LDAP. For instance, importing /etc/services seems useless because all systems have it, and the data is very static. Doing lookups from the LDAP server would only slow things down. The only things that I, personally, see as being useful are users, groups, and hosts. Everything else should use the system files, unless, of course, you have modified your /etc/networks file and such, but most people probably have not.

If you want to migrate everything, you will want to use the migrate_all_online.sh script. I suggest that you comment out the migration of protocols and services due to some problems with migration (you will likely have missing entries due to some errors in the source files). You can do this by editing migrate_all_online.sh and commenting out the following lines:

#echo "Migrating protocols..."
#$PERL -I${INSTDIR} ${INSTDIR}migrate_protocols.pl       $ETC_PROTOCOLS >> $DB
#echo "Migrating services..."
#$PERL -I${INSTDIR} ${INSTDIR}migrate_services.pl       $ETC_SERVICES >> $DB

The next step is to execute the script. You will be asked a few questions, but in most cases the defaults should work fine:

[root@ldap]# ./migrate_all_online.sh
Enter the X.500 naming context you wish to import into: [dc=mylan,dc=net]
Enter the name of your LDAP server [ldap]: localhost
Enter the manager DN: [cn=manager,dc=mylan,dc=net]: cn=root,dc=mylan,dc=net
Enter the credentials to bind with: secret
Do you wish to generate a DUAConfigProfile [yes|no]? no

The X.500 naming context is the base domain to use (the default should be fine since it should read this from the suffix in slapd.conf). The LDAP server name should be localhost unless you are configuring a remote LDAP server (which probably will not be the case). The manager DN should be identical to the rootdn in slapd.conf, which is almost the same except we use cn=root instead of cn=manager which is the default. The credentials to bind with is the password you generated and included in slapd.conf as the rootpw. The DUAConfigProfile should be set to no. This will import everything into your LDAP database and may take a few minutes.

However, I personally favour doing things a little more manually to get some finer controls over what is being imported. For instance, there is no need to import root or the other system accounts into the server. While in most cases the information for these accounts should be read from the local system first, there is no need to make the database bigger than required. Since the system apache or rpm user will never login directly, having them included in the LDAP database is unnecessary. I've also found that migrating everything, especially user information, will require you to do a little cleanup work afterwards anyways due to some incorrect values being imported. I would suggest you decide what you want imported and obtain the values manually. For this example, we will import /etc/hosts, the user information we only want imported, and likewise with the group information. It is a little more work, but it implies accuracy and a database that only contains necessary information.

What we want to do is execute some migration scripts one by one and create ldif files that we will then use to import into the LDAP database. The first thing you must do is create the base structure for the LDAP database running the migrate_base.pl script:

[root@ldap]# ./migrate_base.pl >base.ldif
[root@ldap]# ldapadd -x -D "cn=root,dc=mylan,dc=net" -W -f base.ldif

This generated the base structure and imports it into the LDAP database. Now we can begin to add actual information to the database. Let's start with /etc/hosts:

[root@ldap]# ./migrate_hosts.pl /etc/hosts hosts.ldif
[root@ldap]# ldapadd -x -D "cn=root,dc=mylan,dc=net" -W -f hosts.ldif

Let's assume that one of the entries in your /etc/hosts file was:

10.0.10.23	wrkstation.mylan.net	wrkstation

You can test to make sure the migration worked by executing:

[root@ldap]# ldapsearch -LL -H ldap://localhost -b"dc=mylan,dc=net" -x "(cn=wrkstation)"
version: 1

dn: cn=wrkstation.mylan.net,ou=Hosts,dc=mylan,dc=net
objectClass: top
objectClass: ipHost
objectClass: device
ipHostNumber: 10.0.10.23
cn: wrkstation.mylan.net
cn: wrkstation

Be very careful that you do not have duplicate entires in your /etc/hosts file. While this would be alright for normal operations, when importing the data into LDAP, the import will halt if a duplicate is encountered. In other words, make sure localhost isn't listed twice, and so on.

Next, migrate the group information. Execute:

[root@ldap]# ./migrate_group.pl /etc/group group.ldif

This will put all groups into the group.ldif file. Since this is redundant (do we really need apache group, rpm group, etc.?), you will want to edit the group.ldif file and remove the unwanted groups. I would only keep user groups, not system groups. I also would not put root into the LDAP database for this. The reason is simple: root is a system account and should be treated as such. It should also be specific to the machine. Having the same root password across machines is not a good idea. If you have one system where a user needs root access and you elect to use su and provide them with the root password, you can restrict them to the one machine by not including root in the LDAP database. And since each system will always have a root account, because we lookup locally first, and then the LDAP database, root should never be referenced by LDAP anyways. Some people may prefer to have the same root password across multiple machines (for example, a very large school lab). While I don't agree with this personally, others may find it difficult to remember a thousand different root passwords (a good password management scheme can deal with this, however). In this case, you may prefer to keep root in the LDAP directory and authenticate root against the LDAP directory instead of locally. This can be done by putting root in both the LDAP directory and locally, and changing how pam_ldap authenticates (we'll see about this in a moment). If this is your particular preference, keep root in the group.ldif file.

If you're setting up a system without any pre-existing users, you may need to create this file from scratch. The file will look like this:

dn: cn=vdanen,ou=Group,dc=mylan,dc=net
objectClass: posixGroup
objectClass: top
cn: vdanen
gidNumber: 1001

dn: cn=joe,ou=Group,dc=mylan,dc=net
objectClass: posixGroup
objectClass: top
cn: joe
gidNumber: 1002

dn: cn=users,ou=Group,dc=mylan,dc=net
objectClass: posixGroup
objectClass: top
cn: users
gidNumber: 1000
memberUid: vdanen
memberUid: joe

You can see what objects are to be referenced here with the objectClass keywords. The cn is the group name, the gidNumber is the group ID number. Finally, the dn is a string that consists of the group name, the ou (which is Group), and the domain information (dc=mylan,dc=net). The last example shows a group with multiple users; in this instance the group is "users" and we assign it a gid of 1000, and add both vdanen and joe to the group by using the memberUid attribute. To import this information into LDAP, use:

[root@ldap]# ldapadd -x -D "cn=cn=root,dc=mylan,dc=net" -W -f group.ldif

Now we come to the users. This can be somewhat time-consuming if you are setting up a lot of users, even if you use the migration script. First, let's run the migration script like this:

[root@ldap]# ETC_SHADOW=/etc/shadow ./migrate_passwd.pl /etc/passwd \
passwd.ldif

You must set the $ETC_SHADOW environment variable before executing the migrate_passwd.pl script. This is necessary to tell migrate_passwd.pl where /etc/shadow is located. Without this environment variable, none of the shadow information will be included (which means no passwords). As with the groups, you may have to write this file from scratch. If you do, you can simplify creating the file by cutting and pasting the encrypted password string in /etc/shadow and placing it as the value for the user's userPassword attribute (which is all that migrate_passwd.pl does; there is no kind of conversion of any sort). Taking the above two groups of vdanen and joe, let's make two entries for them which would look like this in your passwd.ldif file:

dn: uid=vdanen,ou=People,dc=mylan,dc=net
uid: vdanen
cn: Vincent Danen
givenname: Vincent
sn: Danen
mail: vdanen@mylan.net
mailRoutingAddress: vdanen@mail.mylan.net
mailHost: mail.mylan.net
objectClass: mailRecipient
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
objectClass: account
objectClass: posixAccount
objectClass: top
objectClass: kerberosSecurityObject
objectClass: shadowAccount
userPassword: {crypt}$1$dYJI1DcK$r7Uod6DPFgh4XWL7l9GTF/
shadowLastChange: 11761
shadowMin: -1
shadowMax: 99999
shadowWarning: -1
shadowInactive: -1
shadowExpire: -1
shadowFlag: 7100670
krbname: vdanen@MYLAN.NET
loginShell: /bin/bash
uidNumber: 1001
gidNumber: 1001
homeDirectory: /home/vdanen
gecos: Vincent Danen

dn: uid=joe,ou=People,dc=mylan,dc=net
uid: joe
cn: Joe User
givenname: Joe
sn: User
mail: joe@mylan.net
mailRoutingAddress: joe@mail.mylan.net
mailHost: mail.mylan.net
objectClass: mailRecipient
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
objectClass: account
objectClass: posixAccount
objectClass: top
objectClass: kerberosSecurityObject
objectClass: shadowAccount
userPassword: {crypt}$1$Ev9HK2ML$IhA3EpyS28SfJ.m74AMvW/
shadowLastChange: 11762
shadowMin: -1
shadowMax: 99999
shadowWarning: -1
shadowInactive: -1
shadowExpire: -1
shadowFlag: 7100670
krbname: joe@MYLAN.NET
loginShell: /bin/bash
uidNumber: 1002
gidNumber: 1002
homeDirectory: /home/joe
gecos: Joe User

Now you will be able to import your passwd.ldif file into LDAP by executing:

[root@ldap]# ldapadd -x -D "cn=root,dc=mylan,dc=net" -W -f passwd.ldif

Finally, let's test to make sure that the data got imported properly. You can use ldapsearch to search for an account you know exists. In our case, we know that user joe exists, so let's search for his data:

[root@ldap]# ldapsearch -LL -H ldap://localhost -b"dc=mylan,dc=net" -x "(uid=joe)"

This will return a screen of input which should like identical to what was stored in your ldif file. We have now imported our hosts, group, and user information into LDAP.

Configuring the OpenLDAP Clients

Now you must configure OpenLDAP on each of the client systems. What we have configured previously is just the OpenLDAP server, or the authentication server. Now you must configure the clients. This also includes the server as it will likely be a client unto itself (ie. it will access the LDAP server via localhost to obtain authentication information). To do this, you must edit the /etc/ldap.conf file. The entries we are most interested in are the following:

host 127.0.0.1
base dc=mylan,dc=net
rootbinddn cn=root,dc=mylan,dc=net
scope one
pam_filter objectclass=posixaccount
pam_login_attribute uid
pam_member_attribute gid
pam_password md5
nss_base_passwd		ou=People,dc=mylan,dc=net?one
nss_base_shadow		ou=People,dc=mylan,dc=net?one
nss_base_group		ou=Group,dc=mylan,dc=net?one
nss_base_hosts		ou=Hosts,dc=mylan,dc=net?one

This tells the LDAP client the IP address of the host. For the server, you can use 127.0.0.1 (localhost), although you should really use the fully qualified domain name (a requirement if you're going to use SSL or TLS). The remote clients must use the domain name (and the IP must be listed in /etc/hosts) or the IP address of the LDAP server (again, using the IP or the FQDN really depends on your SSL settings; more on this in a moment). The base must be the same as the suffix denoted in /etc/openldap/slapd.conf. The pam_password must be set to md5 (it uses the crypt(3) system, which will use the unix MD5 hash instead of the built-in MD5 which is incompatible). If you use "pam_password crypt" (which was what was previously recommended), when you change passwords using the passwd program, passwords will be stored in crypt format, not MD5 format. The nss_base_* entries must be defined properly; they will be commented in the file so you must uncomment those you want to enable (in our case, just passwd, shadow, group, and hosts), and you must also make sure the base DN is correct. The ending "?one" is the search scope and should remain as illustrated.

The rootbinddn keyword sets who to bind to the server as when the effective user ID is root (or 0). The password must be stored in /etc/ldap.secret, which should be mode 0600 (read/write root, no access for group or other), and should be owned by root.root. This is a clear-text version of the password you specified with the "rootpw" keyword in slapd.conf, and the dn should be that of the root user (ie. cn=root,dc=mylan,dc=net). Please note that you must press enter after typing your password into the file. If the file does not end on the second line, connections to the LDAP server will fail. If you use echo to write the file (ie. using "echo secret >ldap.secret"), you do not have to worry about this.

In the previous revision, you'll note that this was a special proxy user we had created just for this situation (to read, and possibly write, the userPassword. This is no longer necessary since pam_ldap actually authenticates properly, and uses an anonymous auth bind, which our ACLs permit.

However, you must decide whether or not each client should have a copy of the root password stored in ldap.secret. I would advise against this, and only specify the rootbinddn on the LDAP server itself. With pam_ldap working, normal users can change their own password on any machine. The only time the rootbinddn is really used is if the root user needs to change another user's password. This should, more often than not, be a seldom situation. By keeping the rootbinddn specified only on the LDAP server, a user with root privileges on that server will be able to change user passwords. root users on other systems, however, will not be able to do so (and depending upon your situation, this may or may not be a good thing). You may want to provide the rootbinddn on a workstation that your LAN administrator users; that way they can change the password directly on their own workstation without logging in on the LDAP server itself. How you implement this is entirely up to you, however I don't believe it is necessary (and would be a security risk), to specify the rootbinddn and root password on every workstation using the LDAP server for authentication. I think it foolhardy and unnecessary. As long as their is one system that the LAN administrator(s) can reach that has the rootbinddn specified, they will be able to change user passwords as required. Remember, users can change their own passwords without needing a proxy user to accomplish it, so this is only required for administrators who may need to change a user's password (in the event is compromised, lost, etc.).

Finally, you should have the domain name and IP address of the OpenLDAP server defined in /etc/hosts.

Configuring NSS to use LDAP

The next step is to configure NSS to use LDAP. NSS stands for Name Service Switch and is a means to tell the system what sources you want referenced for certain information. The configuration file is /etc/nsswitch.conf and probably looks something like this:

passwd:     files nisplus nis
shadow:     files nisplus nis
group:      files nisplus nis
hosts:      files nisplus nis dns

This is somewhat abridged, but the point is clear. Here you can see that we are telling the system to use local files, then nisplus, then nis when attempting to reference passwd, shadow, or group information. We are using the same order but append dns to the search order for hosts. This is what tells most applications to use your /etc/hosts file prior to doing a DNS lookup. And, if you were using NIS, the system would already be configured to do local lookups then NIS-based lookups for user information. However, if you look in the file, there is no mention of LDAP at all. That is why we needed the nss_ldap package installed. Now, since we want to do user and host lookups via LDAP, we would modify the above entries to look like this:

passwd:     files ldap
shadow:     files ldap
group:      files ldap
hosts:      files ldap dns

This tells the system to use local files first, then do LDAP lookups, and, in the case of hosts, use DNS last. Now that you've made this switch, you can trim your /etc/hosts file, however it should contain the localhost definition and the IP/hostname of your LDAP server. Everything else can be removed and will be looked up via LDAP. Finally, you can test to make sure that this is in fact being done by using the getent tool like this:

[root@ldap]# getent hosts
[root@ldap]# getent group
[root@ldap]# getent passwd
[root@ldap]# getent shadow

getent will return the local information first (because we've defined the sort order as being files then ldap), and then the LDAP information. You should see in each instance the LDAP entries at the end. If you have the same user defined locally and in LDAP, you will see two entries for the dual-defined user. If this works as expected then congratulations! You can now setup each client the same way to reference the LDAP server for host and user information.

There is one caveat to this. If you use OpenLDAP to serve host information in place of /etc/hosts on the client systems, you must include the host information on the server in the /etc/hosts file directly. For some strange reason, even if you have OpenLDAP, on the server, defined to use "files ldap dns" for host information, the LDAP server is not referenced for doing reverse-IP lookups on the server. This means that if, for example, the IP address 10.0.10.25 connects to the LDAP server, the LDAP server will spend time trying to determine the hostname of that IP address, but will be unable to obtain it even if it is defined in the LDAP database itself. This will cause delays from 30 seconds to 1 minute. The solution is to have a "fully stocked" /etc/hosts file on the LDAP server that contains the identical information from the LDAP database. To obtain this information, you can use:

[root@ldap]# echo "127.0.0.1 localhost.localdomain localhost" >/etc/hosts
[root@ldap]# echo "10.0.10.20 ldap.mylan.net ldap" >>/etc/hosts
[root@ldap]# getent hosts >>/etc/hosts

Of course, change the IP address and domain name to suit your LDAP server. This will prevent any delays when clients are talking to the server, which is crucial for login information (who wants to sit at a login prompt for an extra 30 seconds?). You may want to do this each time you modify the Hosts database on the LDAP server in order to keep it updated.

Because of this limitation, you may prefer to setup an internal DNS server to handle the LAN as opposed to using LDAP for this. If you use something like BIND or djbdns internally, you can remove all references of host information from the LDAP directory, and remove the call to lookup in LDAP host information from nsswitch.conf.

Configuring PAM to use LDAP

Making PAM use LDAP is very easy. Mandrake Linux makes use of the system-auth facility of PAM, so you will need to modify /etc/pam.d/system-auth to make it LDAP-aware. The following is what your system-auth file should look like to make it LDAP-aware:

#%PAM-1.0
auth        required      /lib/security/pam_env.so
auth        sufficient    /lib/security/pam_unix.so likeauth nullok
auth        sufficient    /lib/security/pam_ldap.so use_first_pass
auth        required      /lib/security/pam_deny.so

account     required      /lib/security/pam_unix.so
account     sufficient    /lib/security/pam_ldap.so

password    required      /lib/security/pam_cracklib.so retry=3 minlen=4 \
dcredit=0 ucredit=0
password    sufficient    /lib/security/pam_unix.so nullok use_authtok \
md5 shadow
password    sufficient    /lib/security/pam_ldap.so use_authtok
password    required      /lib/security/pam_deny.so

session     required      /lib/security/pam_limits.so
session     required      /lib/security/pam_unix.so
session     optional      /lib/security/pam_ldap.so

Please note the two lines that wrap, using the standard "\" character to indicate continuation on a second line. These lines must be a single line in your system-auth file, and should not use the "\" character to wrap the line.

This inserts the pam_ldap.so module into the stack so that it will be referenced if a user is not found locally. This will not affect system users from being able to login and will allow you to have logins from users that are not explicitly defined in /etc/passwd.

If you want to be able to have the system create home directories on the fly (ie. you enter a user into the LDAP database but have not created a home directory for them on the system), you can use the pam_mkhomedir module. Replace the above "session" entries in system-auth with (and note the line wrap):

session     required      /lib/security/pam_mkhomedir.so skel=/etc/skel/ \
umask=0022
session     required      /lib/security/pam_limits.so
session     required      /lib/security/pam_unix.so
session     optional      /lib/security/pam_ldap.so

This will create a home directory for the user, on the fly, using /etc/skel as the skeleton directory (which is the standard when creating new system users anyways). [NOTE: This worked in 8.2, but pam_mkhomedir.so does not seem to work properly in 9.0+ (yet).]

One final note. There seems to be issues with the pam_unix.so and pam_pwdb.so modules. On a Mandrake Linux 8.2 or 9.0 system, the above works just fine. However, on a 9.1-based system, you will need to use pam_pwdb.so in place of pam_unix.so in the "auth" section of system-auth. If you use pam_unix.so, using the su tool will segfault. By using pam_pwdb.so, everything should work fine. (Sadly, this isn't the case in the (current as of May 13th, 2003) Mandrake Cooker, where su'ing works but logging in via ssh does not work, regardless of whether you use pam_unix.so or pam_pwdb.so). If you find something doesn't seem to work as expected (ie. everything but su works, or everything but ssh works, etc.), try fiddling with your system-auth file and interchange pam_unix.so with pam_pwdb.so, change the order that modules are called, etc. There doesn't seem to be any sure-fire way to indicate what definitive order to use, as PAM modules differ from Mandrake Linux version to Mandrake Linux version (and likely vary more from distro to disto).

At this point, any program that uses system-auth for authentication will also be using LDAP. This includes services such as SSH, possibly FTP, and others that authenticate against the system using PAM.

Finally, you will also need to modify your /etc/pam.d/passwd file as well in order to use the passwd program to modify passwords in the LDAP database. The file should look like this:

#%PAM-1.0
auth       sufficient   /lib/security/pam_ldap.so
auth       required	/lib/security/pam_pwdb.so shadow nullok

account    sufficient   /lib/security/pam_ldap.so
account    required	/lib/security/pam_pwdb.so

password   required	/lib/security/pam_cracklib.so retry=3 minlen=4  \
dcredit=0  ucredit=0 
password   sufficient   /lib/security/pam_ldap.so use_authtok
password   required	/lib/security/pam_pwdb.so use_authtok nullok \
md5 shadow

Please note the two lines that wrap, using the standard "\" character to indicate continuation on a second line. These lines must be a single line in your passwd file, and should not use the "\" character to wrap the line. In Mandrake Linux 9.0+, the passwd file uses system-auth, so you don't need to change anything.

Changing passwords used to be problematic, and this is due to how OpenLDAP is built by default. OpenLDAP compiles with it's own MD5 before using the system (crypt(3)) MD5, which makes OpenLDAP look for passwords in a different MD5 format than the crypt(3) MD5 format. Reversing this order fixes the problem and makes OpenLDAP use crypt(3) MD5 first, which means that we can now use pam_ldap to change passwords (the user's login password will be identical to the LDAP password). This has been patched in the Mandrake Linux OpenLDAP updates in MDKA-2003:009; other distributions may or may not have this patch applied. If you do not, you can download the openldap-2.0.27-slapd-Makefile.patch and patch your own OpenLDAP installation (a rebuild would be required).

You can also easily change passwords with Directory Administrator (see the Clients section near the end of this paper).

Host-based Authentication

If you plan to use OpenLDAP to authenticate users in a LAN, you may find situations where users should not be permitted on certain machines. As it stands, if the user supplies a proper password, they will be able to log into any machine that uses OpenLDAP for authentication. Using the pam_mkhomedir module, they will even have a home directory created for them. This may not be what you want. Assume for a moment that you have user Joe and user Jim. Joe has his own system, let's say workA, and Jim has his own, workB. Joe is a competent user and doesn't want Jim to have access to his machine, but Jim likes to have Joe on his computer for support. In this case, Joe must have access to workA and workB, whereas Jim should only have access to workB.

This is very easy to accomplish. In your /etc/ldap.conf file, you must include another keyword attribute:

# check for login rights on the host
pam_check_host_attr yes

This will tell the pam_ldap module to search the "host" attribute in a user's record to determine if he has access to the system. There is no way to determine what machine the user is originating from, but you can determine if they should have access to the machine they are attempting to log into. The easiest way to do this is to create another ldif file that looks something like this (following the scenario above):

dn: uid=joe,ou=People,dc=mylan,dc=net
changetype: modify
add: host
host: workA
host: workB

dn: uid=jim,ou=People,dc=mylan,dc=net
changetype: modify
add: host
host: workB

Now execute the ldapmodify command like this:

[root@ldap]# ldapmodify -H ldap://localhost -D "cn=root,dc=mylan,dc=net" \
-x -W -f host-auth.ldif

Assuming that the file containing the above statements is called host-auth.ldif. This will modify the records for both Joe and Jim, allowing Joe access to workA and workB, and Jim access only to workB. You must use the FQDN for each host as the host will call gethostbyname() for it's own name (you can see the same result using the "hostname" command). If the hostname returned matches one of the host attributes, the user will be allowed to login. If not, PAM will reject the login attempt.

One thing to note: If you do not have the pam_check_host_attr keyword in your ldap.conf, that host will not check the host attribute and will allow any LDAP-authenticated users to login. So if you control your own machine (as root) and are part of a LAN that uses LDAP for authentication, be sure you modify your ldap.conf and enable this. In a LAN situation with, say, 500 users, without this line all 500 users will be able to login to your system. Of course, if your user entry does not contain a host attribute with your hostname, you won't be able to login either.

This should also illustrate the importance of minimizing what attributes users can change in their LDAP entries. For instance, our slapd.access.conf indicates that the user can only modify their userPassword and mail attributes. If you had included something like this instead:

access to *
	by self write
	by * read

You would be allowing the user to add their own host entries. This means they can arbitrarily give themself access to any other host, provided they know the FQDN for that host. Even if pam_mkhomedir is not used, they can still obtain a bash prompt on that host, without administrator approval or knowledge.

Configuring Samba to use LDAP

You can also make Samba use LDAP for authentication. In order to do this, you must rebuild Samba so that it stores smbpasswds in LDAP. Unfortunately, Samba can use either LDAP or the smbpasswd file, and not both. By default, it uses the smbpasswd file. To enable the LDAP support, you must rebuild the src.rpm using:

[root@ldap]# rpm --rebuild --with ldap samba-2.2.3a-10mdk.src.rpm

The Mandrake Linux Samba packages have support for LDAP, provided you build it this way. You will find the Samba source RPM package on your Sources CD, or on any FTP mirror.

Once you have built Samba with LDAP support, you must modify your /etc/samba/smb.conf file to use LDAP. The important things to include in the [global] section are:

ldap admin dn = cn=root,dc=mylan,dc=net
ldap server = 127.0.0.1
ldap suffix = dc=mylan,dc=net
ldap port = 389
ldap ssl = start tls

By default, TLS/SSL support is enabled anyways so it isn't necessary to specify the ldap ssl key. If you do not plan on using TLS/SSL, you can disable it by setting it to "off". TLS/SSL is discussed a little later on. For the purpose of Samba, you should know that by default it will try to talk TLS over port 636, which is the standard LDAPS port (LDAP+SSL). However, if you enable TLS, it uses port 389, which is the standard LDAP port. If you wish to use SSL on port 636, you must have "ssl on" instead of "ssl start_tls" in your slapd.conf file. For the purposes of this tutorial, TLS is started, so you must tell Samba to use port 389 otherwise you will consistently get errors trying to bind to the LDAP server.

You should also have the latest samba.schema included in your slapd.conf file. It will default to the /usr/share/openldap/schema/samba.schema which you do not want to use. You will want add to your slapd.conf file instead:

include /usr/share/doc/samba-doc-2.2.3a/examples/LDAP/samba.schema

The next step is to give Samba a password for the admin dn you defined previously (cn=root,dc=mylan,dc=net). This can be done using smbpasswd:

[root@ldap]# smbpasswd -w [password]

Unfortunately, you have to pass the password on the command line, so be sure to clear your history once you have done so. The admin dn password is stored in the /etc/samba/secrets.tdb file. Now you will have to add Samba accounts. The existing users do not impact the Samba password settings at all, so you will need to add each user manually using:

[root@ldap]# smbpasswd -a [username]

There are some migration scripts in the /usr/share/docs/samba-doc-2.2.3a/examples/LDAP directory but they are not up-to-date and may not work properly. The only attributes that need to be added to existing users are the rid, lmpasswd, ntpasswd, and sambaAccount attributes, which is what smbpasswd should do.

On a side note, if you plan to have Samba authenticate against the LDAP database, you should protect the lmPassword and ntPassword attributes the same as you would the userPassword attribute in slapd.access.conf.

For more detailed information on configuring Samba with LDAP, which includes covering topics such as configuring a Samba BDC, password changing, expiry, and synchronization, please read Buchan's excellent Implementing disconnected authentication and PDC/BDC relationships using Samba and OpenLDAP article, as well as Jim's Implementing a Samba LDAP Primary Domain Controller Setup on Mandrake 9.x article.

Using SSL/TLS with OpenLDAP

Every example previously shown with ldapadd, ldapmodify, ldapsearch, and so on assumed that the LDAP server was running without any means of encrypting traffic (or basic authentication). On a local system, this would be ok, but using OpenLDAP for authentication obviously shines when many machines are involved. Passing password data in the clear definitely is not a good idea, so OpenLDAP provides the means to access the server using SASL or TLS. Since TLS is the easier to setup, we'll look at using it instead of SASL.

TLS uses SSL, so it also uses certificate files and keys. TLS provides proof of server identity and protection of data in transit. Because of this, it is almost necessary when plaintext passwords or other data may be transmitted over a network.

To begin using TLS, you must create a certificate and a key file. The ldap initscript in Mandrake Linux will create a self-signed certificate for you to use; the file is /etc/openldap/ldap.pem. To enable TLS support for both the server and the client, you must first modify slapd.conf like this:

TLSCertificateFile /etc/openldap/ldap.pem
TLSCertificateKeyFile /etc/openldap/ldap.pem
TLSCACertificateFile /etc/openldap/ldap.pem

Then, in your /etc/ldap.conf file, comment out the default of turning SSL off, and turn TLS on like this:

ssl start_tls
#ssl off

Once you restart the server, TLS will be used on the standard LDAP port of 389. The LDAP server will handle TLS and unencrypted traffic on the same port.

If you don't want to use the pre-generated certificate, but generate your own (using a Certificate Authority-signed cert instead of a self-signed cert), the following will illustrate how to accomplish it. Execute on the LDAP server:

[root@ldap]# openssl genrsa -out ldap.key 1024
[root@ldap]# openssl req -new -key ldap.key -out ldap.csr

You will have to fill in all the appropriate information for the CSR (or Certificate Signing Request). For the Common Name field, you should use the exact name that clients will use when contacting the server, usually the FQDN. You can either have a registered Certificate Authority like Thawte sign your CSR or you can create your own CA to sign it. If you already have your own CA, you would sign the CSR using:

[root@ldap]# openssl x509 -req -in ldap.csr -out ldap.cert -CA ca.cert \
-CAkey ca.key -CAcreateserial -days 365

The resulting file, ldap.cert is the certificate for the LDAP server. If you do not have your own CA setup, you can easily do so using:

[root@ldap]# openssl genrsa -des3 -out ca.key 2048
[root@ldap]# openssl req -new -x509 -days 365 -key ca.key \
-out ca.cert

If you wish to examine the contents of the LDAP certificate, you can use:

[root@ldap]# openssl x509 -in ldap.cert -text -noout

The LDAP server needs access to a copy of it's own certificate and key files, as well as the CA certificate. The key file must only be readable by the server process, so make it mode 0400 and owned by user ldap, group ldap. To make the server aware of these files, edit your slapd.conf file and include:

TLSCertificateFile /etc/ssl/openldap/ldap.cert
TLSCertificateKeyFile /etc/ssl/openldap/ldap.key
TLSCACertificateFile /etc/ssl/openldap/ca.cert

Now you must restart the LDAP server by issuing a "service ldap restart" command.

This will now provide transport-level encryption for your LDAP traffic, which will keep the data secure across the network.

Using webmin with OpenLDAP

For those of you who use webmin, you can use it to ease some of the user management pains of using ldapmodify on the command line. webmin comes with three LDAP modules in the "Others" section: LDAP Browser, LDAP Manager, and LDAP users and group administration.

In order for these modules to work properly, you must install the perl-ldap package (which comes with Mandrake Linux), and the perl-Convert-ASN1 package (which does not, but it is available in contribs). To install perl-ldap from your installation CDs, just use:

[root@ldap]# urpmi perl-ldap

If you purchased a Power Pack or ProSuite, or can find a contribs mirror for your particular distribution, you can install the perl-Convert-ASN1 module from your installation CDs or from an external mirror site.

Once both modules are installed, you can click on the first module, LDAP Browser. The first time you do this, it will report that it cannot connect to the LDAP server and give you an error. Click on the "Module Config" link to configure the module. The settings you select will look something like this:

LDAP server host name or IP			127.0.0.1
LDAP server port number				389
Root DN for your directory tree			dc=mylan,dc=net
LDAP administrative user credentials		cn=root,dc=mylan,dc=net
LDAP administrative password (clear text)	secret

When you save these changes, you will see a listing of all the Distinguished Name entries in the database and will be able to manipulate these entries.

The next module is the LDAP users and groups administration module. Again, you will get an error when you first click on the module, so enter "Module Config" once more. The settings you will select will look something like this:

LDAP server host name or IP			127.0.0.1
LDAP server port number				389
Root DN for your directory tree			dc=mylan,dc=net
LDAP administrative user credentials		cn=root,dc=mylan,dc=net
LDAP administrative password (clear text)	secret
LDAP users directory				ou=People,dc=mylan,dc=net
LDAP groups directory				ou=Group,dc=mylan,dc=net

Once you have saved the configuration, the index page of the module will now list the LDAP users and groups on the system. To make it easy to create new users, you can define a local template. This will allow you to select which attributes new users should have, and allow you to assign some default values to these attributes. You will have to save the template to a file on the webmin server's filesystem; ie. you could save it as /etc/openldap/ldap-webmin-user.template or something.

Now if you select to create a new user, you will be asked for the new user's full name, and the attributes you wish to add to the users profile, or the template file to use. Unfortunately, the module always seems to give errors, with or without a template, so it's not quite ready for use in adding new users. However, it does make a great browser for users and groups, and will allow you to modify a user's attributes easily enough.

The final module, LDAP Manager, does not work without additional perl modules installed, like the perldap module which currently is not packaged due to a requirement on the Mozilla LDAP SDK. It would be nice if the module was re-written to work with perl-ldap instead.

Other OpenLDAP Clients

Of course, you can also use GUI tools to handle your LDAP data. One tool that comes with Mandrake Linux is called Directory Administrator. To install it from your installation CDs, execute:

[root@ldap]# urpmi directory_administrator

The latest version as of this writing is 1.3.5 and has some good bug fixes. You can rebuild the latest version from cooker or download it from the Directory Administrator website. It has been included in Mandrake Linux since version 8.2.

Another good tool is gq, which is available in contribs. This will allow you to view your entire LDAP database in a tree view and modify all aspects of it. It's a very comprehensive and powerful GTK-based LDAP tool.

You can also use the MandrakeSoft tool userdrake to modify LDAP users.

Finally, you need to do the worst part, the paperwork, specifically documenting whatwas done. To make this easier, you can supply your own values below, and when you hit"Apply", the page will be regenerated with your own settings in all theexamples.

CustomizeCurrent value
Basics
Base DN:dc=mylan,dc=net
Mail Domain:
Details
Root DN:cn=root,dc=mylan,dc=net
Samba DNs:cn=samba,dc=mylan,dc=net
Mail Host:mail.mylan.net
LDAP Master server:ldap.mylan.net
LDAP Slave servers:ldap1.mylan.net;ldap2.mylan.net
Which LDAP slave to show:
Netbios Domain Name :mylan

Credits and Links

I would be a very bad person if I didn't give some thanks here. Because this was my first foray into the world of LDAP, a lot of questions were asked and I'd like to thank the folks on the discuss@mandrakesecure.net mailing list, as well as Todd Lyons and Buchan Milne for their help. As well, to all of those who posted great comments on the first revision of the article; those comments are very helpful. Hopefully this revision will incorporate enough of the Mandrake-specific comments and provide a good help to those who are contemplating setting up an LDAP-based authentication system. Finally, I would like to thank Jose Permuy for pointing me to the patch to fix the MD5 issue with OpenLDAP, which was the whole source of the problem with pam_ldap we previously had.

Here are some other OpenLDAP-related links that may help to further your understanding of OpenLDAP, both for authentication purposes and just general usage. Some of them are slightly dated, but a lot of the information is still valid; you may just have to pick and choose what information you apply to anything you wish to accomplish.

User Contributed Notes Add Note
vdanen@mandrakesoft.com
2003-07-10 10:30 PM
I approved Brian's last comment because it may help out others that encounter this problem however I have never come across it and I use devfs on all my machines. I have three 9.1-based LDAP setups (2 x86, 1 PPC) and all three use devfs and never have I see that problem. I also have an additional 3 9.0/x86-based systems that do not require this, as well as one 8.2-based. All use LDAP, and all use devfs. I do not believe that this is due to a conflict between nsswitch, devfs, or LDAP but something different (although I'm unsure as to what that may be and this will not be turned into a means of troubleshooting... Brian, please use the discuss list to find out the real answer to the problem rather than a hack). There is no reason why you shouldn't be able to use LDAP and devfsd together like many others do.
brian@brie.com
2003-07-10 9:28 PM
I followed this document, implementing the LDAP auth, and then when I rebooted,
DevFS would not start and my system could not find see my root partition.
I checked on the discus mailing list, and someone suggested that I change
the boot parameter in /etc/lilo.conf so that it has nomount instead. That seemed to
do the trick. I repeated this howto process a couple times, and I noticed this
problem would occur after making the changes to /etc/nsswitch.conf. I am
not sure why this impacts DevFS. I am using MD 9.1 with the updated LDAP
packages on x86 architecture.

Changes to lilo.conf (run lilo after making these changes)

Before change:
append="devfs=mount acpi=off quiet"

After change:
append="devfs=nomount acpi=off quiet"

brian@brie.com
2003-07-07 11:22 PM
You indicate in your article that you have two choices for conversion, but you
don't delineate where the choices are in the article. Below, I included the excerpt
where you indicate that you have two choices. I am assuming that the first choice
is migrate_all_online.sh method, and the second choice is to use the indvidual
methods starting with "./migrate_base.pl >base.ldif" followed by the migrate_hosts,
etc.

"Now you have two choices. You can migrate everything on the current system
into the LDAP database, including hosts, groups, users, networks, services, etc."

I would think that you would distinguish the two choices with headers. I already
ran the migrate_all_online script, and it appears I did method one without realizing
it.

Method 1 (All in one)
[root@ldap]# ./migrate_all_online.sh

Method 2 (Indidual steps)
[root@ldap]# ./migrate_base.pl >base.ldif
...
[root@ldap]# ./migrate_hosts.pl /etc/hosts hosts.ldif

In addition, I believe you have an error with your search command.
Below is the command from your article.

[root@ldap]# ldapsearch -LL -H ldap://localhost -b"cn=root,dc=mylan,dc=net" -x "(cn=wrkstation)"

I believe it should be written as follows. At least it worked for me.

[root@ldap]# ldapsearch -LL -H ldap://localhost -b"dc=mylan,dc=net" -x "(cn=wrkstation)"

I am still working on this stuff, so if I am mistaken, please forgive me.
LDAP authentication is the stuff that will really bring Linux thundering
into the networked business environment.




lrivera@racsa.co.cr
2003-05-21 1:11 PM
The above segfault exists in Mandrake 9.1 with pam_ldap > 153. Versions newer than this have very few bugfixes and instead include tons of stuff to help them compile in HPUX, AIX, etc. You may want to try some of these.
psilva@dcc.online.pt
2003-05-16 9:04 AM
It's good to see some update in this tuturial, it was this one I used for deploying ldap at work :-)
Still I'd like to see some more improvement like:
- configuration of timelimits and that really could mean
- why is ldap limited to 1024 file descriptors
- configuration of two subtrees of users in the same server
- replication
I think this is something that many people are strugling to do (as I did...)
Never the less I still think this is the best tuturial i've seen for deploying ldap and it's from Mandrake :-))), well done!
ying@yingternet.com
2003-05-13 10:02 PM
For some strange reason, su to root always result in segfauls unless i remove
account sufficient /lib/security/pam_ldap.so.

the following is my /etc/pam.d/system-auth on 9.1/x86

auth required /lib/security/pam_env.so
auth sufficient /lib/security/pam_unix.so likeauth nullok
auth sufficient /lib/security/pam_ldap.so use_first_pass
auth required /lib/security/pam_deny.so

account required /lib/security/pam_unix.so
#account sufficient /lib/security/pam_ldap.so

password required /lib/security/pam_cracklib.so retry=3 minlen=4 dcredit=0 ucredit=0
password sufficient /lib/security/pam_unix.so nullok use_authtok md5 shadow
password sufficient /lib/security/pam_ldap.so use_authtok
password required /lib/security/pam_deny.so

session required /lib/security/pam_limits.so
session required /lib/security/pam_unix.so
session optional /lib/security/pam_ldap.so
Add Note