Pages

Sunday, December 25, 2011

Creating an LDAP based address book

Imagine you have a small company with one manager, his secretary and a couple of workers. Each of these guys needs their own space to store some contacts, eg. the manager needs to store some manager contacts, the secretary needs to store some secretary contacts and workers need to store some contact about sales guys. At this point you need to create an organizational unit for your address book with more organizational units in it for all workers etc:

# vi addressbook.ldif
dn: ou=addressbook,dc=example,dc=com
objectclass: organizationalUnit
objectclass: top
ou: addressbook

dn: ou=manager,ou=addressbook,dc=example,dc=com
objectclass: organizationalUnit
objectclass: top
ou: manager

dn: ou=secretary,ou=addressbook,dc=example,dc=com
objectclass: organizationalUnit
objectclass: top
ou: secretary

dn: ou=worker1,ou=addressbook,dc=example,dc=com
objectclass: organizationalUnit
objectclass: top
ou: worker1

dn: ou=worker2,ou=addressbook,dc=example,dc=com
objectclass: organizationalUnit
objectclass: top
ou: worker2

Then add it to your LDAP:

# ldapadd -x -W -D 'cn=ldapadmin,dc=example,dc=com' -f addressbook.ldif
...

The above ldif file will create the address book ou with one manger, one secretary and two workers. Now you need to configure the ACL which is a little more tricky. Each manager, secretary and worker should only be able to read and write within their own address book. The manager should not be able to read the address book of one of his workers etc. Here is a sample how I solved it:

# cd /etc/openldap/
# vi acl.conf
...
access to dn.regex="^ou=([^,]+),ou=addressbook,dc=example,dc=com$"
  by dn.regex="^uid=$1,ou=users,dc=example,dc=com$$" write
  by * none

access to dn.children="ou=addressbook,dc=example,dc=com"
  by * write

access to dn.base="ou=addressbook,dc=example,dc=com"
  by * write
...

The ACL are read from the bottom to the top. First give write access to everybody to the complete address book. The second ACL dn.children is needed to create non existing contacts. With the first ACL you are only allowed to modify already existing contacts but not be able to create them. The third ACL defines that only the current authenticated user may write into his address book. Eg. the manager is logged in with uid manager stored in uid=manager,ou=users,dc=example,dc=com. Then he tries to change or add a contact. In this case he needs to authenticate to the LDAP server again and uses his uid=manager,ou=users,dc=example,dc=com again. The regex will check the login name and makes sure that only uid=manager,ou=users,dc=example,dc=com may write to ou=manager,ou=addressbook,dc=example,dc=com.
But sometimes you need some shared space that everybody can use. In this case you need some kind of an public address book too. The next ldif file will create such a address book under ou=addressbook,dc=example,dc=com:

# vi public.ldif
dn: ou=public,ou=addressbook,dc=example,dc=com
objectclass: organizationalUnit
objectclass: top
ou: public

Then add it to your LDAP again:

# ldapadd -x -W -D 'cn=ldapadmin,dc=example,dc=com' -f public.ldif
...

But I don't want that everybody has write access to the public address book. Only the secretary shall be able to write into the public address book, all others (including the manager) shall have read access only. If they want to change or add a contact then they have to go over the secretary. In this case you need to change the ACL one more time:

# cd /etc/openldap/
# vi acl.conf
...
access to dn.base="ou=public,ou=addressbook,dc=example,dc=com"
  by dn.exact="uid=secretary,ou=users,dc=example,dc=com" write
  by * read

access to dn.regex="^ou=([^,]+),ou=addressbook,dc=example,dc=com$"
  by dn.regex="^uid=$1,ou=users,dc=example,dc=com$$" write
  by * none

access to dn.children="ou=addressbook,dc=example,dc=com"
  by * write

access to dn.base="ou=addressbook,dc=example,dc=com"
  by * write
...

The ACL are read from the bottom to the top. The first three ACL are the same as above. The fourth ACL will give write access to the secretarys account, all other have only read access. That's all and all to easy again. If you configure some application (like kaddressbook/kontact) against it make sure that the user uses his own uid and that the DN maps against the appropiate address book of the user.
If you have trouble with the ACL then run the slapd in foreground with debug level 128, eg:

# /usr/libexec/slapd -h ldap://192.168.1.73:389 -d 128
...
<= acl_mask: [1] applying read(=rscxd) (stop)
<= acl_mask: [1] mask: read(=rscxd)
=> slap_access_allowed: read access granted by read(=rscxd)
=> access_allowed: read access granted by read(=rscxd)
...

Then search/add/modify any contact to see what happens.