OpenLDAP
I wanted to achieve 3 things:
- Ability to enable/disable account and domain
- Proper groups and membership
- Authorization for services
I personally don’t like OLC or On-Line Config, as I like to do my things using Ansible. So here is the slapd.conf:
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/opendkim.schema
include /usr/local/etc/openldap/schema/pmi.schema
pidfile /var/run/openldap/slapd.pid
argsfile /var/run/openldap/slapd.args
modulepath /usr/local/libexec/openldap
moduleload back_mdb
moduleload memberof
overlay memberof
memberof-group-oc groupOfUniqueNames
memberof-member-ad uniqueMember
memberof-refint TRUE
TLSCACertificateFile /usr/local/etc/openldap/certs/chain.pem
TLSCertificateFile /usr/local/etc/openldap/certs/fullchain.pem
TLSCertificateKeyFile /usr/local/etc/openldap/certs/privkey.pem
security ssf=128 tls=1
access to attrs=userPassword
by self write
by anonymous auth
access to *
by self write
by users read
by anonymous auth
database mdb
suffix "dc=ldap"
rootdn "cn=root,dc=ldap"
directory /var/db/openldap-data
index objectClass,mail eq
include /usr/local/etc/openldap/slapd-secret.conf
include /usr/local/etc/openldap/slapd-multimaster.conf
And this is the interesting part of the directory:
dn: dc=ldap
objectClass: domain
dc: ldap
dn: dc=account,dc=ldap
objectClass: domain
dc: account
dn: ou=meka.rs,dc=account,dc=ldap
objectClass: organizationalUnit
ou: meka.rs
dn: uid=meka,ou=meka.rs,dc=account,dc=ldap
objectClass: pilotPerson
objectClass: posixAccount
cn: Goran
sn: Mekić
uidNumber: 65534
gidNumber: 65534
homeDirectory: /var/mail/domains/meka.rs/meka
mail: meka@meka.rs
userClass: enabled
uid: meka
dn: dc=group,dc=ldap
objectClass: domain
dc: group
dn: cn=mail,dc=group,dc=ldap
objectClass: groupOfUniqueNames
cn: mail
uniqueMember: uid=meka,ou=meka.rs,dc=account,dc=ldap
dn: dc=service,dc=ldap
objectClass: domain
dc: service
dn: cn=postfix,dc=service,dc=ldap
objectClass: person
cn: postfix
sn: service
description: SMTP service
Let me ignore enable/disable of domain for a bit. Let’s just focus on accounts.
In slapd.conf, every line with memberof string in it is for groups. By
default, memberof module uses groupOfNames, but I think it is better to use
groupOfUniqueNames, so it needs some extra configuration. Let’s see what it
provides.
ldapsearch -x -Z -W -D cn=root,dc=ldap memberOf=cn=mail,dc=group,dc=ldap '*' 'memberOf'
. . .
dn: uid=meka,ou=meka.rs,dc=account,dc=ldap
objectClass: pilotPerson
objectClass: posixAccount
cn: Goran
sn: Mekić
uidNumber: 65534
gidNumber: 65534
homeDirectory: /var/mail/domains/meka.rs/meka
mail: meka@meka.rs
userClass: enabled
uid: meka
memberOf: cn=mail,dc=group,dc=ldap
. . .
So with memberOf filter, you can easily get members of a group. Notice that
there’s '*' 'memberOf' at the end. That says “give me all attributes of an
object, plus give me memberOf”. If you omit memberOf, it will not be
displayed, although you requested all attributes. That is because it is dynamic
attribute and it is returned only if explicitly requested. But there are few
cases I found that gave me headache. For example, if you create the group, then
configure slapd.conf to use it, it won’t work. I guess that something is
triggered on creation and/or modification of a group that is not triggered in
this scenario. Another anomaly I found is when I’m restoring from backup. For
some reason, groups are created before accounts, so memberOf doesn’t work.
Having dc=group,dc=ldap separated from dc=account,dc=ldap allows you to
restore accounts before groups. Also, there is msuser.schema. I first thought
that I need to include it to be able to use memberOf, but that is wrong. When
I include this file, it defines memberOf and I guess that’s why it doesn’t work
the way I wanted.
Look closely at ldapsearch output and notice userClass: enabled. If extend
the previously used filter, you get something like this:
ldapsearch -x -Z -W -D cn=root,dc=ldap (&(userClass=enabled)(memberOf=cn=mail,dc=group,dc=ldap)) '*' 'memberOf'
Output will pretty much be the same, only difference is which accounts will be listed.
Enabling or disabling domain is partially working. For example, postfix has a
filter for domains, but dovecot does not, while ejabberd can’t use LDAP for list
of domains. Depending on the capabilities of a service, you might or might not
achieve this. For example, solution I use for ejabberd is to have group, just
like for the accounts, but for domains called enabled and use same memberOf
filter to get the list. Then I use that info in Ansible to provision
configuration file.
For authorization of services I chose not to use dc=account,dc=ldap for base,
but cn=<service>,dc=service,dc=ldap. That way services can not interfere with
user accounts, but beside different base, those are just like normal accounts.
For more context on how I use OpenLDAP, take a look at my set of services for communication as it might give you a broader picture what I’m trying to solve.