Using OpenLDAP For Authentication

Revision/Modified: Jun 18, 2002
Author: Vincent Danen

 

[NOTE: The second revision of this article is now available: Using OpenLDAP for Authentication; Revision 2.]

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 done using Mandrake Linux 8.2, and while all supported versions of Mandrake Linux should operate in the same way, your mileage may vary. The information here should be enough to get you started, if not help you finish everything off. Keep in mind that some package upgrades and/or tweaking may be required to get older versions of Mandrake Linux to function the way I will attempt to describe.

On a final note, this piece may grow in the future. OpenLDAP, due to it's nature, can be used for far more than simple user authentication. We will look at it for system access (ie. user logins), but it can also be used for other forms of authentication, such as with squid, among other servers and services. If you have any information to contribute, please feel free to get in contact with me. One other note: I am a complete newbie with OpenLDAP and this is really my first experience with it, so some of what I am illustrating may not be the optimal means of using OpenLDAP. I welcome all criticism and suggestions.

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 8.2 system) are:

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.

Finally, the versions of nss_ldap and pam_ldap that ship with Mandrake Linux 8.2 are a little dated. You can obtain newer versions from cooker or from rpmhelp.net.

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 "mylan.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

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. 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 "[email protected]". 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.

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=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: [email protected]
mailRoutingAddress: [email protected]
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: [email protected]
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: [email protected]
mailRoutingAddress: [email protected]
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: [email protected]
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.

The Proxy User

Before progressing further, you will need to create a special user called the proxy user. This user will be used to read the userPassword entries in order to supply that data to clients performing authentication.

The first step is to create a new ldif file (I called it proxy.ldif) which contains the following:

dn: cn=proxyuser,dc=mylan,dc=net
cn: proxyuser
sn: proxyuser
objectclass: top
objectclass: person
userPassword: {MD5}Xr4ilOzQ4PCOq3aQ0qbuaQ==

The userPassword is "secret" in MD5 format. Import this file using:

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

You now have to give your proxy user access to read the userPassword attribute. This can be done by modifying slapd.access.conf's userPassword clause to look like this instead of that noted previously:

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

Now the proxy user will be able to see userPassword attributes for all users. To test if this works, first restart the LDAP server, and then execute:

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

This should return the value of the userPassword, or the user's encrypted password. Note that it will not look exactly the same as it would in a shadow password file entry. As long as it returns the userPassword attribute, we're ok.

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=proxyuser,dc=mylan,dc=net
scope one
pam_filter objectclass=posixaccount
pam_login_attribute uid
pam_member_attribute gid
pam_template_login_attribute uid
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), but 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. 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). 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 gave your proxy user, and the dn should be that of the proxy user (ie. cn=proxyuser,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.

You now have to make a serious decision. The fact that ldap.secret is a cleartext password in order to read the userPassword is not ideal. However, providing read-only access to userPassword minimizes the impact (somewhat). This means that anyone who has access to read the ldap.secret file will be able to obtain every user's userPassword attribute. Since the file should only be accessible to root, this is no different than root having access to /etc/shadow; the end result is the same: either way, a person who has root access has access to the user's encrypted passwords.

The drawback to having the proxy user only have read access is that the passwd tool will not work as it should. User's will be unable to use passwd to change their own passwords. If the proxy user has write access, then the passwd tool will work (provided PAM is correctly configured, which we will see in a moment). So if you choose to give the proxy user write access, you simple need to change the "read" to "write" in slapd.access.conf. The problem with this is that while a person with root access on a client machine (note that they do not have to have root access on the LDAP server, it is sufficient that they have root access on their own client machine), and is able to view ldap.secret will likewise be able to change any user's password. You may not want this! For this reason, our example ACL for the proxy user has read access and not write access.

So where might you want to give the proxy user write access? If you run your own LAN, in your home or at work, and you are the only person with root access on each client system, you may want to give the proxy user write access. It does make changing passwords much more convenient and easy. The drawback is if numerous other people have physical access to the systems, they can boot from the Mandrake Linux install CDs into rescue mode, or boot into single mode via LILO, and view the contents of ldap.secret. So it really is a matter of how secure you feel your LAN is. If it is for one or two people to use locally, tightly firewalled from the internet or other networks, and closely supervised, it might be feasible to give the proxy user write access. If you think that an unauthorized person may be able to gain access as root, and thus able to view ldap.secret, keep the proxy user's access rights as read-only. Don't take a chance with someone else changing your user passwords on you. Also note that the proxy user can only view or modify the userPassword attribute; it has no rights to any other account information. If you opt to keep the proxy user with read access, users will have to manually modify their passwords with ldapmodify.

Finally, using the proxy user will only allow root to change user passwords. User's will be unable to do so unless they have an LDAP password, which they will not, by default. This is problematic because users will have to request a password change via the adminstrator of the LDAP server, and will be unable to do it themselves.

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.

Configuring PAM to use LDAP

[NOTE: I do not believe PAM is required to make use of OpenLDAP for authentication purposes. It is definately required to make password changes using the passwd command, provided the proxy user has write access to passwords. You do not need to make the changes to system-auth for the actual authentication to work provided that nss_ldap is installed and is used to get shadow information (as the pam_unix module will use this information). Inserting the pam_ldap module into the system-auth stack, however, does not seem to impact performance or useability. I do believe that it is useful for authentication somehow, but all of my tests so far indicate that it is not being used (I suspect that anoymous authentication is done with the pam_ldap module, but it does not seem to work properly, or I have configured it incorrectly). If someone can enlighten me, or I find out how to make pam_ldap work properly for authentication, this document will be modified to reflect that. However, if you configure PAM as described below, please note that it is not being used for the actual authentication, but that nss_ldap and pam_unix are accomplishing this.]

Making PAM use LDAP is very easy. Mandrake Linux 8.2 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 (as far as PAM goes; remember, if you have nss_ldap installed, pam_unix will do all of the work for you and you do not need to have pam_ldap inserted into the system-auth stack (in other words, you do not need to modify system-auth if you really don't want to)):

#%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     [default=bad success=ok user_unknown=ignore \
service_err=ignore system_err=ignore] /lib/security/pam_ldap.so

password    required      /lib/security/pam_cracklib.so retry=3
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).

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.

Changing passwords, as briefly mentioned earlier, is problematic. There seems to be no clear solution to how to accomplish this in an easy manner, and some tests indicate sporatic results. In one scenario, using passwd would change both the LDAP and unix passwords, in another it wouldn't. If you set the user's LDAP password with ldappasswd, and then the user tries to change their password afterward, they can use their LDAP login password once to change their unix password, but it seems as though the LDAP password gets written in an incompatible format and differs from the unix password (although in the other scenario, both were set to the identical password and a user could merrily change their password from that day forward).

Until I can nail down an exact way to have user passwords changed on the commandline, I'm afraid that your best bet is to only allow the administrator to change user passwords with ldapmodify or another similar tool. While this is far from a good scenario, perhaps someone reading this can indicate to me how this should be accomplished (working solutions only please!), because despite many days of testing and research, I have not found a consistent solution.

Fortunately, you can 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 8.2 Samba package has 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=danen,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 commandline, 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.

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 definately 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 8.2 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

I chose to use /etc/ssl/openldap to contain the files because I believe in all SSL certificate files being centralized (and this will be the default location of SSL certs in the next version of Mandrake Linux). You may choose to place them somewhere else, like /etc/openssl/keys or another location. 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 8.2), and the perl-Convert-ASN1 package (which does not). You can obtain perl-Convert-ASN1 from rpmhelp.net or from contribs. To install perl-ldap from your 8.2 installation CDs, just use:

