Deploying LDAP


Copyright 2002, 2003 Andy Barclay
OpenContent License (OPL)

So you want to create an LDAP accessible directory?

What the hell for? You might as well just keep all that
information up-to-date in 20 different places, right?

You think I'm exagerating, don't you? Well lets just count
the various directories that I maintain at home:
-DNS
-/etc/passwd on 3 Unix machines
-samba password database
-Windows 2000 password database
-netscape address book
-nokia cell phone contacts list
-palm pilot contact database

Ok, so how do we do this?

Compile openldap
----------------
First, download and install the latest version of openldap.
We will use openldap, however, we could also use netscape directory server,
or active directory.

1. Install BerkeleyDB version 4.2
buggs:/opt/src/db-4.2.52/build_unix$ ../dist/configure --enable-shared
*** OR without the --enable-shared
buggs:/opt/src/db-4.2.52/build_unix$ make
buggs:/opt/src/db-4.2.52/build_unix$ sudo make install

1.5. Install openssl
###NOTE: Do NOT compile openssl with shared library support

2. Install openldap (I used version 2.0.11)

$ CPPFLAGS=-I/usr/local/BerkeleyDB.4.2/include
$ LDFLAGS=-L/usr/local/BerkeleyDB.4.2/lib
$ export CPPFLAGS LDFLAGS
$ ./configure --with-ldbm-api=berkeley
/* may have to use --with-cyrus-sasl=no --with-tls=no */

$ make depend
$ make
$ sudo make install

Now, design your DIT.

I suggest you begin by taking your DNS domain name and dividing it into
components. For example, I own the unixpeople.com domain, so my top-level
ldap directory would be:
dc=unixpeople,dc=com

Next modify the default slapd.conf, adding in the necessary schema files:
--------------
include     /usr/local/etc/openldap/schema/core.schema
include     /usr/local/etc/openldap/schema/cosine.schema
include     /usr/local/etc/openldap/schema/inetorgperson.schema
include     /usr/local/etc/openldap/schema/nis.schema
include     /usr/local/etc/openldap/schema/nisdomainobject.schema
--------------

Note that the nisdomainobject.schema does not come stock with the openldap
distribution. You can download it here

Change the suffix to match the top-level ldap directory above.
-----------
suffix      "dc=unixpeople, dc=com"
-----------

Change the rootdn:
----------
rootdn      "cn=Manager, dc=unixpeople, dc=com"
----------

Change the rootpw:
----------
rootpw     secret
---------

NOTE: This file will not be publicly readable, so its probably ok
to leave the password here in plain text form, however, if you are
really security concious, you may want to insert the password
in an encrypted format. Use slappasswd to generate this:
Here is an example:
----
[berlin:/home/abarclay]$ /usr/local/sbin/slappasswd
New password: 
Re-enter new password: 
{SSHA}jGi/UXlNh2J0B2v5YT7BVF7H1A8Tv2Ew
----

NOTE: Just got a message from Jennifer. It seems that the lib directory
for BerkeleyDB is NOT found when she runs slappasswd.....
Try setting the LD_LIBRARY_PATH before running the command:
-------
# AWB - slapd is complaining that it can't find the db lib
LD_LIBRARY_PATH=/usr/local/BerkeleyDB.4.2/lib
export LD_LIBRARY_PATH
-------

NOTE: MAKE SURE THAT THE directory referenced in slapd.conf
exists and has 700 permissions. Without this, you will get
a rather cryptic error:
> adding new entry "dc=unixpeople, dc=com"
> ldap_add: Unknown error
>         additional info: next_id add failed
>
> ldif_record() = 80



Start the directory server
# /etc/init.d/ldap.server start

This is simply a startup script that runs "/usr/local/libexec/slapd"

Now, we need to stick some data into the database.
We need to create the most basic ldif file.
-----------------------
dn: dc=unixpeople,dc=com
objectclass: dcObject
objectclass: organization
objectclass: nisDomainObject
o: UnixPeople
dc: unixpeople
nisDomain: unixpeople.com

dn: cn=Manager,dc=unixpeople,dc=com
objectclass: organizationalRole
cn: Manager
-----------------------

