Creating my own LDAP Directory – part 3
Introduction
It has been a while since I wrote the part two, mainly because of other priorities.
Let’s see what we got so far (remember the brainstorm from part one):

In part one I created got LDAP running, created a directory and found some use for it as global address book, in part two the apache configuration was done. So those are finished.
Although we got a working directory, it’s not finished yet: it’ll grow (more element will be created, and existing elements will get additional elements).
Now I’m going to focus on the FreeBSD part (actually this will be working on all kind of Unices, but I’ve only tested in on FreeBSD).
Suppose you have several users and several FreeBSD machines. In the traditional way, if you want all users to log on to all machines, you have to create all user accounts on all machines. If you have only few machines, like I do it can be managed, but one of the major drawbacks is that you actually have as much user databases as you have machines. Users themselves are then responsible for synchronizing passwords between the different machines (assuming users always want one username/password for logging in anywhere).
If you have strong password policies (like maximum lifetime of a password), and users do logon to other machines exceptionally, then those passwords will definitely get out of sync.
Here’s where LDAP can help: have FreeBSD authenticate users against a central directory, and all your (and your users’) problems will be over. There will be no need for creating users on the machines itself, and the nice thing is: you can still have control over what machines users are allowed to log in.
It’s even possible to create a home directory for that user on first login, I’ll describing that below. What I will not yet describe (maybe in a later article) is mounting a remote home directory automatically during login.
Preparing FreeBSD
Configuring LDAP logins on FreeBSD is not that hard, because the main facility for this is already in place: PAM, or Pluggable Authentication Service. Whenever an application can use PAM (and lots of applications can), then with all kind of plugins you can do all kind of cool stuff. For instance, one of the default PAM modules (at least, in FreeBSD 7.2) is pam_passwdqc.so (all standard PAM modules are stored in /usr/lib) facilitates a configurable, although simple, password policy, like minimum allowed password lengths. Other PAM modules are available from the ports collection.
We are going to need three of them: pam_ldap, pam_mkhomedir and nss_ldap. The latter is no PAM module, but is needed for being able to authenticate. The application I’m going to focus on is login, but it can also be applied to ssh, ftp, telnet and other services.
pam_ldap and nss_ldap depend on the cyrus-sasl and openldap-sasl-client, so these will be installed also if you had not already. In my case I installed the openldap-client, without the sasl-part, so installation failed. I had to reinstall the openldap-client. I used portupgrade for that:
portupgrade -fo /net/openldap24-sasl-client net/openldap24-client
pam_mkhomedir doesn’t need any configuring, but the other two will. We are going to create one config file in /usr/local/etc, called ldap.conf, and create a link to another file in the same directory, called nss_ldap.conf
The easiest way to get started is to copy the ldap.conf.dist to ldap.conf (as root, of course)
cd /usr/local/etc
cp ldap.conf.dist ldap.conf
ln -s ldap.conf nss_ldap.conf
My ldap.conf looks like this (I’ve removed all the commented entries, since they’re not relevant, or not changed from the default):
host ldap.boosten.org
base dc=boosten,dc=org
uri ldap://192.168.13.15/
scope sub
pam_filter objectclass=posixAccount
pam_login_attribute uid
pam_min_uid 1000
pam_max_uid 5000
nss_base_passwd ou=people,dc=boosten,dc=org?one
nss_base_group ou=groups,dc=boosten,dc=org?one
From the top:
host ldap.boosten.org
base dc=boosten,dc=org
uri ldap://ldap.boosten.org/
scope sub
In this part I define where pam_ldap and nss_ldap can find the LDAP server by defining host and/or uri (I think that one of them actually is enough, and I cannot recall why I ended up with the two of them, but it doesn’s hurt either), and where the search for users should start. I chose the top of the directory as starting point, and defined the scope to by ‘sub’, which means ‘look in sub containers as well’.
pam_filter objectclass=posixAccount
pam_login_attribute uid
These two are to be remembered: whenever a user logs in to the host, pam_ldap will compare the login name to the attribute uid, defined in LDAP, but it will only look for that uid in elements defined as objectClass posixAccount. It’ll search for other things as well, I’ll come to that later.
pam_min_uid 1000
pam_max_uid 5000
change these parameters as you like: they describe the minimum and maximum user id (called uid, which is rather confusing, because we also defined uid as login name).
Let me clarify: in Unix, you’re nothing but a number. Yes, it’s really true. All permissions on files are assigned to numbers, and it are the smart tools that map those numbers, actually called uid’s to real names: login names. Since LDAP wasn’t designed solely for Unix, the designers didn’t follow Unix naming conventions.
So what’s called a login name in Unix, is an uid in LDAP and the uid in Unix is the uidNumber in LDAP.
Back to those parameters: they let you define what the minimum and maximum uid(Number) is, that can login to that host, using LDAP authentication. I guess it’s one way of preventing someone logging into the host using a service account, or root (uid 0) when that person somehow got access to the LDAP server and modified stuff.
nss_base_passwd ou=people,dc=boosten,dc=org?one
nss_base_group ou=groups,dc=boosten,dc=org?one
These parameters tell nss where to look for groups and passwords. The “?one” defines whether nss should look in sub containers or not. Since I don’t have sub containers underneath ou=people or ou=groups, I use “?one”, otherwise use “?sub”.
The next thing to edit is the /etc/nsswitch.conf. From the man page for that file:
The configuration file controls how a process looks up various databases containing information regarding hosts, users (passwords), groups, etc.
I changed two entries (for now, but maybe more in future):
group: ldap files
passwd: ldap files
Roughly, the entries mean something like: “Dear system: look for information on groups or users in the LDAP directory (as configured in the nss_ldap.conf file described earlier), and if you cannot find it in LDAP, look in the traditional files” (which are /etc/passwd and /etc/group in this case).
The order in the file describes the order of looking: look first in LDAP and then in local files. You could do it the other way around, if you like, but that’s up to you.
The last file to change is the PAM configuration. These configuration files are located in /etc/pam.d/, and that’s where we go. my directory looks like this:
README cron ftpd imap login passwd rsh su telnetd
atrun ftp gdm kde other pop3 sshd system xdm
Services installed from ports (like sudo) will put their PAM configuration files in /usr/local/etc/pam.d/.
Like mentioned earlier, for this tutorial I’m going to change the login service only, so logging in on the console. If you want to apply this to sshd for instance, it’ll also work, however you have to make sure that your sshd uses PAM for authentication (/etc/ssh/sshd_config). Mine doesn’t!
First a small PAM course. a PAM configuration file for a service consists of four module types (max): auth, account, session and password.
An auth module prompts for a password to authenticate that the user is who they say they are, and will set any credentials.
An account module handles the authorization (not authentication), based on time, resources etc. If a restriction has been defined for a user in LDAP, like whether he’s allowed to login to a specific host or not, whis module type will handle this.
A session module is responsible for the housekeeping tasks before and/or after login.
A password module will be responsible if a user changes his password.
A PAM configuration file can have more than one of each module type. This is very important for us, since you want to have a way of logging into the host whenever LDAP is not available, using a local account (for instance root).
Each module type calls a PAM module, and has several control flags: required, requisite, sufficient, binding and optional.
Required means that the calling PAM module should report a success, or the entire request will be denied. For instance you have two account modules one checks if the account has not been deactivated, the other one checks whether the user is allowed to log in on that particular host (it’s just an example), then both modules would have to be successful, or the user is not allowed to log in.
Requisite means the same as required, however no following PAM modules are executed whenever this PAM module reports failure.
Sufficient means that if this module reports success, no other PAM modules are run, and the request is allowed (assuming any previous required or requisite modules reported success as well). When a sufficient module reports failure, then the request may still be allowed, whenever a following module reports success. This is the one we want.
Binding is the same as sufficient, however on failure the entire request fails.
Optional is ignored, unless the other modules return PAM_IGNORE.
PAM modules can also have arguments, of which the most useful probably is ‘debug’.
Let’s have a look at my /etc/pam.d/login configuration file. I’ve highlighted the modifications (additions, and that’s the beauty of it):
#
# $FreeBSD: src/etc/pam.d/login,v 1.17.8.1 2009/04/15 03:14:26 kensmith Exp $
#
# PAM configuration for the "login" service
#
# auth
auth sufficient /usr/local/lib/pam_ldap.so no_warn
auth sufficient pam_self.so no_warn
auth include system
# account
account requisite pam_securetty.so
account required pam_nologin.so
account sufficient /usr/local/lib/pam_ldap.so
account include system
# session
session required /usr/local/lib/pam_mkhomedir.so
session optional /usr/local/lib/pam_ldap.so
session include system
# password
password sufficient /usr/local/lib/pam_ldap.so md5
password include system
Several lines hold the word ‘include’: a file called system is included, because obviously some services share common parameters. I could have made my alterations to that system file, and then all services which call this system file would benefit from my LDAP directory, however for educational purposes I only chose one service to be altered. What’s important is the order: if you have several auth modules, they’re read from top to bottom, and if you would put the unix authentication (which is defined by pam_unix.so in the system file) before the LDAP authentication (assuming you would not alter the control option from required to sufficient), and a user without local account would try to log in, he would never been able to enter the system, because when a required module fails, the entire request would fail.
Because of that reason, my auth starts with:
auth sufficient /usr/local/lib/pam_ldap.so no_warn
if login is able to match the given user account and password with the information found in LDAP, then login can stop processing other auth modules, if no valid account is found, look further in the other PAM modules.
Same for account: is the information needed in LDAP, use it, otherwise look somewhere else. You might notice the requisite and required modules in the account section. pam_securetty.so checks whether the tty is secured or not, and the user trying to login is superuser (root). If he is, and the tty is insecure, then logging in is not possible. pam_nologin.so checks if the file /etc/nologin exists, and will fail if it does and the user is not superuser (root), or is part of a class which doesn’t have to comply to that rule (man login.conf). But be assured that a normal user has to comply in a default installation.
The most important session module is the pam_mkhomedir.so: after a user has been granted login access to the host, this module checks whether a home directory exists for that user (as defined in the homeDirectory attribute in LDAP) and creates it if it’s not.
The session module type also would be the place where you would mount a remote drive to that home directory (not described here).
The password module type lets you specify where an authentication (in this case a password) would be changed if a user types passwd.
This concludes the host part. The beauty of it is that you can copy those three files to all other hosts in your network: all configurations can be the same.
Differences in authorizations can be established from within the LDAP directory.
Preparing LDAP
If you followed my tutorials so far, you’ll notice that you are not able to log in to a host using LDAP (assuming no local user equivalent exists on that host).
The reason is that although we have a working LDAP directory, it doesn’t contain the information the host is looking for. I log everything (my syslog.conf would contain “*.* /var/log/complete” if I would use syslog, so I noticed the following entries (local4.debug) in my log file, even without a user trying to log in:
slapd[3263]: conn=243 op=4 SRCH base="ou=people,dc=boosten,dc=org" scope=1 deref=0 filter="(&(objectClass=posixAccount)(uid=smmsp))"
slapd[3263]: conn=243 op=4 SRCH attr=uid userPassword uidNumber gidNumber cn homeDirectory loginShell gecos description objectClass
shadowLastChange shadowMax shadowExpire
It seems that other services already are trying to log in using LDAP, but since they do not exist in LDAP they’re tried locally, where they do exist. In this case it’s smmsp, which is part of sendmail.
Remember we made the pam_ldap.so module ‘sufficient’: if we would have made it ‘required’, or ‘binding’, the the entire request would have failed, and sendmail would not have been able to function normally.
There are actually two interesting facts in these log entries:
- apparantly, nss_ldap is searching ‘ou=people,dc=boosten,dc=org’ for objectClass=posixAccount, and uid=smmsp
- it tries to get the following attributes from an entry, once found: ‘uid userPassword uidNumber gidNumber cn homeDirectory loginShell gecos description objectClass shadowLastChange shadowMax shadowExpire’
Since we have not yet defined most of these attributes in our directory, we are going to extend one entry, the entry for my daughter Dunja. What do we have already:
ra% ldapsearch -H ldap://ldap.boosten.org -b "ou=people,dc=boosten,dc=org" -D "cn=root,dc=boosten,dc=org" -W -LLL "(uid=Dunja)"
Enter LDAP Password:
dn: cn=Dunja Boosten,ou=people,dc=boosten,dc=org
cn: Dunja Boosten
sn: Boosten
givenName: Dunja
objectClass: top
objectClass: person
objectClass: inetOrgPerson
objectClass: organizationalPerson
uid: dunja
userPassword::
mail: dunja@boosten.org
No objectClass=posixAccount can be found, so this account is not going to be searched for when Dunja tries to log in to a host (not that she would, since she’s only three years of age).
Let’s have a look at the nis.schema, where the posixAccount is defined:
objectclass ( 1.3.6.1.1.1.2.0 NAME 'posixAccount'
DESC 'Abstraction of an account with POSIX attributes'
SUP top AUXILIARY
MUST ( cn $ uid $ uidNumber $ gidNumber $ homeDirectory )
MAY ( userPassword $ loginShell $ gecos $ description ) )
The attributes behind MUST and MAY look familiar, don’t they?
The MUST attributes are mandatory, and already we have two of them defined in our element: cn and uid.
Like mentioned before, uidNumber and gidNumber map to uid and gid in Unix, so these will contain number. Now you will have to make sure that the uidNumber is unique for every element you create within LDAP, or else you will get a access rights hell. The gidNumber doesn’t have to be unique, since a group of users (as defined by gid or Group ID) can consist of more than one user. FreeBSD however creates for every user a personal group, and in most of the cases gidNumber will match uidNumber.
The homeDirectory attribute should contain the path to the users (hence the name) home directory. Normal practice is to have a home directory in /home/ called like the users login name (or uid attribute): /home/dunja.
The MAY attributes are optional, so it seems a password is optional, a loginShell is optional (not sure what happens if you do not assign this value, so we’ll try that), gecos information can contain some additional information, like the users phone number (what you would normally change with the chfn command, however I still found no way to alter this information, other than with ldapmodify). Description could hold “greatest daughter in the world”, but I would have to do that for both of my daughters.
Let’s start with the mandatory fields:
# Dunja-add.ldif
dn: cn=Dunja Boosten,ou=people,dc=boosten,dc=org
changetype: modify
add: objectClass
objectClass: posixAccount
-
add: uidNumber
uidNumber: 3000
-
add: gidNumber
gidNumber: 3000
-
add: homeDirectory
homeDirectory: /home/dunja
I’ll have her have a uidNumber that’s very high, for visibilty. The dashes between all atrtibute actions are mandatory (man ldapmodify or man ldif), without them you will get an error like ‘ldapmodify: wrong attributeType at line 5, entry “cn=…’. Let’s import this into LDAP:
ra% ldapmodify -H ldap://ldap.boosten.org -D "cn=root,dc=boosten,dc=org" -W -f dunja-add.ldif
Enter LDAP Password:
modifying entry "cn=Dunja Boosten,ou=people,dc=boosten,dc=org"
Let’s try if we can log into the host, using this LDAP account (there’s no local account for Dunja). But before that, let’s show the contents of /home:
ramses% pwd
/home
ramses% ls -l
total 6
drwxr-xr-x 2 bert bert 512 Jul 23 08:49 bert
drwxr-xr-x 2 jozina jozina 512 Jul 21 20:33 jozina
drwxr-xr-x 8 peter peter 1024 Jul 23 10:57 peter
Now trying to log in:
FreeBSD.i386 (ramses.egypt.nl) (ttyv0)
login: dunja
Password:
No home directory.
Logging in with home = "/".
Copyright blahblah
$
Hey, this worked. Although it states that there’s no home directory, pam_mkhomedir actually made one:
ramses% pwd
/home
ramses% ls -l
total 8
drwxr-xr-x 2 bert bert 512 Jul 23 08:49 bert
drwxr-xr-x 2 dunja 3000 512 Jul 23 10:58 dunja
drwxr-xr-x 2 jozina jozina 512 Jul 21 20:33 jozina
drwxr-xr-x 8 peter peter 1024 Jul 23 10:57 peter
logging out and in (or is it: ‘off’ and ‘on’?) again, corrects that first error message, and puts you right in the right directory.
There are (at least) two things not right yet:
- I don’t have a shell (echo $SHELL shows nothing), but since I’m able to look around I must have, probably /bin/sh
- the listing of /home shows the group ID, and not a name. Obviously there’s no group 3000, which we will call dunja.
Let’s correct both. First the shell:
# dunja-shell.ldif
dn: cn=Dunja Boosten,ou=people,dc=boosten,dc=org
changetype: modify
add: loginShell
loginShell: /usr/local/bin/zsh
ra% ldapmodify -H ldap://ldap.boosten.org -D "cn=root,dc=boosten,dc=org" -W -f dunja-shell.ldif
Enter LDAP Password:
modifying entry "cn=Dunja Boosten,ou=people,dc=boosten,dc=org"
Oke, this works. Now let’s create a group. Remember from part one that I created an Organizational Unit called “ou=groups,dc=boosten,dc=org”, and earlier in this article I defined the following parameter in /usr/local/etc/ldap.conf (or nss-ldap.conf, it’s the same file):
nss_base_group ou=groups,dc=boosten,dc=org?one
So from my log file I discover this:
slapd[6276]: conn=81 op=4 SRCH base="ou=groups,dc=boosten,dc=org" scope=1 deref=0 filter="(&(objectClass=posixGroup)(gidNumber=3000))"
slapd[6276]: conn=81 op=4 SRCH attr=cn userPassword memberUid uniqueMember gidNumber
nss_ldap searches for a posixGroup objectclass, with gidNumber 3000 in “ou=groups,dc=boosten,dc=org” and requests 5 attributes from it. The posixGroup objectClass has two mandatory attributes: cn and gidNumber, all others are optional.
We give nss_ldap what it wants: a group.
# group-3000.ldif
dn: cn=dunja,ou=groups,dc=boosten,dc=org
objectClass: posixGroup
cn: dunja
gidNumber: 3000
memberUid: 3000
The memberUid actually isn't necessary, but it looks nice. Now add it:
ra% ldapadd -H ldap://ldap.boosten.org -D "cn=root,dc=boosten,dc=org" -W -f group-3000.ldif
Enter LDAP Password:
adding new entry "cn=dunja,ou=groups,dc=boosten,dc=org"
Works like charm. It even works already, without logging back in:
ramses% ls -l
total 8
drwxr-xr-x 2 bert bert 512 Jul 23 08:49 bert
drwxr-xr-x 2 dunja dunja 512 Jul 23 10:58 dunja
drwxr-xr-x 2 jozina jozina 512 Jul 21 20:33 jozina
drwxr-xr-x 8 peter peter 1024 Jul 23 10:57 peter
Logging out and in again solves the shell as well.
This concludes this part.
This entry was posted on Thursday, July 23rd, 2009 at 12:00 pm and is filed under freebsd. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.
WOW – the most concise work of how to centralize your users and passwords and is the best explained ldap setup that i`ve seen. You were able to answer most of my questions about LDAP setup. Even the article is old, here is none like it.
Good job.
This is a great ldap setup publication. I especially like that your apache setup is explained. I was hoping to see a SSL/TLS setup so that I could use it for reference when setting up mine. BRilliant article even if it’s almost outdated now as we’re all being told to use the olc context for configuration.