[root@ldap]# urpmi perl-ldap

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 8.2 is called Directory Administrator. To install it from your installation CDs, execute:

[root@ldap]# urpmi directory_administrator

The version of Directory Administrator that comes with Mandrake Linux 8.2 is version 1.1.3, and this is quite old. The latest version as of this writing is 1.1.8 and has some good bug fixes. You can rebuild the latest version from cooker or download it from rpmhelp.net. Be sure to download a version greater than or equal to 2rph from rpmhelp.net or 2mdk from cooker as 1.1.8 "out of the box" will not properly save MD5 passwords if you change a user's password. As well, in the Password policies tab, be sure to change the password expiry date; Directory Administrator mistakenly assumes a value of -1 (which means inactive) means the current date; if you don't change this value, users will be locked out until you fix it. If you try to disable it within Directory Administrator, the program will segfault. You will also need to turn off the "Use the authPassword attribute when saving passwords" in the Compatibility tab in order for the passwords to be updated properly. So, there are a few annoying quirks in the 1.1.8 release, but hopefully the next version will be better.

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. Where Directory Administrator only allows you to administrate user and group entities, gq will let you modify everything. It's a very comprehensive and powerful GTK-based LDAP tool.

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

Credits and Links

I would be a very bad person if I didn't give some thanks here. Because this was my first forray into the world of LDAP, a lot of questions were asked and I'd like to thank the folks on the [email protected] mailing list, as well as Todd Lyons and Buchan Milne for their help.

Here are some other OpenLDAP-related links that may help to further your understanding of OpenLDAP, both for authentication purposes and just general useage. 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.

On a final note, this document will be undergoing a revision in the future as I plan to document using SASL for authentication. However, all of the information provided should be sufficient to get you up and going using basic authentication, encrypted with TLS to provide a secure authentication server without password exposure. Using SASL will provide enhanced security, but also adds another level of complexity to the entire operation. I also intend to find an easier way for users to change their own password as the current situation really doesn't sit well with me. Stay tuned!


This story found at http://www.mandrakesecure.net/en/docs/ldap-auth.php.

Last modified: Thu Mar 11 22:23:47 2004 

Linux is a registered trademark of Linus Torvalds. The contents of these pages and all images are copyright by their respective owners. All the contents of the pages - texts and logos - are copyrighted by MandrakeSoft SA 2001-2002.