Of course, since we are planning on storing all our unix information in
the directory, we also create containers for all that information:
-----------------------
dn: ou=Ethers,dc=unixpeople,dc=com
objectclass: organizationalUnit
ou: Ethers

dn: ou=Group,dc=unixpeople,dc=com
objectclass: organizationalUnit
ou: Group

dn: ou=Aliases,dc=unixpeople,dc=com
objectclass: organizationalUnit
ou: Aliases

dn: ou=Netgroup,dc=unixpeople,dc=com
objectclass: organizationalUnit
ou: Netgroup

dn: ou=Networks,dc=unixpeople,dc=com
objectclass: organizationalUnit
ou: Networks

dn: ou=People,dc=unixpeople,dc=com
objectclass: organizationalUnit
ou: People

dn: ou=protocols,dc=unixpeople,dc=com
objectclass: organizationalUnit
ou: protocols

dn: ou=rpc,dc=unixpeople,dc=com
objectclass: organizationalUnit
ou: rpc

dn: ou=Services,dc=unixpeople,dc=com
objectclass: organizationalUnit
ou: Services

dn: ou=Hosts,dc=unixpeople,dc=com
objectclass: organizationalUnit
ou: Hosts

dn: ou=profile,dc=unixpeople,dc=com
objectclass: organizationalUnit
ou: profile
-----------------------

Now add that all into the database (assuming its in a file called base.ldif)
$ ldapmodify -a -h buggs.unixpeople.com -D cn=Manager,dc=unixpeople,dc=com -w secret -f base.ldif

The -a says we will be adding entries.
It wouldn't be very secure if anyone could just dump data into the directory.
There has to be some kind of authentication, so the -D specifies the dn
(distinguished name) of a user who has write access to the database.
Because we don't have anyone in the database yet, this is actually the
rootdn from the slapd.conf. The -w specifies the password for this dn
(also specified in the slapd.conf as rootpw).

Now we have the outline created, so its time to add some data.

First, create an account. Solaris expects the account to be in the "ou=People"
container. Linux doesn't care where it is. Again, we use an ldif file.

---------------------
dn: cn=Andy Barclay,ou=People,dc=unixpeople,dc=com
objectClass: person
objectClass: posixAccount
objectClass: shadowAccount
cn: Andy Barclay
sn: Barclay
uid: andybar
uidNumber: 330
gidNumber: 10
homeDirectory: /home/andybar
userPassword: {CRYPT}FugtiShb5De8Q
loginShell: /bin/ksh
gecos: Andy Barclay, x23051
shadowLastChange: 11022
shadowFlag: 0
---------------------

Note that the format of the user password is specified in curly braces
just before the password. The CRYPT format specifies DES encryption which
is the same as what is used in the UNIX passwd/shadow files. Other options
here are SHA or MD5.

Jennifer asks, "How can you generate the CRYPT password?"
Well, you can cut and paste it out of /etc/shadow, or you can use:
$ slappasswd -s foobar -h "{CRYPT}"

Also note that there are other attributes that are in the shadowAccount
objectclass, but I don't recommend adding them in until after you
have login authentication working. The reason is that I added values for
them, and I was getting authentication failures.

Add the account in:

$ ldapmodify -a -h buggs.unixpeople.com -D cn=Manager,dc=unixpeople,dc=com -w secret -f account.ldif

Configuring Solaris to query the LDAP accessible directory:
-----------------------------------------------------------
NOTE: You cannot use the "ldapclient" command to initialize a solaris client.
The script sends a query to the ldap server that it is unable to answer
(unless the server is the iPlanet/netscape directory server). There is
a source-code modification to openldap to make it answer this query, but
its easier to simply create the /var/ldap/ldap_client_file with the 
following contents:
---------------------
NS_LDAP_SERVERS=127.0.0.1:389
NS_LDAP_SEARCH_BASEDN=dc=unixpeople,dc=com
---------------------

Now, edit the file /etc/nsswitch.conf, changing the passwd entry to 
search the ldap database too.
----------------
passwd: files ldap
----------------

Better make a copy of the existing password and shadow databases:
----------------
$ cd /etc
$ sudo cp passwd passwd.preldap
$ sudo cp shadow shadow.preldap
----------------

Now, remove the account information from the /etc/passwd and /etc/shadow

First, see if the OS can map the uid in the inode back to a login name.

