I'm using [LDAP][] ([RFC 4510][rfc4510]) to maintain a centralized address book at home. Here are my setup notes, mostly following Gentoo's [LDAP howto][howto]. Install [OpenLDAP][] with the `ldap` USE flag enabled: # emerge -av openldap If you get complaints about a `cyrus-sasl` ↔ `openldap` dependency cycle, you should temporarily (or permanently) disable the `ldap` USE flag for `cyrus-sasl`: # echo 'dev-libs/cyrus-sasl -ldap' > /etc/portage/package.use/ldap # -ldap" emerge -av1 cyrus-sasl # emerge -av openldap Generate an administrative password: $ slappasswd New password: Re-enter new password: {SSHA}EzP6I82DZRnW+ou6lyiXHGxSpSOw2XO4 Configure the `slapd` LDAP server. Here is a very minimal configuration, read the [OpenLDAP Admin Guide][admin] for details: # emacs /etc/openldap/slapd.conf # cat /etc/openldap/slapd.conf include /etc/openldap/schema/core.schema include /etc/openldap/schema/cosine.schema include /etc/openldap/schema/inetorgperson.schema pidfile /var/run/openldap/slapd.pid argsfile /var/run/openldap/slapd.args database hdb suffix "dc=example,dc=com" checkpoint 32 30 rootdn "cn=Manager,dc=example,dc=com" rootpw {SSHA}EzP6I82DZRnW+ou6lyiXHGxSpSOw2XO4 directory /var/lib/openldap-data index objectClass eq [inetOrgPerson][] is huge, but it's standardized. I think it's better to pick a big standard right off, than to outgrow something smaller and need to migrate. Gentoo creates the default database directory for you, so you can ignore warnings about needing to create it yourself. Configure LDAP client access. Again, read the docs for details on adapting this to your particular situation: # emacs /etc/openldap/ldap.conf $ cat /etc/openldap/ldap.conf BASE dc=example,dc=com URI ldap://ldapserver.example.com You can edit '/etc/conf.d/slapd' if you want command line options passed to `slapd` when the service starts, but the defaults looked fine to me. Start `slapd`: # /etc/init.d/slapd start Add it to your default runlevel: # eselect rc add /etc/init.d/slapd default Test the server with $ ldapsearch -x -b '' -s base '(objectclass=*)' Build a hierarchy in your database (this will depend on your organizational structure): $ emacs /tmp/people.ldif $ cat /tmp/people.ldif version: 1 dn: dc=example, dc=com objectClass: dcObject objectClass: organization o: Example, Inc. dc: example dn: ou=people, dc=example,dc=com objectClass: organizationalUnit ou: people description: All people in organisation dn: cn=Manager, dc=example,dc=com objectClass: organizationalRole cn: Manager description: Directory Manager $ ldapadd -D "cn=Manager,dc=example,dc=com" -xW -f /tmp/people.ldif $ rm /tmp/people.ldif abook ----- If you currently keep your addresses in [abook][], you can export them to [LDIF][] with: $ abook --convert --infile ~/.abook/addressbook --outformat ldif \ | abook-ldif-cleanup.py --basedn 'ou=people,dc=example,dc=com' > dump.ldif where [[abook-ldif-cleanup.py]] does some compatibility processing using the [python-ldap][] module. Add the people to your LDAP database: $ ldapadd -D "cn=Manager,dc=example,dc=com" -xW -f dump.ldif To check if that worked, you can list all the entries in your database: $ ldapsearch -x -b 'dc=example,dc=com' '(objectclass=*)' Then remove the temporary files: $ rm -rf dump.ldif Aliases ------- Ok, we've put lots of people into the `people` OU, but what if we want to assign them to another department? We can use aliases ([RFC 4512][rfc4512]), the symlinks of the LDAP world. To see how this works, lets create a test OU to play with: $ emacs /tmp/test.ldif $ cat /tmp/test.ldif version: 1 dn: ou=test, dc=example,dc=com objectClass: organizationalUnit ou: testing $ ldapadd -D "cn=Manager,dc=example,dc=com" -xW -f /tmp/test.ldif $ rm /tmp/test.ldif Now assign one of your people to that group: $ emacs /tmp/alias.ldif $ cat /tmp/alias.ldif version: 1 dn: cn=Jane Doe, ou=test,dc=example,dc=com objectClass: alias aliasedObjectName: cn=Jane Doe, ou=people,dc=example,dc=com $ ldapadd -D "cn=Manager,dc=example,dc=com" -xW -f /tmp/alias.ldif $ rm /tmp/alias.ldif The `extensibleObject` class allows us to add the DN field, without it you get: $ ldapadd -D "cn=Manager,dc=example,dc=com" -xW -f /tmp/alias.ldif Enter LDAP Password: adding new entry "cn=Jane Doe, ou=test,dc=example,dc=com" ldap_add: Object class violation (65) additional info: attribute 'cn' not allowed You can search for all entries (including aliases) with $ ldapsearch -x -b 'ou=test, dc=example,dc=com' '(objectclass=*)' … dn: cn=Jane Doe,ou=test,dc=example,dc=com objectClass: alias objectClass: extensibleObject aliasedObjectName:: Y249TWljaGVsIFZhbGxpw6hyZXMsb3U9cGVvcGxlLGRjPXRyZW1pbHksZGM9dXM= … You can control dereferencing with the `-a` option: $ ldapsearch -x -a always -b 'ou=test, dc=example,dc=com' '(objectclass=*)' … dn: cn=Jane Doe,ou=people,dc=example,dc=com cn: Jane Doe sn: Doe … Once you've played around, you can remove the `test` OU and its descendants: $ ldapdelete -D "cn=Manager,dc=example,dc=com" -xW -r ou=test,dc=example,dc=com shelldap -------- There are a number of tools to make it easier to manage LDAP databases. Command line junkies will probably like [shelldap][]: $ shelldap --server ldapserver.example.com ~ > ls cn=Manager ou=people ~ > cat cn=Manager dn: cn=Manager,dc=example,dc=com objectClass: organizationalRole cn: Manager ~ > cd ou=people ou=people,~ > ls Shelldap's `edit` command spawns your `EDITOR` on a temporary file populated by the entry you're editing. You can either alter the entry as you see fit, or try something fancier in [LDIF][]. JPEG photos and binary data --------------------------- [inetOrgPerson][] has a [jpegPhoto][] attribute, which holds a base64 encoded JPEG. The easiest way to set this attribute is to use the `:<` delimiter mentioned in `ldif(5)` and [RFC 2849][rfc2849]: $ cat thumb.ldif version: 1 dn: cn=Jane Doe,ou=people,dc=example,dc=com changetype: modify add: jpegPhoto jpegPhoto:< file:///tmp/jdoe.jpeg - $ ldapmodify -f thumb.ldif You can extract the thumbnail from the database using: $ ldapsearch -tT /tmp "cn=Jane Doe" … jpegPhoto:< file:///tmp/ldapsearch-jpegPhoto-Vvg2Ot … Which dumps non-printable values (like our `jpegPhoto`) to temporary files. If you just want to look up someone's picture, take a look at my [[ldap-jpeg.py]] script. It searches for a query string in any of [cn][], [uid][], or [mail][], and for matching entries with a `jpegPhoto` attribute, it uses your [[mailcap]]-specified viewer to display the photo. Mutt ---- If you use the [[Mutt]] email client (or just want a simple way to query email addresses from the command line) there are a [number of scripts][mutts] available. Pick whichever sounds most appealing to you. I wrote up [[mutt-ldap.py|mutt-ldap]], which has since seen contributions from others and been pulled out into its own repository. Apple Address Book ------------------ You can configure Apple's [Address Book][aab] to search an LDAP directory. See [[Humanizing_OS_X]] for details. SSL/TLS ------- It took me a bit of work to get [SSL/TLS][] working with my [[GnuTLS]]-linked OpenLDAP. First, you'll probably need to generate new SSL/TLS keys (`/etc/openldap/ssl/*`) with [certtool][] (see [[X.509_certificates]]). Then add the following lines to `/etc/openldap/slapd.conf`: TLSCipherSuite NORMAL TLSCACertificateFile /etc/openldap/ssl/ca.crt TLSCertificateFile /etc/openldap/ssl/ldap.crt TLSCertificateKeyFile /etc/openldap/ssl/ldap.key TLSVerifyClient never Where `ca.crt`, `ldap.crt`, and `ldap.key` are your new CA, certificate, and private key. If you want to disable unencrypted connections completely, remove the `ldap://` entry from your `slapd` command line by editing (on Gentoo) `/etc/conf.d/slapd` so it has OPTS="-h 'ldaps:// ldapi://%2fvar%2frun%2fopenldap%2fslapd.sock'" Now you should be able to restart `slapd` so it will use the new configuration. Have clients running on your server use the local socket by editing `/etc/openldap/ldap.conf` to set: URI ldapi://%2fvar%2frun%2fopenldap%2fslapd.sock Test your server setup by running (on the server) $ ldapsearch -x -b '' -s base '(objectclass=*)' Copy your CA over to any client machines (I put it in `/etc/openldap/ssl/ldapserver.crt`), and set them up with the following two lines in `/etc/openldap/ldap.conf`: URI ldaps://ldapserver.example.com TLS_CACERT /etc/openldap/ssl/ldapserver.crt Test your client setup by running (on the client) $ ldapsearch -x -b '' -s base '(objectclass=*)' You can configure `shelldap` with the following lines in `~/.shelldap.rc`: server: ldaps://ldapserver.example.com tls: yes tls_cacert: /etc/openldap/ssl/ldapserver.crt You can configure `mutt-ldap.py` with the following lines in `~/.mutt-ldap.rc`: port = 636 ssl = yes Access control and authentication --------------------------------- There are a number of possible approaches to authentication for LDAP, so read the [admin manual][admin] for details. I've got [[Kerberos]] setup on my home system, and I'll walk through this setup here. ### Server side I expose the LDAPS port to the external world through my router, and I don't want anonymous users to be able to download all my contact information. The solution to this is to implement [access control][access]. For my situation, the following `/etc/openldap/slapd.conf` directives seemed appropriate: access to attrs=uid by anonymous auth by * read access to * by self write by anonymous auth by * read The first directive allows anonymous users to use the [uid][] attribute when authenticating, and allows authenticated users to read anyone's `uid` attribute. This keeps users from being able to change their own `uid`. The second directive allows authenticated users to update their own entry and to read every entry. Anonymous are allowed to authenticate themselves, but have no other privileges. Alright, so how should user's go about [authenticating][security]? We'll want to set `slapd` up as a Kerberos service, and have clients authenticate using [GSSAPI][]. For the LDAP service, we'll need a `ldap/@REALM` principal. Because we want that service to start automatically at boot, we need to keep its key in a keytab file. # kadmin.local -p jdoe/admin Authenticating as principal jdoe/admin with password. Password for jdoe/admin@R.EDU: kadmin.local: add_principal -randkey ldap/ldapserver.example.com WARNING: no policy specified for ldap/ldapserver.example.com@R.EDU; defaulting to no policy Principal "ldap/ldapserver.example.com@R.EDU" created. kadmin.local: ktadd -k /etc/openldap/krb5-ldap.keytab ldap/ldapserver.example.com Entry for principal kdap/ldapserver.example.com... … kadmin.local: quit # chown ldap:ldap /etc/openldap/krb5-ldap.keytab You need use `kadmin.local` here (instead of `kadmin`) so the process has premission to create and edit the keytab file. You'll need to point your `slapd` server to the new keytab. On [[Gentoo]], you do this by uncommenting KRB5_KTNAME=/etc/openldap/krb5-ldap.keytab in `/etc/conf.d/slapd`. On Red Hat, you add export KRB5_KTNAME=/etc/openldap/ldap.keytab to `/etc/sysconfig/ldap`. You should also configure your realm and hostname in `/etc/openldap/slapd.conf`: sasl-realm R.EDU sasl-host ldapserver.example.com You'll also want to associate user's Kerberos principles to LDAP DNs. The template `slapd` uses is: uid=,cn=,cn=gssapi,cn=auth so `jdoe@R.EDU` is associated with uid=jdoe,cn=r.edu,cn=gssapi,cn=auth and `jdoe/admin@R.EDU` is associated with uid=jdoe/admin,cn=r.edu,cn=gssapi,cn=auth You'll probably want to [map these authentication DNs][map] to the appropriate directory entry, for example: cn=Jane Doe,ou=people,dc=r,dc=edu There are a number of ways to this, but I chose authz-regexp uid=([^,]*),cn=r.edu,cn=gssapi,cn=auth ldap:///ou=people,dc=r,dc=edu??one?(uid=$1) From the manual: > This will initiate an internal search of the LDAP database inside > the slapd server. If the search returns exactly one entry, it is > accepted as being the DN of the user. If there are more than one > entries returned, or if there are zero entries returned, the > authentication fails and the user's connection is left bound as the > authentication request DN. [Indexing][index] sounds like a good idea, so we turn it on with index objectClass eq index uid eq index cn,mail sub If you change your index configuration, you'll have to stop `slapd` and run `slapindex` to regenerate the indexes. ### Client side Users will have to do the usual `kinit` to get their Ticket Granting Ticket (TGT), and then instruct their client software to use GSSAPI (`-Y GSSAPI` with the OpenLDAP client tools). If you don't want to type `-Y GSSAPI`, you can add SASL_MECH GSSAPI to your `~/.ldaprc`. If you're on Gentoo, you'll want the `kerberos` and `sasl` `USE` flags set when you emerge `openldap`. #### Reverse DNS issues Because my SLAPD server runs on a dynamic IP address, I ran into trouble with reverse DNS. The client would resolve the server address into an IP, then resolve that IP address to its canonical name, and asks the ticket granting server (TGS) for authorization to use `ldap/@REALM`. Because the dynamic canonical name doesn't match the hostname, the TGS denies the request, leading to output like: $ ldapwhoami -Y GSSAPI ldap_sasl_interactive_bind_s: Local error (-2) additional info: SASL(-1): generic failure: GSSAPI Error: Unspecified GSS failure. Minor code may provide more information (Server krbtgt/EDU@R.EDU not found in Kerberos database) And messages like: … krb5kdc[15239](info): TGS_REQ (4 etypes {18 17 16 23}) …: UNKNOWN_SERVER: authtime 0, jdoe@R.EDU for host/some.dynamic.canonical.host.net@R.EDU, Server not found in Kerberos database in the server's KDC log. I tried disabling the reverse DNS lookup with both the `-N` command line option to `ldapwhoami` and the `SASL_NOCANON true` option in `~/.ldaprc`. I also added: [libdefaults] rdns = false to my client's `/etc/krb5.conf`. Even with all of these, I was still getting reverse DNS attempts, so I gave up and just added an entry to `/etc/hosts` to ensure I got the right hostname when the client tried to resolve it. You can get more detailed messages from `ldapwhoami` by increasing the debuglevel (for example, with the `-d 1` option), which helps when you're troubleshooting these kinds of issues. For example: $ ldapwhoami -d 1 -Y GSSAPI … ldap_int_sasl_open: host=some.dynamic.canonical.host.net … $ ldapwhoami -d 1 -Y GSSAPI -N … ldap_int_sasl_open: host=ldapserver.example.com … Currently, `ldapwhoami` and friends will ignore the `SASL_NOCANON` configuration option and only respect the `-N` command line option. I've submitted [an OpenLDAP bug][7271] fixing this (included in version 2.4.32, 2012-07-31), but there is still a reverse DNS call happening at some point. Debian-based systems -------------------- I wanted to mirror my home LDAP info on my public Ubuntu server. Here's a quick rundown of the Ubuntu setup. Install OpenLDAP: $ sudo apt-get install slapd ldap-utils Don't serve in the clear: $ cat /etc/default/slapd … SLAPD_SERVICES="ldaps:/// ldapi:///" … Avoid `Unrecognized database type (hdb)` by loading the `hdb` backend module before declaring `hdb` databases: $ sudo cat /etc/ldap/slapd.conf … moduleload back_hdb database hdb … Convert the old school `slapd.conf` to the new [slapd.d][]: $ sudo mv slapd.d{,.bak} $ sudo mkdir slapd.d $ sudo slaptest -f slapd.conf -F slapd.d … hdb_db_open: database "dc=example,dc=com": db_open(/var/lib/slapd/id2entry.bdb) failed: No such file or directory (2). … slap_startup failed (test would succeed using the -u switch) … $ sudo chown -R openldap.openldap slapd.d Don't worry about that `db_open` error, the conversion to `slapd.d` will have completed successfully. Set permissions on the database directory (note that the databases should be under `/var/lib/ldap` to match Ubuntu's default apparmor config. Otherwise you'll see `invalid path: Permission denied` errors when `slapd` tries to initialize the databaes). $ sudo chown openldap.openldap /var/lib/ldap/ $ sudo chmod 750 /var/lib/ldap/ Configure your clients $ cat /etc/ldap/ldap.conf BASE dc=example,dc=com URI ldaps://example.com TLS_CACERT /etc/ldap/ssl/ldapserver.crt Start `slapd` and add it to your default runlevel: $ sudo /etc/init.d/slapd start $ sudo update-rc.d slapd defaults Finally, import your directory data. Dump the data on your master server: master$ sudo slapcat -b 'dc=example,dc=com' > database.ldif Load the data on your slave: $ sudo /etc/init.d/slapd stop $ sudo slapadd -l database.ldif $ sudo /etc/init.d/slapd start References ---------- There's a [good overview][schema] of schema and objectclasses by Brian Jones on O'Reilly. If you want to use inetOrgPerson but also include the countryName attribute, ... [LDAP]: http://en.wikipedia.org/wiki/LDAP [rfc4510]: http://tools.ietf.org/html/rfc4510 [howto]: http://www.gentoo.org/doc/en/ldap-howto.xml [OpenLDAP]: http://www.openldap.org/ [admin]: http://www.openldap.org/doc/admin/ [inetOrgPerson]: http://tools.ietf.org/html/rfc2798 [abook]: http://abook.sourceforge.net/ [LDIF]: http://en.wikipedia.org/wiki/LDAP_Data_Interchange_Format [python-ldap]: http://www.python-ldap.org/ [rfc4512]: http://tools.ietf.org/html/rfc4512 [shelldap]: http://projects.martini.nu/shelldap/ [jpegPhoto]: http://tools.ietf.org/html/rfc2798#section-2.6 [cn]: http://tools.ietf.org/html/rfc2798#section-9.1.2 [uid]: http://tools.ietf.org/html/rfc2798#page-16 [mail]: http://tools.ietf.org/html/rfc2798#section-9.1.3 [jpegPhoto]: http://tools.ietf.org/html/rfc2798#section-2.6 [rfc2849]: http://tools.ietf.org/html/rfc2849 [mutts]: http://wiki.mutt.org/?QueryCommand [aab]: http://support.apple.com/kb/ht2486 [SSL/TLS]: http://en.wikipedia.org/wiki/Transport_Layer_Security [certtool]:http://www.gnu.org/software/gnutls/manual/html_node/Invoking-certtool.html#Invoking-certtool [access]: http://www.openldap.org/doc/admin24/access-control.html [security]: http://www.openldap.org/doc/admin24/security.html [GSSAPI]: http://en.wikipedia.org/wiki/Generic_Security_Services_Application_Program_Interface [map]: http://www.openldap.org/doc/admin24/sasl.html#Mapping%20Authentication%20Identities [index]: http://www.openldap.org/doc/admin24/tuning.html#Indexes [7271]: http://www.openldap.org/its/index.cgi?findid=7271 [slapd.d]: http://www.openldap.org/doc/admin24/slapdconf2.html [schema]: http://www.oreillynet.com/pub/a/sysadmin/2006/11/09/demystifying-ldap-data.html [[!tag tags/linux]] [[!tag tags/tools]]