1 I'm using [LDAP][] ([RFC 4510][rfc4510]) to maintain a centralized
2 address book at home. Here are my setup notes, mostly following
3 Gentoo's [LDAP howto][howto].
5 Install [OpenLDAP][] with the `ldap` USE flag enabled:
9 If you get complaints about a `cyrus-sasl` ↔ `openldap` dependency
10 cycle, you should temporarily (or permanently) disable the `ldap` USE
11 flag for `cyrus-sasl`:
13 # echo 'dev-libs/cyrus-sasl -ldap' > /etc/portage/package.use/ldap
14 # -ldap" emerge -av1 cyrus-sasl
17 Generate an administrative password:
21 Re-enter new password:
22 {SSHA}EzP6I82DZRnW+ou6lyiXHGxSpSOw2XO4
24 Configure the `slapd` LDAP server. Here is a very minimal
25 configuration, read the [OpenLDAP Admin Guide][admin] for details:
27 # emacs /etc/openldap/slapd.conf
28 # cat /etc/openldap/slapd.conf
29 include /etc/openldap/schema/core.schema
30 include /etc/openldap/schema/cosine.schema
31 include /etc/openldap/schema/inetorgperson.schema
32 pidfile /var/run/openldap/slapd.pid
33 argsfile /var/run/openldap/slapd.args
35 suffix "dc=example,dc=com"
37 rootdn "cn=Manager,dc=example,dc=com"
38 rootpw {SSHA}EzP6I82DZRnW+ou6lyiXHGxSpSOw2XO4
39 directory /var/lib/openldap-data
42 [inetOrgPerson][] is huge, but it's standardized. I think it's better
43 to pick a big standard right off, than to outgrow something smaller
46 Gentoo creates the default database directory for you, so you can
47 ignore warnings about needing to create it yourself.
49 Configure LDAP client access. Again, read the docs for details on
50 adapting this to your particular situation:
52 # emacs /etc/openldap/ldap.conf
53 $ cat /etc/openldap/ldap.conf
54 BASE dc=example,dc=com
55 URI ldap://ldapserver.example.com
57 You can edit '/etc/conf.d/slapd' if you want command line options
58 passed to `slapd` when the service starts, but the defaults looked
63 # /etc/init.d/slapd start
65 Add it to your default runlevel:
67 # eselect rc add /etc/init.d/slapd default
71 $ ldapsearch -x -b '' -s base '(objectclass=*)'
73 Build a hierarchy in your database (this will depend on your
74 organizational structure):
76 $ emacs /tmp/people.ldif
77 $ cat /tmp/people.ldif
80 dn: dc=example, dc=com
82 objectClass: organization
86 dn: ou=people, dc=example,dc=com
87 objectClass: organizationalUnit
89 description: All people in organisation
91 dn: cn=Manager, dc=example,dc=com
92 objectClass: organizationalRole
94 description: Directory Manager
95 $ ldapadd -D "cn=Manager,dc=example,dc=com" -xW -f /tmp/people.ldif
101 If you currently keep your addresses in [abook][], you can export them
104 $ abook --convert --infile ~/.abook/addressbook --outformat ldif \
105 | abook-ldif-cleanup.py --basedn 'ou=people,dc=example,dc=com' > dump.ldif
107 where [[abook-ldif-cleanup.py]] does some compatibility processing
108 using the [python-ldap][] module.
110 Add the people to your LDAP database:
112 $ ldapadd -D "cn=Manager,dc=example,dc=com" -xW -f dump.ldif
114 To check if that worked, you can list all the entries in your
117 $ ldapsearch -x -b 'dc=example,dc=com' '(objectclass=*)'
119 Then remove the temporary files:
126 Ok, we've put lots of people into the `people` OU, but what if we want
127 to assign them to another department? We can use aliases ([RFC
128 4512][rfc4512]), the symlinks of the LDAP world. To see how this
129 works, lets create a test OU to play with:
131 $ emacs /tmp/test.ldif
134 dn: ou=test, dc=example,dc=com
135 objectClass: organizationalUnit
137 $ ldapadd -D "cn=Manager,dc=example,dc=com" -xW -f /tmp/test.ldif
140 Now assign one of your people to that group:
142 $ emacs /tmp/alias.ldif
143 $ cat /tmp/alias.ldif
145 dn: cn=Jane Doe, ou=test,dc=example,dc=com
147 aliasedObjectName: cn=Jane Doe, ou=people,dc=example,dc=com
148 $ ldapadd -D "cn=Manager,dc=example,dc=com" -xW -f /tmp/alias.ldif
151 The `extensibleObject` class allows us to add the DN field, without it
154 $ ldapadd -D "cn=Manager,dc=example,dc=com" -xW -f /tmp/alias.ldif
156 adding new entry "cn=Jane Doe, ou=test,dc=example,dc=com"
157 ldap_add: Object class violation (65)
158 additional info: attribute 'cn' not allowed
160 You can search for all entries (including aliases) with
162 $ ldapsearch -x -b 'ou=test, dc=example,dc=com' '(objectclass=*)'
164 dn: cn=Jane Doe,ou=test,dc=example,dc=com
166 objectClass: extensibleObject
167 aliasedObjectName:: Y249TWljaGVsIFZhbGxpw6hyZXMsb3U9cGVvcGxlLGRjPXRyZW1pbHksZGM9dXM=
170 You can control dereferencing with the `-a` option:
172 $ ldapsearch -x -a always -b 'ou=test, dc=example,dc=com' '(objectclass=*)'
174 dn: cn=Jane Doe,ou=people,dc=example,dc=com
179 Once you've played around, you can remove the `test` OU and its
182 $ ldapdelete -D "cn=Manager,dc=example,dc=com" -xW -r ou=test,dc=example,dc=com
187 There are a number of tools to make it easier to manage LDAP
188 databases. Command line junkies will probably like [shelldap][]:
190 $ shelldap --server ldapserver.example.com
196 dn: cn=Manager,dc=example,dc=com
197 objectClass: organizationalRole
203 Shelldap's `edit` command spawns your `EDITOR` on a temporary file
204 populated by the entry you're editing. You can either alter the entry
205 as you see fit, or try something fancier in [LDIF][].
207 JPEG photos and binary data
208 ---------------------------
210 [inetOrgPerson][] has a [jpegPhoto][] attribute, which holds a base64
211 encoded JPEG. The easiest way to set this attribute is to use the
212 `:<` delimiter mentioned in `ldif(5)` and [RFC 2849][rfc2849]:
216 dn: cn=Jane Doe,ou=people,dc=example,dc=com
219 jpegPhoto:< file:///tmp/jdoe.jpeg
221 $ ldapmodify -f thumb.ldif
223 You can extract the thumbnail from the database using:
225 $ ldapsearch -tT /tmp "cn=Jane Doe"
227 jpegPhoto:< file:///tmp/ldapsearch-jpegPhoto-Vvg2Ot
230 Which dumps non-printable values (like our `jpegPhoto`) to temporary
233 If you just want to look up someone's picture, take a look at my
234 [[ldap-jpeg.py]] script. It searches for a query string in any of
235 [cn][], [uid][], or [mail][], and for matching entries with a
236 `jpegPhoto` attribute, it uses your [[mailcap]]-specified viewer to
242 If you use the [[Mutt]] email client (or just want a simple way to
243 query email addresses from the command line) there are a [number of
244 scripts][mutts] available. Pick whichever sounds most appealing to
245 you. I wrote up [[mutt-ldap.py]], which lets you configuration the
246 connection details via a config file (`~/.mutt-ldap.rc`) rather than
247 editing the script itself. Usage details are available in the
253 You can configure Apple's [Address Book][aab] to search an LDAP
254 directory. See [[Humanizing_OS_X]] for details.
259 It took me a bit of work to get [SSL/TLS][] working with my
260 [[GnuTLS]]-linked OpenLDAP. First, you'll probably need to generate
261 new SSL/TLS keys (`/etc/openldap/ssl/*`) with [certtool][] (see
262 [[X.509_certificates]]). Then add the following lines to
263 `/etc/openldap/slapd.conf`:
265 TLSCipherSuite NORMAL
266 TLSCACertificateFile /etc/openldap/ssl/ca.crt
267 TLSCertificateFile /etc/openldap/ssl/ldap.crt
268 TLSCertificateKeyFile /etc/openldap/ssl/ldap.key
269 TLSVerifyClient never
271 Where `ca.crt`, `ldap.crt`, and `ldap.key` are your new CA,
272 certificate, and private key. If you want to disable unencrypted
273 connections completely, remove the `ldap://` entry from your `slapd`
274 command line by editing (on Gentoo) `/etc/conf.d/slapd` so it has
276 OPTS="-h 'ldaps:// ldapi://%2fvar%2frun%2fopenldap%2fslapd.sock'"
278 Now you should be able to restart `slapd` so it will use the new
281 Have clients running on your server use the local socket by editing
282 `/etc/openldap/ldap.conf` to set:
284 URI ldapi://%2fvar%2frun%2fopenldap%2fslapd.sock
286 Test your server setup by running (on the server)
288 $ ldapsearch -x -b '' -s base '(objectclass=*)'
290 Copy your CA over to any client machines (I put it in
291 `/etc/openldap/ssl/ldapserver.crt`), and set them up with the
292 following two lines in `/etc/openldap/ldap.conf`:
294 URI ldaps://ldapserver.example.com
295 TLS_CACERT /etc/openldap/ssl/ldapserver.crt
297 Test your client setup by running (on the client)
299 $ ldapsearch -x -b '' -s base '(objectclass=*)'
301 You can configure `shelldap` with the following lines in
304 server: ldaps://ldapserver.example.com
306 tls_cacert: /etc/openldap/ssl/ldapserver.crt
308 You can configure `mutt-ldap.py` with the following lines in
314 Access control and authentication
315 ---------------------------------
317 There are a number of possible approaches to authentication for LDAP,
318 so read the [admin manual][admin] for details. I've got [[Kerberos]]
319 setup on my home system, and I'll walk through this setup here.
323 I expose the LDAPS port to the external world through my router, and I
324 don't want anonymous users to be able to download all my contact
325 information. The solution to this is to implement [access
326 control][access]. For my situation, the following
327 `/etc/openldap/slapd.conf` directives seemed appropriate:
338 The first directive allows anonymous users to use the [uid][]
339 attribute when authenticating, and allows authenticated users to read
340 anyone's `uid` attribute. This keeps users from being able to change
343 The second directive allows authenticated users to update their own
344 entry and to read every entry. Anonymous are allowed to authenticate
345 themselves, but have no other privileges.
347 Alright, so how should user's go about [authenticating][security]?
348 We'll want to set `slapd` up as a Kerberos service, and have clients
349 authenticate using [GSSAPI][].
351 For the LDAP service, we'll need a `ldap/<fqdn>@REALM` principal.
352 Because we want that service to start automatically at boot, we need
353 to keep its key in a keytab file.
355 # kadmin.local -p jdoe/admin
356 Authenticating as principal jdoe/admin with password.
357 Password for jdoe/admin@R.EDU:
358 kadmin.local: add_principal -randkey ldap/ldapserver.example.com
359 WARNING: no policy specified for ldap/ldapserver.example.com@R.EDU; defaulting to no policy
360 Principal "ldap/ldapserver.example.com@R.EDU" created.
361 kadmin.local: ktadd -k /etc/openldap/krb5-ldap.keytab ldap/ldapserver.example.com
362 Entry for principal kdap/ldapserver.example.com...
365 # chown ldap:ldap /etc/openldap/krb5-ldap.keytab
367 You need use `kadmin.local` here (instead of `kadmin`) so the process
368 has premission to create and edit the keytab file.
370 You'll need to point your `slapd` server to the new keytab. On
371 [[Gentoo]], you do this by uncommenting
373 KRB5_KTNAME=/etc/openldap/krb5-ldap.keytab
375 in `/etc/conf.d/slapd`. On Red Hat, you add
377 export KRB5_KTNAME=/etc/openldap/ldap.keytab
379 to `/etc/sysconfig/ldap`.
381 You should also configure your realm and hostname in
382 `/etc/openldap/slapd.conf`:
385 sasl-host ldapserver.example.com
387 You'll also want to associate user's Kerberos principles to LDAP DNs.
388 The template `slapd` uses is:
390 uid=<primary[/instance]>,cn=<realm>,cn=gssapi,cn=auth
392 so `jdoe@R.EDU` is associated with
394 uid=jdoe,cn=r.edu,cn=gssapi,cn=auth
396 and `jdoe/admin@R.EDU` is associated with
398 uid=jdoe/admin,cn=r.edu,cn=gssapi,cn=auth
400 You'll probably want to [map these authentication DNs][map] to the
401 appropriate directory entry, for example:
403 cn=Jane Doe,ou=people,dc=r,dc=edu
405 There are a number of ways to this, but I chose
408 uid=([^,]*),cn=r.edu,cn=gssapi,cn=auth
409 ldap:///ou=people,dc=r,dc=edu??one?(uid=$1)
413 > This will initiate an internal search of the LDAP database inside
414 > the slapd server. If the search returns exactly one entry, it is
415 > accepted as being the DN of the user. If there are more than one
416 > entries returned, or if there are zero entries returned, the
417 > authentication fails and the user's connection is left bound as the
418 > authentication request DN.
420 [Indexing][index] sounds like a good idea, so we turn it on with
426 If you change your index configuration, you'll have to stop `slapd`
427 and run `slapindex` to regenerate the indexes.
431 Users will have to do the usual `kinit` to get their Ticket Granting
432 Ticket (TGT), and then instruct their client software to use GSSAPI
433 (`-Y GSSAPI` with the OpenLDAP client tools). If you don't want to
434 type `-Y GSSAPI`, you can add
438 to your `~/.ldaprc`. If you're on Gentoo, you'll want the `kerberos`
439 and `sasl` `USE` flags set when you emerge `openldap`.
441 #### Reverse DNS issues
443 Because my SLAPD server runs on a dynamic IP address, I ran into
444 trouble with reverse DNS. The client would resolve the server address
445 into an IP, then resolve that IP address to its canonical name, and
446 asks the ticket granting server (TGS) for authorization to use
447 `ldap/<canonical>@REALM`. Because the dynamic canonical name doesn't
448 match the hostname, the TGS denies the request, leading to output
451 $ ldapwhoami -Y GSSAPI
452 ldap_sasl_interactive_bind_s: Local error (-2)
453 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)
457 … 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
459 in the server's KDC log.
461 I tried disabling the reverse DNS lookup with both the `-N` command
462 line option to `ldapwhoami` and the `SASL_NOCANON true` option in
463 `~/.ldaprc`. I also added:
468 to my client's `/etc/krb5.conf`. Even with all of these, I was still
469 getting reverse DNS attempts, so I gave up and just added an entry to
470 `/etc/hosts` to ensure I got the right hostname when the client tried
473 You can get more detailed messages from `ldapwhoami` by increasing the
474 debuglevel (for example, with the `-d 1` option), which helps when
475 you're troubleshooting these kinds of issues. For example:
477 $ ldapwhoami -d 1 -Y GSSAPI
479 ldap_int_sasl_open: host=some.dynamic.canonical.host.net
481 $ ldapwhoami -d 1 -Y GSSAPI -N
483 ldap_int_sasl_open: host=ldapserver.example.com
486 Currently, `ldapwhoami` and friends will ignore the `SASL_NOCANON`
487 configuration option and only respect the `-N` command line option.
488 I've submitted [an OpenLDAP bug][7271] fixing this (included in
489 version 2.4.32, 2012-07-31), but there is still a reverse DNS call
490 happening at some point.
495 I wanted to mirror my home LDAP info on my public Ubuntu server.
496 Here's a quick rundown of the Ubuntu setup. Install OpenLDAP:
498 $ sudo apt-get install slapd ldap-utils
500 Don't serve in the clear:
502 $ cat /etc/default/slapd
504 SLAPD_SERVICES="ldaps:/// ldapi:///"
507 Avoid `Unrecognized database type (hdb)` by loading the `hdb` backend
508 module before declaring `hdb` databases:
510 $ sudo cat /etc/ldap/slapd.conf
516 Convert the old school `slapd.conf` to the new [slapd.d][]:
518 $ sudo mv slapd.d{,.bak}
520 $ sudo slaptest -f slapd.conf -F slapd.d
522 hdb_db_open: database "dc=example,dc=com": db_open(/var/lib/slapd/id2entry.bdb) failed: No such file or directory (2).
524 slap_startup failed (test would succeed using the -u switch)
526 $ sudo chown -R openldap.openldap slapd.d
528 Don't worry about that `db_open` error, the conversion to `slapd.d`
529 will have completed successfully.
531 Set permissions on the database directory (note that the databases
532 should be under `/var/lib/ldap` to match Ubuntu's default apparmor
533 config. Otherwise you'll see `invalid path: Permission denied` errors
534 when `slapd` tries to initialize the databaes).
536 $ sudo chown openldap.openldap /var/lib/ldap/
537 $ sudo chmod 750 /var/lib/ldap/
539 Configure your clients
541 $ cat /etc/ldap/ldap.conf
542 BASE dc=example,dc=com
543 URI ldaps://example.com
544 TLS_CACERT /etc/ldap/ssl/ldapserver.crt
546 Start `slapd` and add it to your default runlevel:
548 $ sudo /etc/init.d/slapd start
549 $ sudo update-rc.d slapd defaults
551 Finally, import your directory data. Dump the data on your master
554 master$ sudo slapcat -b 'dc=example,dc=com' > database.ldif
556 Load the data on your slave:
558 $ sudo /etc/init.d/slapd stop
559 $ sudo slapadd -l database.ldif
560 $ sudo /etc/init.d/slapd start
565 There's a [good overview][schema] of schema and objectclasses by Brian
566 Jones on O'Reilly. If you want to use inetOrgPerson but also include
567 the countryName attribute, ...
569 [LDAP]: http://en.wikipedia.org/wiki/LDAP
570 [rfc4510]: http://tools.ietf.org/html/rfc4510
571 [howto]: http://www.gentoo.org/doc/en/ldap-howto.xml
572 [OpenLDAP]: http://www.openldap.org/
573 [admin]: http://www.openldap.org/doc/admin/
574 [inetOrgPerson]: http://tools.ietf.org/html/rfc2798
575 [abook]: http://abook.sourceforge.net/
576 [LDIF]: http://en.wikipedia.org/wiki/LDAP_Data_Interchange_Format
577 [python-ldap]: http://www.python-ldap.org/
578 [rfc4512]: http://tools.ietf.org/html/rfc4512
579 [shelldap]: http://projects.martini.nu/shelldap/
580 [jpegPhoto]: http://tools.ietf.org/html/rfc2798#section-2.6
581 [cn]: http://tools.ietf.org/html/rfc2798#section-9.1.2
582 [uid]: http://tools.ietf.org/html/rfc2798#page-16
583 [mail]: http://tools.ietf.org/html/rfc2798#section-9.1.3
584 [jpegPhoto]: http://tools.ietf.org/html/rfc2798#section-2.6
585 [rfc2849]: http://tools.ietf.org/html/rfc2849
586 [mutts]: http://wiki.mutt.org/?QueryCommand
587 [aab]: http://support.apple.com/kb/ht2486
588 [SSL/TLS]: http://en.wikipedia.org/wiki/Transport_Layer_Security
589 [certtool]:http://www.gnu.org/software/gnutls/manual/html_node/Invoking-certtool.html#Invoking-certtool
590 [access]: http://www.openldap.org/doc/admin24/access-control.html
591 [security]: http://www.openldap.org/doc/admin24/security.html
592 [GSSAPI]: http://en.wikipedia.org/wiki/Generic_Security_Services_Application_Program_Interface
593 [map]: http://www.openldap.org/doc/admin24/sasl.html#Mapping%20Authentication%20Identities
594 [index]: http://www.openldap.org/doc/admin24/tuning.html#Indexes
595 [7271]: http://www.openldap.org/its/index.cgi?findid=7271
596 [slapd.d]: http://www.openldap.org/doc/admin24/slapdconf2.html
597 [schema]: http://www.oreillynet.com/pub/a/sysadmin/2006/11/09/demystifying-ldap-data.html