# ls -l /home/andybar

If you see a numeric uid in the list of files, then something has
definately gone wrong. I saw the following list:
-------------
total 14266
-rw-r--r--   1 andybar staff      20547 Dec 16 15:43 Corio.phone
drwx------   3 andybar staff        512 Jan 24 08:12 Desktop
drwxr-xr-x   6 andybar staff       1536 Jun  2 08:42 Mail
-rw-r--r--   1 andybar staff        573 May  7  2000 StarOffice52
drwxr-xr-x   2 andybar staff        512 Jul 15  2000 dos
drwxr-x---  19 andybar staff        512 Apr 19 23:21 laptop
drwxr-x---   2 andybar staff        512 May 18 16:46 ldif
drwx------   2 andybar staff        512 Dec 10 16:36 mail
drwxr-xr-x   2 andybar staff       1536 May 26  1999 memos
drwxrwx---   3 andybar staff       3072 Apr  8 20:03 multimedia
drwxr-xr-x   4 andybar staff       2560 May 22 11:44 notes
drwxr-xr-x   6 andybar staff        512 Jun  2  2000 ns_imap
-------------

You can see that the Operating System is able to cross reference the numeric
uid to an account name using the ldap database. (Assuming that it didn't get
the information from somewhere else - like the nscd daemon...)

Configuring Linux to query the LDAP accessible directory:
-----------------------------------------------------------
Edit the file /etc/ldap.conf and change the host and base directives:
----
host 192.168.37.2
base dc=unixpeople,dc=com
----

Adding the rest of the naming service info
-------------------------------------------
You can replace the services file by using an ldif file similar to:
-----------------
dn: cn=domain,ou=Services,dc=eng,dc=corio,dc=com
objectClass: ipService
cn: domain
ipServicePort: 53
ipServiceProtocol: udp
ipServiceProtocol: tcp

-----------------

You can replace the protocols file by using an ldif file similar to:
------------------
dn: cn=ip,ou=Protocols,dc=eng,dc=corio,dc=com
objectClass: ipProtocol
cn: ip
ipProtocolNumber: 0
description: internet protocol, pseudo protocol number

------------------

What about Login Authentication?
---------------------------------
The way a program or operating system SHOULD authenticate an LDAP user
is by attempting to BIND to the database.

The openldap server will accept the username and password and
compare it to what is in the database. If the BIND is successfull,
then the authentication should be successfull. If the BIND fails,
then the authentication should fail.

