Deploying OpenLDAP under FreeBSD 7.0
Categories: FreeBSD, Sysadmin.
Adrien Reboisson and Géraud de Mareschal are just about to launch Astase as a professional-grade software engineering company. While I will not be part of the adventure, I am pleased to help them as far as I can in order to make this launch a great success!
They have just bought a computer to act as their main server, and decided to run FreeBSD on it. As a FreeBSD expert
(according to them), I was asked to provide some expertise in the setup of the machine. Surprisingly, it was the path of great challenges I had not experienced before: while considering the services that would be running on the computer, I realised an LDAP directory would make it easier to work on the system (with a single centralised password), to get in touch with customers (LDAP is a directory, right?), and would provide a reliable infrastructure for the future.
Requirements
- Use a single password for all services (e.g. SSH, HTTP);
- Provide a centralised customer address book;
- Cope with cataclysms (i.e. backups needed!).
At first sight, at least 3 branches are necessary for managing the directory entries:
- customers
- Customers information, accessible with read/write permissions by the company's staff.
- services
- Internal accounts for services (e.g. Apache httpd). Services will generally only need a read-only access to parts of the directory.
- staff
- Adrien and Géraud (and others soon).
We so have the following structure:
dc=astase,dc=com |-> ou=customers | `-> ... |-> ou=services | `-> ... `-> ou=staff `-> ...
Installation
I chose to install OpenLDAP 2.3 (although version 2.4 is available) because the port of pam_ldap (and probably other tools) depends on that version of OpenLDAP and I did not have enough time to try to compile it against version 2.4.
# portinstall net/openldap23-server
The file /usr/local/ext/openldap/slapd.conf has to be configured according to the server's environment. At least, the suffix
and rootdn
of the database have to be changed. I also added the following schemes:
include /usr/local/etc/openldap/schema/cosine.schema include /usr/local/etc/openldap/schema/inetorgperson.schema
While editing this file, permissions can be fixed:
access to attrs=userPassword by self write by anonymous auth access to * by self write by dn.children="ou=staff,dc=astase,dc=com" write by users read by anonymous auth
Before running the server, it is required to import the bare minimal into the directory database. I wrote astase.ldif as so:
dn: dc=astase,dc=com dc: astase objectClass: dcObject objectClass: organization o: Astase dn: ou=staff,dc=astase,dc=com ou: staff objectClass: organizationalUnit dn: ou=customers,dc=astase,dc=com ou: customers objectClass: organizationalUnit dn: ou=services,dc=astase,dc=com ou: services objectClass: organizationalUnit
This file can be imported in the database using slapadd(8C) :
# slapadd -v -l astase.ldif
The server can now be launched. As usual in the FreeBSD world, /etc/rc.conf has to be edited to allow slapd(8C) to start:
# echo 'slapd_enable="YES"' >> /etc/rc.conf # /usr/local/etc/rc.d/slapd start
It is then possible to fill in the directory using a cute editor. My personal preference goes to ldapvi, but you might prefer GQ...
Logging-in
Interesting things begin here!
We have an LDAP directory containing users with logins and passwords on a system containing users ... with logins and passwords! The first thing we may want to do is to associate both so that a single user only have a single password, in other word identify users using the information stored in the LDAP directory.
Under FreeBSD (and GNU/Linux), this can be done using PAM.
Pluggable Authentication Module (PAM)
FreeBSD default setup provides various PAM modules (you can list them issuing ls /usr/lib/pam_*
in a terminal), but does not provide PAM module for LDAP. Such a module is provided as a third party software package called pam_ldap:
# portinstall pam_ldap
As any third party package, files are installed in $LOCALBASE (defaults to /usr/local) and PAM will not be able to find the library pam_ldap.so. This can be fixed by symlinking the library in /usr/lib:
# ln -s /usr/local/lib/pam_ldap.so /usr/lib
The file /usr/local/etc/ldap.conf has to be edited to configure how the PAM module is supposed to search information in the LDAP directory. You can refer to /usr/local/etc/ldap.conf.dist (installed by pam_ldap) for details about all available options. My ldap.conf file looks like this:
host 127.0.0.1 base dc=astase,dc=org binddn cn=pam_ldap,ou=services,dc=astase,dc=com bindpw secret scope sub pam_password exop nss_base_passwd ou=staff,dc=astase,dc=com nss_base_shadow ou=staff,dc=astase,dc=com
The pam_ldap distinguish name refer to the user account PAM will use to query the LDAP directory. This user of the services organisational unit has been created with ldapvi like so:
add cn=pam_ldap,ou=services,dc=astase,dc=com cn: pam_ldap objectClass: top objectClass: inetOrgPerson sn: PAM userPassword: secret
Everything is now ready. We can now tweak the PAM configuration. But beware! Mistakes with PAM will either prevent you from being able to login anymore, or grant you access whatever password you type in (considering a password is asked). It is so recommended to have a physical access to the machine, and be logged in on many terminals before breaking everything.
ssh(1)
The file /etc/pam.d/sshd describes the behaviour of remote logins via SSH. If we consider that only real users can remotely log-in (i.e. no ssh root@...
, in other word the default under FreeBSD), we can safely replace unix auth with ldap.
# auth auth sufficient pam_opie.so no_warn no_fake_prompts auth requisite pam_opieaccess.so no_warn allow_local #auth sufficient pam_krb5.so no_warn try_first_pass #auth sufficient pam_ssh.so no_warn try_first_pass auth required pam_ldap.so #auth required pam_unix.so no_warn try_first_pass # account account required pam_nologin.so #account required pam_krb5.so account required pam_login_access.so account required pam_ldap.so #account required pam_unix.so # session #session optional pam_ssh.so session sufficient pam_ldap.so session required pam_permit.so # password #password sufficient pam_krb5.so no_warn try_first_pass password required pam_ldap.so #password required pam_unix.so no_warn try_first_pass
passwd(1)
Now that it is possible to log-in using the password stored in LDAP, it is helpful to tell passwd(1) to change the password of the user in the LDAP directory instead of the one in the shadow file. This is done by editing /etc/pam.d/passwd like this:
# password #password requisite pam_passwdqc.so enforce=users ###password required pam_unix.so no_warn try_first_pass nullok password required pam_ldap.so
su(1)
su(1)'s PAM configuration file (/etc/pam.d/su, you guessed it) reference the /etc/pam.d/system file. I would advise to keep the unix auth as a fallback so that it is still possible to switch to another user not in the LDAP directory (e.g. root):
# # System-wide defaults # # auth auth sufficient pam_opie.so no_warn no_fake_prompts auth requisite pam_opieaccess.so no_warn allow_local #auth sufficient pam_krb5.so no_warn try_first_pass #auth sufficient pam_ssh.so no_warn try_first_pass auth sufficient pam_ldap.so auth required pam_unix.so no_warn try_first_pass nullok # account #account required pam_krb5.so account required pam_login_access.so account sufficient pam_ldap.so account required pam_unix.so # session #session optional pam_ssh.so session required pam_ldap.so session required pam_lastlog.so no_fail # password #password sufficient pam_krb5.so no_warn try_first_pass password required pam_unix.so no_warn try_first_pass
sudo(8)
Just like su(1), sudo(8) relies on the /etc/pam.d/system configuration file so it is already configured.
But sudo can do better! All sudo rules can be stored in the LDAP directory. The FreeBSD port of sudo has a WITH_LDAP knob to enable this feature. A schema is then installed and can be copied with the others...
# cp /usr/local/share/doc/sudo/schema.OpenLDAP /usr/local/etc/openldap/schema/sudo.schema
... added to the list of included schemas ...
include /usr/local/etc/openldap/schema/sudo.schema
... before rebooting the server to take the changes into account...
# /usr/local/etc/rc.d/slapd restart
The LDAP directory then needs a new node to store sudo rules. ldapvi is still my friend:
add ou=sudoers,ou=services,dc=astase,dc=com ou: sudoers objectClass: organizationalUnit
An existing sudoers file can then be easily imported in the LDAP directory:
# setenv SUDOERS_BASE ou=sudoers,ou=services,dc=astase,dc=com # /usr/local/share/doc/sudo/sudoers2ldif /usr/local/etc/sudoers > /tmp/sudoers.ldif # ldapadd -f /tmp/sudoers.ldif -h dev.astase.com -D 'cn=Manager,dc=astase,dc=com' -W -x Enter LDAP Password: adding new entry "cn=defaults,ou=sudoers,ou=services,dc=astase,dc=com" adding new entry "cn=root,ou=sudoers,ou=services,dc=astase,dc=com" adding new entry "cn=%wheel,ou=sudoers,ou=services,dc=astase,dc=com" #
sudo(8)'s LDAP base has to be configured in /usr/local/etc/ldap.conf:
sudoers_base ou=sudoers,ou=services,dc=astase,dc=com
The file /usr/local/etc/sudoers is then redundant. I recommend emptying it in order to avoid to have rules in the LDAP directory and the sudoers file. Using visudo, I wrote the following in the sudoers file:
# This file intentionally left empty. # # SUDO is configured in the LDAP directory under # ou=sudoers,ou=services,dc=astase,dc=com
Last step: tell sudo to read it's configuration from LDAP, not files, adding this line to /etc/nsswitch.conf:
sudoers: ldap
Apache httpd
The Apache web server provide two LDAP modules: mod_ldap and mod_authnz_ldap. The former act as a cache for LDAP queries, and the later allows basic authentication.
These modules are available as options for apache (disabled by default). If you have compiled these LDAP modules, you should file them typing-in:
# ls /usr/local/libexec/apache22/ | grep ldap mod_authnz_ldap.so mod_ldap.so #
A quick way to test both modules is to setup a special page that displays LDAP cache statistics (mod_ldap) protected by a basic LDAP authentication (mod_authnz_ldap). This was inspired by the Apache documentation:
LDAPSharedCacheSize 200000 LDAPCacheEntries 1024 LDAPCacheTTL 600 LDAPOpCacheEntries 1024 LDAPOpCacheTTL 600 <Location /ldap-status> SetHandler ldap-status Order deny,allow Deny from all Allow from 192.168 AuthType basic AuthBasicProvider ldap AuthName "Apache httpd LDAP Cache" AuthLDAPURL ldap://127.0.0.1/ou=staff,dc=astase,dc=com?uid AuthLDAPBindDN cn=httpd,ou=services,dc=astase,dc=com AuthLDAPBindPassword secret Require valid-user </Location>
The httpd user was created with ldapvi:
add cn=httpd,ou=services,dc=astase,dc=com cn: httpd objectClass: top objectClass: inetOrgPerson sn: Apache httpd userPassword: secret
AddressBook
Configuring an AddressBook can be really quick. can
because I have just tried to setup Evolution and that was really easy (two much?). Evolution use its own schema that you have to copy on the OpenLDAP server and include in the configuration. That's all!
The schema is part of evolution-data-server and installed as:
/usr/local/share/evolution-data-server-2.22/evolutionperson.schema
Backing-up the LDAP directory
At the time this article was written, slapcat(8) could not operate on a running OpenLDAP server. This shell script was a workaround to prevent disasters, but you SHALL use slapcat(8) to do backup now!
We now have a complete setup. The last step (and not the least important one!) is to setup automatic backup of the directory. I like to do this backup just like PostgreSQL ports do it: every day, databases are dumped and a one-week history is kept. This can be implemented in /usr/local/etc/periodic/daily/100.openldap with the following shell-script:
#!/bin/sh LDAPSEARCH="/usr/local/bin/ldapsearch" BACKUPDIR="/var/backups/openldap" PASSWDFILE="/root/.ldap-backup-password" HOST="127.0.0.1" BINDDN="cn=backup,ou=services,dc=astase,dc=com" BASE="dc=astase,dc=com" FIND="/usr/bin/find" BZIP2="/usr/bin/bzip2" umask 077 echo echo "OpenLDAP maintenance" if [ '!' -d "${BACKUPDIR}" ]; then mkdir "${BACKUPDIR}" ; fi "${LDAPSEARCH}" -L -H "ldap://${HOST}" -D "${BINDDN}" -y "${PASSWDFILE}" -b "${BASE}" | \ ${BZIP2} - > "${BACKUPDIR}/${BASE}-`date +%F`.ldif.bz2" # Clean up if [ $? -eq 0 ]; then ${FIND} "${BACKUPDIR}" -type f -ctime +1w -delete fi
One more time, an entry has to be added to the directory for the backup service. One more time ldapvi can do this:
add cn=backup,ou=services,dc=astase,dc=com cn: backup objectClass: top objectClass: inetOrgPerson sn: backup userPassword: secret
Here I chose to put the password in another file (just to mention the -n flag witch is required... if missing the password will end with a carriage return and authentication of the backup user will fail):
# umask 077 # echo -n 'secret' > /root/.ldap-backup-password
Last, the access rule for user passwords has to be changed so that the backup user can effectively save all user passwords:
access to attrs=userPassword by self write by dn="cn=backup,ou=services,dc=astase,dc=com" read by anonymous auth
cron(8) will now backup the LDAP directory every night at 3am. If no backup occurs, check that the following line exists in /etc/periodic.conf:
local_periodic="/usr/local/etc/periodic"
Overview
That's it! All the LDAP infrastructure is ready. The directory tree is finally like this (not far away from what I though at the beginning):
dc=astase,dc=com |-> ou=customers | `-> ... |-> ou=services | |-> cn=backup | |-> cn=httpd | |-> cn=pam_ldap | `-> ou=sudoers | |-> cn=%wheel | |-> cn=defaults | `-> cn=root `-> ou=staff |-> cn=Adrien Reboisson `-> cn=Géraud de Mareschal
Hope that help! Good luck boys!
Comments
On March 30, 2008, Adrien Reboisson wrote:
A great, great, great work :-)
Thank you very much for the time you spend !
(pourquoi j'écris en Anglais moi ? ^^)
On April 5, 2010, Joost wrote:
Hi
I get this error doing the above
/usr/local/etc/openldap/slapd.conf: line 15: suffix <dc=uwaterloo,dc=ca> not allowed in frontend database. slapadd: bad configuration file!
Do you know what it means and how this can be solved? Thanks in advance.
On April 5, 2010, Romain Tartière wrote:
Hey!
Maybe a base-dn error. Difficult to say without seeing the slapd.conf file. /var/log/debug.log may also contain useful details ;-)