Solaris, of course, doesn't work this way. Instead, it retrieves the
userPassword and then does the comparison on the client. The only
real problem with this is if we choose to use {MD5} or {SHA} rather
than {CRYPT}, then I believe solaris will fail to authenticate (I
haven't actually tried it).

Solaris is annoying me
-----------------------
While troubleshooting problems authenticating at login, the
messages that solaris was throwing, were less than helpfull.
When I login using the "andy" user via telnet, it would give me a
"Connection closed" message after apparently acceping my login.

ssh would give me, "Permission denied, please try again."

ftp is the only application that gave me a good indication of what was
going on, "530 Account is expired".

As it turns out, the shadow parameters that I had inserted were not
good. Although, in a different ldap server, they worked fine - oh well.

Fully populate the server with accounts and groups
--------------------------------------------------
I wrote a script to take the /etc/passwd file and insert it into
the People container. One Note about using this script. Solaris
has two accounts, uucp and nuucp with the same gecos. This is
perfectly acceptable in the passwd file, but using this field
as the cn, means that these must be unique. I modified the nuucp
entry in /etc/passwd, changing the nuucp entry to "nuucp Admin".

Once these accounts have been added, remove any entries from /etc/passwd
that are not system accounts.

Although rebooting is not necessary, restarting all the programs that
may use the ldap information IS REQUIRED. On my system, this included:
ssh, apache, samba 

Now, add in the groups. The ldif file would look like:
-------------
dn: cn=webadmin,ou=Group,dc=unixpeople,dc=com
objectClass: posixGroup
cn: webadmin
gidNumber: 60003
memberUid: jeff
memberUid: andybar
memberUid: omiranda
-------------

Remove all non system-groups from /etc/group.

Security
--------
Lets start with an easy one. Since the rootdn account can make any
modifications to the database, and the password is also specified
in the slapd.conf, make sure the mode on the slapd.conf is 600.

Notice that you can run an "anonymous" ldapsearch and see the
encrypted passwords:
----------------
$ ldapsearch -b ou=people,dc=unixpeople,dc=com cn=Andy* userPassword
cn=Andy W. Barclay,ou=People,dc=unixpeople,dc=com
userPassword={CRYPT}FugbiShb5De8Q
----------------
(This may not show up quite like this)

This is no more secure than having the encrypted passwords stored in the 
/etc/passwd file as they were before the days of /etc/shadow.

One method of fixing this is to make the userpassword attribute readable
only by the rootdn. Add the following to slapd.conf
-----------------
access to dn="(.*,)?ou=People,dc=unixpeople,dc=com" attr=userPassword
                by self write

# AWB - hopefully this should allow people to change their passwords
access to *
        by self write
        by anonymous auth
        by * read
-----------------

Replace the solaris ldap pam modules by following the steps at:
http://www.bolthole.com/solaris/LDAP.html
In general:
----------------------------
download, compile, and install the latest version of openssl
-follow instructions in the file building.openssl.txt
download, compile and install the latest version of BerkeleyDb
db-3.2.9/build_unix$ ../dist/configure --enable-shared
db-3.2.9/build_unix$ make
db-3.2.9/build_unix$ sudo make install

download, compile and install the latest version of openldap (as above with
additional CPP and LDFLAGS)
$ CPPFLAGS="$CPPFLAGS -I/usr/local/ssl/include"
$ LDFLAGS="$LDFLAGS -R/usr/local/ssl/lib -L/usr/local/ssl/lib"
$ ./configure --with-ldbm-api=berkeley --with-tls
change "LD=/usr/ccs/bin/ld" to "LD=/usr/ccs/bin/ld -R/usr/local/ssl/lib" in libtool
$ make depend
$ make
$ sudo make install

generate the key pair and certificate
$ cd /usr/local/etc/openldap
$ sudo openssl genrsa -des3 -rand /etc/hosts -out ldaptest.corio.com.key 1024
$ openssl req -new -key ldaptest.corio.com.key -out ldaptest.corio.com.csr
$ /opt/ssl/bin/openssl x509 -req -days 3650 -in ldaptest.corio.com.csr \
	-signkey ldaptest.corio.com.key -out ldaptest.corio.com.crt
$ openssl rsa -in ldaptest.corio.com.key -out ldaptest.corio.com.key.new

add the following lines to slapd.conf
---
# move the ldap server to ssl
TLSCipherSuite HIGH:MEDIUM:+SSLv2
TLSCertificateFile /usr/local/etc/openldap/certs/ldaptest.corio.com.crt
TLSCertificateKeyFile /usr/local/etc/openldap/certs/ldaptest.corio.com.key.new
TLSCACertificateFile /usr/local/etc/openldap/certs/ldaptest.corio.com.crt
---

start openldap
$ sudo  /usr/local/libexec/slapd -h ldaps:///

----------------------------

Replicating the database
-----------------------
There are a few reasons why one might want to replicate the database.
1) providing multiple directory servers allows us to spread the load
between these servers for scalability.
2) If one of the servers crashes, an additional server provides increased
reliability
3) I replicate the directory from my home machine to my laptop so that
I have access to the directory even when I am not connected to the Net.

The steps for doing this are outlined in the openldap administrator's 
guide, but there are a couple of differences in the way I did it.

1) Export the database to a file
$ /usr/local/bin/ldapsearch -b dc=unixpeople,dc=com \
	-D cn=manager,dc=unixpeople,dc=com -w secret 'objectclass=*' \
	>$HOME/unixpeople.dib

2) Shutdown the ldap server on the master
# /etc/init.d/ldap.server stop

3) copy the dib file to the machine you want to replicate the database to.

4) copy the slapd.conf file to the machine you want to replicate the database
to.

5) start with an empty database on the slave
# rm /var/ldap/openldap-ldbm/*

6) Add the following lines to the slapd.conf ON THE SLAVE
---
updatedn "cn=Manager,dc=unixpeople,dc=com"
updateref ldap://buggs.unixpeople.com
---

7) start the daemon on the slave

8) import the dib on the slave
$ ldapadd -D cn=manager,dc=unixpeople,dc=com -w secret \
	-f /$HOME/unixpeople.dib

9) Add the following lines to the slapd.conf ON THE MASTER
----
replica host=barclay.unixpeople.int
  bindmethod=simple
  "binddn=cn=Manager,dc=unixpeople,dc=com"
  credentials=secret

replogfile /usr/local/var/openldap-slurp/log
----

10) start the daemon on the master

11) Test the replication by making a change on the master.

Performance
-----------
At work, I added 136 accounts into ldap using a script.
The login process was VERY slow. After trying to troubleshoot
this problem for a while, I found that the loglevel was set to 4095,
and the idle time on the machine was dropping to near zero.
In addition, I chose to create indexes for uid and uidNumber, and gidNumber.
----------------
index   objectClass,uid,uidNumber,gidNumber eq
index	cn sub
----------------

Now, regenerate the indexes
# /etc/init.d/ldap.server stop
# /usr/local/sbin/slapindex -b dc=unixpeople,dc=com -f /usr/local/bin/slapd.conf
# /etc/init.d/ldap.server start

Aliases
-------
Ok, one of the people in my contacts list just got married, and
she has decided to change her last name to her spouse's. How can
I handle this without replicating the whole entry?

Openldap does have support for alias entries.
Create an ldif file as follows:
----------------------
dn: cn=Christine Pilmer,ou=contacts,cn=Andy W. Barclay,ou=People,dc=unixpeople,dc=com
objectClass: alias
aliasedObjectName: cn=Christine Calhoun,ou=contacts,cn=Andy W. Barclay,ou=People,dc=unixpeople,dc=com
----------------------

So, how can I leverage this directory?
-------------------------------------

Well, I can create a new container for holding the contact list
--------------
dn: ou=contacts,cn=Andy Barclay,ou=People,dc=unixpeople,dc=com
objectClass: organizationalUnit
ou: contacts

# Andy Barclay,dc=unixpeople,dc=com
dn: cn=Andy Barclay,ou=contacts,cn=Andy Barclay,ou=People,dc=unixpeople,dc=com
objectClass: inetOrgPerson
cn: Andy Barclay
sn: Barclay
mail: billy@buggs.unixpeople.com
l: Altamont
st: California
street: Corsina
homePostalAddress: 341 Corsina Court, Altamont, Ca, 94518
telephoneNumber: 925 737 6600
preferredLanguage: English
mobile: 925 737 6700
--------------

Then I can configure my mail agent to search my contact directory for
mail addresses.
Go to Address Book, Create New Directory:
Description: Test Directory
LDAP Server: buggs.unixpeople.com
Server Root: ou=People,dc=unixpeople,dc=com

Note: I use this server root so I can search other people's contact lists.
If I only want to search my contact list, then I would use:
ou=contacts,cn=Andy W. Barclay,ou=People,dc=unixpeople,dc=com
as my server root.

What about Operating Systems that don't natively support LDAP?
(like solaris 2.6 and 2.7?)
--------------------------------------------------------------
download nss_ldap and pam_ldap modules from www.padl.com

install nss_ldap:
	$ ./configure
******** edit the Makefile and remove the -lldap and -llber and
replace them with /usr/local/lib/libldap.a /usr/local/lib/liblber.a
	$ /usr/local/bin/make
	$ chmod a+rx install-sh
	$ sudo /usr/local/bin/make install

pam_ldap is not required as I couldn't get solaris 2.6 to change passwords...
Kept throwing:
--------------
passwd:  Changing password for andybar
Supported configurations for passwd management are as follows:
    passwd: files
    passwd: files nis
    passwd: files nisplus
    passwd: compat
    passwd: compat AND
    passwd_compat: nisplus
Please check your /etc/nsswitch.conf file
Permission denied
--------------
install pam_ldap:
	$ ./configure
******** edit the Makefile and remove the -lldap and -llber and
replace them with /usr/local/lib/libldap.a /usr/local/lib/liblber.a
	$ /usr/local/bin/make
	$ chmod a+rx install-sh
	$ sudo /usr/local/bin/make install

edit /etc/ldap.conf, customizing the host, and base entries.