OpenLDAP is an open source implementation of the LDAP protocol. It includes libraries, clients, and a server. This page is about configuring and running the OpenLDAP Standalone LDAP Daemon slapd on Debian.

Initial Installation

Install the following packages:

debconf will prompt you for a password for the database administrator (or, in case of a noninteractive installation, a random password will be set).

Change default BaseDN

By default, an initial database is created using the system's DNS domain name. If your system is in the domain example.com, the database suffix (BaseDN) will be dc=example,dc=com. The domain name and other low-level details can be changed by running

dpkg-reconfigure -plow slapd

after installation.

To check the database suffix, once the server is running, use ldapsearch(1) to read the namingContexts attribute of the root DSE:

# ldapsearch -x -LLL -s base -b "" namingContexts
dn:
namingContexts: dc=example,dc=com

Tools

After the above installation, two groups of tools will be available on your system:

OpenLDAP specific

The OpenLDAP specific tools are low-level, and meant to be executed directly on the systems where slapd has been installed (they can generally be executed while slapd isn't running as they access the underlying database(s) directly).

LDAP generic

The generic tools can be used on servers as well as clients.

Configuration

Since version 2.3 (released in 2005), the actual configuration for OpenLDAP servers is managed within a special database (DIT), typically rooted at the cn=config entry. This configuration system is known as OpenLDAP online configuration, or OLC (and further described in slapd-config(5)).

The old configuration scheme, using a plain slapd.conf(5) file is still supported, but its use is deprecated and support for it will be withdrawn in a future release. There is, however, still a lot of online documentation which refers to the old configuration scheme and which therefore needs to be adapted to the new configuration scheme.

After the initial installation, the config tree will typically look something like this:

# ldapsearch -LLLQ -Y EXTERNAL -H ldapi:/// -b cn=config dn
dn: cn=config
dn: cn=module{0},cn=config
dn: cn=schema,cn=config
dn: cn={0}core,cn=schema,cn=config
dn: cn={1}cosine,cn=schema,cn=config
dn: cn={2}nis,cn=schema,cn=config
dn: cn={3}inetorgperson,cn=schema,cn=config
dn: olcDatabase={-1}frontend,cn=config
dn: olcDatabase={0}config,cn=config
dn: olcDatabase={1}mdb,cn=config

While editing a configuration using the various LDAP tools LDIF records may seem a bit unwieldy, such knowledge is anyway necessary for day-to-day interaction with the LDAP directory. Additionally, the advantage of online configuration is that configuration changes can be applied without having to restart the slapd server and, since the configuration is just another database, it is also replicated to other servers.

The mapping between the old configuration options and the new style options can often be determined by consulting the slapd-config(5) man page (and, if necessary, by comparing with the slapd.conf(5) man page). It is also possible to introspect a running slapd instance to find the configuration classes/attributes which are actually available on that particular instance.

To do so, first find the name of the (sub)entry holding the controlling (sub)schema for the cn=config DIT:

# ldapsearch -Y EXTERNAL -H ldapi:/// -LLLQ -b "cn=config" -s base subschemaSubentry
dn: cn=config
subschemaSubentry: cn=Subschema

then read out a list of attributes from this entry:

# ldapsearch -x -LLL -b cn=Subschema -s base '(objectClass=subschema)' +

Note: this will generally produce a lot of output, you might want to grep for things like the objectClass definition of the olcGlobal class:

# ldapsearch -x -LLL -b cn=Subschema -s base -o ldif-wrap=no '(objectClass=subschema)' + | \
grep "^objectClasses:" | \
grep "NAME 'olcGlobal'"

Schema Files

One of the consequences of the change from the slapd.conf configuration system to the online configuration system is that the handling of LDAP Schema files is also different.

Traditionally, files ending in .schema, either installed in /etc/ldap/schema or, sometimes, somewhere under /usr/share/doc/<package>/ were included in the legacy slapd.conf file.

Under the new configuration system, files ending in .ldif can be imported into the configuration using ldapadd(1). This copies them into the configuration database and the source files are no longer used afterward.

The slapd package includes .ldif versions of schema files in /etc/ldap/ldif, but many other packages (and online articles on configuring OpenLDAP) only provide .schema files, which need to be converted to .ldif format before they can be used.

One tool for doing so is the schema2ldif utility provided by the schema2ldif package:

# apt install schema2ldif
[...]
# schema2ldif < /usr/share/doc/<package>/<xyz>.schema > /tmp/<xyz>.ldif
# ldapadd -Q -Y EXTERNAL -H ldapi:/// -f /tmp/<xyz>.ldif 
adding new entry "cn=<xyz>,cn=schema,cn=config"

Database Backends

OpenLDAP supports a number of different database backends, the default one being MDB and the alternatives slated for removal. Unless you have specific needs, like running a legacy installation, you should stick with the default.

It is, however, worth pointing out that the backend type is reflected in the configuration tree (see, for example, the olcDatabase={1}mdb,cn=config entry in the previous section), meaning that you might have to adapt configuration examples provided in various guides found on the Internet if they are based on other backends.

Permissions

Administrator Access

In Buster and earlier releases, an administrator entry is created under the BaseDN (e.g. cn=admin,dc=example,dc=com) as part of the initial database creation:

# ldapsearch -LLLQ -Y EXTERNAL -H ldapi:/// -b dc=example,dc=com
dn: dc=example,dc=com
objectClass: top
objectClass: dcObject
objectClass: organization
o: example.com
dc: example

dn: cn=admin,dc=example,dc=com
objectClass: simpleSecurityObject
objectClass: organizationalRole
cn: admin
description: LDAP administrator

While no such entry is present after installing slapd in later releases:

# ldapsearch -LLLQ -Y EXTERNAL -H ldapi:/// -b dc=example,dc=com
dn: dc=example,dc=com
objectClass: top
objectClass: dcObject
objectClass: organization
o: example.com
dc: example

The administrator is also defined separately in the OpenLDAP configuration, on a per-datbase basis, via the olcRootDN and olcRootPW attributes:

# ldapsearch -LLLQ -Y EXTERNAL -H ldapi:/// -b "olcDatabase={1}mdb,cn=config" olcRootDN OlcRootPW
dn: olcDatabase={1}mdb,cn=config
olcRootDN: cn=admin,dc=example,dc=com
olcRootPW: {SSHA}37s466RsEERQnkgsaj5IL6MfW8JwRhdq

Note that the olcRootDN does not have to refer to an actual entry in the database. Also, the user defined in olcRootDN is not subject to any access control list, ACL, checks or other checks, such as password policies defined by using the ppolicy overlay, which may be a positive or a negative, depending on your preferences.

This duplicate administrator definition can be addressed either by deleting the cn=admin,dc=example,dc=com entry (which would match the behavior of later Debian packages):

# ldapdelete -x -D "cn=admin,dc=example,dc=com" -W -H ldapi:/// cn=admin,dc=example,dc=com
Enter LDAP Password: 

or by deleting the olcRootDN and olcRootPW attributes (in which case appropriate ACLs are necessary to give cn=admin,dc=example,dc=com sufficient rights):

# ldapmodify -Q -Y EXTERNAL -H ldapi:/// <<EOF
dn: olcDatabase={1}mdb,cn=config
changetype: modify
delete: olcRootPW
-
delete: olcRootDN

dn: olcDatabase={0}config,cn=config
changetype: modify
delete: olcRootDN
EOF
modifying entry "olcDatabase={1}mdb,cn=config"

modifying entry "olcDatabase={0}config,cn=config"

Access Control Lists

The current access control lists are stored as per-database olcAccess attributes in the cn=config database:

# ldapsearch -LLLQ -Y EXTERNAL -H ldapi:/// -b cn=config -s one olcAccess
dn: cn=module{0},cn=config

dn: cn=schema,cn=config

dn: olcBackend={0}mdb,cn=config

dn: olcDatabase={-1}frontend,cn=config
olcAccess: {0}to * by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth manage by * break
olcAccess: {1}to dn.exact="" by * read
olcAccess: {2}to dn.base="cn=Subschema" by * read

dn: olcDatabase={0}config,cn=config
olcAccess: {0}to * by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth manage by * break

dn: olcDatabase={1}mdb,cn=config
olcAccess: {0}to attrs=userPassword by self write by anonymous auth by * none
olcAccess: {1}to attrs=shadowLastChange by self write by * read
olcAccess: {2}to * by * read

The dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth entry is a rather prolix way of defining the system root user (uid and gid 0), connecting via SASL EXTERNAL authentication.

All in all, Debian's default access rules (shown above) allow only the system root user to change the configuration, by connecting with SASL EXTERNAL authentication (which is only possible over the ldapi:/// connection):

# ldapsearch -Y EXTERNAL -H ldapi:/// -b "cn=config"

In addition, the default access rules allow anyone to search the directory without any authentication:

# ldapsearch -x -b "dc=example,dc=com"

Finally, the database administrator has unrestricted access (via the olcRootDN and OlcRootPW attributes discussed above). To connect as the database administrator, use simple authentication, and when prompted, enter the password configured during installation:

# ldapsearch -x -D "cn=admin,dc=example,dc=com" -W -b "dc=example,dc=com"

Access Control Example 1 - chsh and chfn

Here's an example of how to change the default ACLs so that chsh and chfn can work with LDAP by giving users write access to their own (and administrator access to everyone's) loginShell and gecos attributes:

# ldapmodify -Y EXTERNAL -H ldapi:/// <<EOF
dn: olcDatabase={1}mdb,cn=config
changetype: modify
add: olcAccess
olcAccess: {1}to attrs=loginShell,gecos
  by dn="cn=admin,dc=example,dc=com" write
  by self write
  by * read
EOF

Access Control Example 2 - Multiple LDAP Admins

If you choose to use LDAP for many functions, such as having a single server for DNS, Authentication, and networking flat file database replacement, you may wish to have LDAP administrative users for each subtree in addition to the global admin (dn="cn=admin, dc=example, dc=com). The following example is useful when using a separate authentication tree which includes Samba.

 # The manager dn has full write access to the auth subtree
 # Everyone else has read access to not otherwise protected fields and entries
 access to dn.sub="ou=auth,dc=example,dc=com"
         by dn="cn=Manager,ou=auth,dc=example,dc=com" write
         by * read

Basic Tasks

Logging

The server logs are sent to the system log (syslog and/or the journal). The default log level is none.

To enable basic request logging, change the log level to stats:

# ldapmodify -Q -H ldapi:/// -Y EXTERNAL <<EOF
dn: cn=config
changetype: modify
replace: olcLogLevel
olcLogLevel: stats
EOF
modifying entry "cn=config"

If you wish to disable request logging later, repeat the procedure and set the log level to none.

For more information, read about olcLogLevel in the slapd-config(5) man page.

Database Max Size

The default database uses the LMDB storage backend. This backend requires little configuration or tuning, but there is one important parameter: the max size.

The database is stored in a sparse file, /var/lib/ldap/data.mdb. It has a fixed maximum size, specified by the olcDbMaxSize parameter. The default database has its max size configured to 1 GiB upon installation. When the database reaches its max size, writes (even updates to existing entries) will fail.

Use du(1) to check the actual space used by the database:

du -h /var/lib/ldap/data.mdb

To check the current max size of database #1:

ldapsearch -H ldapi:/// -Y EXTERNAL -b "olcDatabase={1}mdb,cn=config" olcDbMaxSize

To set the max size of database #1 to 10 GiB (10737418240 bytes):

ldapmodify -H ldapi:/// -Y EXTERNAL << EOF
dn: olcDatabase={1}mdb,cn=config
changetype: modify
replace: olcDbMaxSize
olcDbMaxSize: 10737418240

EOF

On 32-bit systems, the max size is constrained by address space limitations, and it may not be possible to grow the database larger than about 2 GiB. For larger databases, a 64-bit system is recommended.

For more information, read about maxsize in slapd-mdb(5).

Changing the Administrator's Password

The password configured during installation is saved in two places. To change the password, both values must be updated. If only one is changed, the old password can still be used. This should be fixed in the next release; see Debian bug #821331.

Use slappasswd(8) to hash the new password, and then use ldapmodify(1) to update the hashed password in the olcRootPW attribute in the database configuration.

# slappasswd
New password: newpassword
Re-enter new password: newpassword
{SSHA}zYHmkowzdMxwX0KtEPNak5IbzfY8YmdQ
# ldapmodify -H ldapi:/// -Y EXTERNAL
SASL/EXTERNAL authentication started
SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
SASL SSF: 0
dn: olcDatabase={1}mdb,cn=config
changetype: modify
replace: olcRootPW
olcRootPW: {SSHA}zYHmkowzdMxwX0KtEPNak5IbzfY8YmdQ

modifying entry "olcDatabase={1}mdb,cn=config"

Next, use ldappasswd(1) to change the password the administrator's account in the directory.

# ldappasswd -x -D cn=admin,dc=example,dc=com -W -S
New password: newpassword
Re-enter new password: newpassword
Enter LDAP Password: oldpassword

Finally, test authenticating with the old and new passwords, and confirm that only the new password can be used.

# ldapwhoami -x -D cn=admin,dc=example,dc=com -W
Enter LDAP Password: oldpassword
ldap_bind: Invalid credentials (49)
# ldapwhoami -x -D cn=admin,dc=example,dc=com -W
Enter LDAP Password: newpassword
dn:cn=admin,dc=example,dc=com

Indexes

For better performance do more indexing than the default:

# ldapmodify -Y EXTERNAL -H ldapi:/// <<EOF
dn: olcDatabase={1}mdb,cn=config
changetype: modify
add: olcDbIndex
olcDbIndex: cn pres,sub,eq
-
add: olcDbIndex
olcDbIndex: sn pres,sub,eq
-
add: olcDbIndex
olcDbIndex: uid pres,sub,eq
-
add: olcDbIndex
olcDbIndex: displayName pres,sub,eq
-
add: olcDbIndex
olcDbIndex: default sub
-
add: olcDbIndex
olcDbIndex: uidNumber eq
-
add: olcDbIndex
olcDbIndex: gidNumber eq
-
add: olcDbIndex
olcDbIndex: mail,givenName eq,subinitial
-
add: olcDbIndex
olcDbIndex: dc eq
EOF

Note: use the correct database backend in the first line, as noted above, the default type is currently mdb.

Do not leave out the - (dash character) from the file, it is needed. After execution of the ldapmodify command, slapd will launch a internal task to create indexes. Don't stop slapd during indexing.

TLS/SSL

Enabling TLS/SSL

To enable TLS in slapd, the server needs the server certificate and the associated private key, both in PEM format. You may also have an intermediate certificate. Clients will need the CA certificate which is the Issuer of the server or intermediate certificate.

The files must all be readable by the openldap user. It is recommended to ensure the private key is not readable by any user except openldap.

The error main: TLS init def ctx failed: -1 in server logs may indicate a permission problem on certificate/key files.

To configure the server certificate, private key, and intermediate certificate used by slapd:

ldapmodify -H ldapi:/// -Y EXTERNAL << EOF
dn: cn=config
changetype: modify
replace: olcTLSCertificateFile
olcTLSCertificateFile: /etc/ssl/certs/server.pem
-
replace: olcTLSCertificateKeyFile
olcTLSCertificateKeyFile: /etc/ssl/private/server.key
-
replace: olcTLSCACertificateFile
olcTLSCACertificateFile: /etc/ssl/certs/intermediate.pem

EOF

If you do not have an intermediate certificate, the olcTLSCACertificate lines should be omitted. The root CA does not need to be configured in slapd.

If the modifications fail with ldap_modify: Other (e.g., implementation specific) error (80), check the file paths for typos, and ensure the files are readable by the openldap user.

After applying the configuration, test a secure connection using StartTLS:

LDAPTLS_CACERT=/etc/ssl/certs/ca.pem ldapwhoami -H ldap://ldap.example.com -ZZ -x

where ca.pem is the root CA certificate and ldap.example.com is the server's name, exactly matching the Common Name (CN) or Subject Alternative Name (SAN) in the server certificate.

If the secure connection is successful, ldapwhoami should just print anonymous. If it fails, append -d 1 to the command line to enable debug output, and look for lines beginning with TLS:.

If a client uses an LDAP URL for connection configuration, it can be configured to use StartTLS by including the StartTLS extension in the URL (the ldapurl(1) tool is useful for constructing correct LDAP URLs). For example:

ldap://ldap.example.com/dc=example,dc=com????!StartTLS

Enabling LDAPS on port 636

By following the above instructions, slapd is configured to support StartTLS on the standard LDAP port 389. StartTLS is, however, vulnerable to downgrade attacks, which is why LDAPS (LDAP over SSL on port 636) has gone from being considered deprecated to being recommended.

Note that some LDAP clients do not support LDAPS, but are still able to use StartTLS.

If you wish to enable the LDAPS protocol on port 636, edit /etc/default/slapd, add ldaps:/// to the SLAPD_SERVICES line, and restart slapd (if you are certain that no plain/StartTLS-only clients are in use in your organization, you can optionally disable ldap:/// at the same time):

vi /etc/default/slapd

...
SLAPD_SERVICES="ldap:/// ldapi:/// ldaps:///"
...

service slapd restart

LDAPS can be tested the same way, using -H ldaps:// instead of -ZZ:

LDAPTLS_CACERT=/etc/ssl/certs/ca.pem ldapwhoami -H ldaps://ldap.example.com -x

Again, if successful, it should just print anonymous.

For more information, read about TLS Options in the slapd-config(5) man page, noting that the TLS implementation used in Debian is GnuTLS. The OpenLDAP Administrator's Guide also has a chapter about TLS.

Force StartTLS or SSL connection

By default, client can connect with StartTLS, SSL or without encryption. If you wish to disable connections without encryption and allow only secured connections with StartTLS or SSL, you need to add/modify the olcSecurity option with value ssf=n.

ssf stands for Security Strength Factor and is a measure of how strong the security of a given connection is considered to be. For a TLS/SSL connection, n usually corresponds to the key size. For other mechanisms (such as Keberos/GSSAPI, the ssf may have other values/meanings, 256 is a common value as these connections are generally considered to be of high security).

After configuring the (minimum) ssf, all connections with a lower ssf (for StartTLS/SSL, using key sizes less than ssf) will be denied. Common key length values are 128 and 256.

Moreover the olcLocalSSF setting defines the ssf value the server should assume for local (ldapi) connections. It has to be equal or higher than olcSecurity or ldapi connections will be denied.

ldapmodify -Q -Y EXTERNAL -H ldapi:/// <<EOF
dn: cn=config
changetype: modify
replace: olcLocalSSF
olcLocalSSF: 128
-
replace: olcSecurity
olcSecurity: ssf=128
EOF

Troubleshooting

[...] TLS: could not set cipher list HIGH:MEDIUM:-SSLv2. in slapd debug output:, [...] main: TLS init def ctx failed: -1 in syslog: If you try to install (slapd) with Debian Lenny, it comes compiled against the GnuTLS library. It means you cannot use an OpenSSL style directive like TLSCipherSuite HIGH:MEDIUM:-SSLv2 in slapd.conf.

In /etc/ldap/slapd.conf, either comment out TLSCipherSuite option to let gnutls choose rather sane default for you, or use something like TLSCipherSuite NORMAL

For info about the supported GnuTLS cipher suite names, see the gnutls-bin package and the TLS/SSL control options in gnutls-cli.1

To use only 256 bit ciphers, use this setting:

TLSCipherSuite SECURE256:!AES-128-CBC:!ARCFOUR-128:!CAMELLIA-128-CBC:!3DES-CBC:!CAMELLIA-128-CBC

Another useful tool to test server-supported TLS options is to use gnutls-cli-debug. First add ldaps:/// string to the SLAPD_SERVICES option in /etc/default/slapd, restart slapd, then run gnutls-cli-debug -p 636 <fqdn_of_you_ldap_host>. That will show you cryptographic suits your LDAP server supports.

slapd TLS: can't connect: A TLS packet with unexpected length was received.. or Could not negotiate a supported cipher suite.: Debian uses GnuTLS, and it doesn't play nice with OpenSSL certificates. Use the gnutls certificate generator certtool, available in gnutls-bin. See certtool.1.

# generate a CA private key
certtool --generate-privkey --outfile ca.key
# generate a self-signed CA certificate
certtool --generate-self-signed --load-privkey ca.key --outfile ca.crt

Since this certificate is self-signed, certificate verification must be disabled on LDAP clients:

If you have an existing CA private key/certificate and wish to sign slapd's certificate with it. this allows you to keep your existing OpenSSL CA:

certtool --generate-privkey --outfile ldap.key
certtool --generate-certificate --load-privkey ldap.key --outfile ldap.crt --load-ca-certificate my.ca.crt --load-ca-privkey my.ca.key

Verify with ldapsearch -x -LLL -s base -b ""

OpenLDAP as a Backend

Samba

Since version 4, Samba is able to act as an Active Directory Domain Controller, which essentially ties together LDAP, Kerberos, DNS and other services. In order to do so, Samba includes internal implementations of the necessary services. See Samba/ActiveDirectoryDomainController

It is still possible to use OpenLDAP as a backend for user account information for old, NT4-style Domain setups. See Samba/DcWithLdapBackend

Kerberos

Older/obsolete information on setting up LDAP + Kerberos can be found at LDAP/OpenLDAPSetup#Kerberos

Configuring MIT Kerberos to use OpenLDAP as a backend allows one to tie Kerberos principals together with user/machine accounts and can be used in lieu of ordinary passwords stored in the userPassword attribute.

In the following instructions, it will be assumed that the Kerberos KDC and the OpenLDAP server will be setup on the same system (server.example.com). If that is not the case, it should be easy enough to adapt the instructions below (in essence by using LDAP over TLS instead of the ldapi:/// unix socket).

First, install the packages containing the LDAP-enabled Kerberos servers (krb5-kdc-ldap and krb5-admin-server) and the schema2ldif tool:

# apt install krb5-kdc-ldap krb5-admin-server schema2ldif

Then load the kerberos schema:

# zcat /usr/share/doc/krb5-kdc-ldap/kerberos.openldap.ldif.gz | ldapadd -Q -Y EXTERNAL -H ldapi:/// 
adding new entry "cn=kerberos,cn=schema,cn=config"

And add an index on the krbPrincipalName (improves performance and also suppresses some log messages if slapd is configured to log more than default) for the database(s) where you intend to store Kerberos data:

# ldapmodify -Q -Y EXTERNAL -H ldapi:/// <<EOF
dn: olcDatabase={1}mdb,cn=config
add: olcDbIndex
olcDbIndex: krbPrincipalName eq,pres,sub
EOF

modifying entry "olcDatabase={1}mdb,cn=config"

Next, you need to create and configure two entries which will be used by the Kerberos servers to connect to OpenLDAP. If you are running Kerberos and OpenLDAP on the same system, these steps are optional, but recommended. In order to keep things nicely separated, everything will be created under a separate organizationalUnit. Note that a simple bind (-x -D) is used instead of an EXTERNAL bind since write access to the dc=example,dc=com DIT is necessary:

# ldapadd -x -D cn=admin,dc=example,dc=com -W <<EOF
dn: ou=Services,dc=example,dc=com
objectClass: organizationalUnit
objectClass: top
ou: Services

dn: ou=kerberos,ou=Services,dc=example,dc=com
objectClass: organizationalUnit
objectClass: top
ou: kerberos

dn: uid=kdc,ou=kerberos,ou=Services,dc=example,dc=com
uid: kdc
objectClass: account
objectClass: simpleSecurityObject
userPassword: {CRYPT}x
description: Kerberos KDC Account

dn: uid=kadmin,ou=kerberos,ou=Services,dc=example,dc=com
uid: kadmin
objectClass: account
objectClass: simpleSecurityObject
userPassword: {CRYPT}x
description: Kerberos Admin Server Account
EOF
Enter LDAP Password: SECRET

adding new entry "ou=Services,dc=example,dc=com"

adding new entry "ou=kerberos,ou=Services,dc=example,dc=com"

adding new entry "uid=kdc,ou=kerberos,ou=Services,dc=example,dc=com"

adding new entry "uid=kadmin,ou=kerberos,ou=Services,dc=example,dc=com"

The passwords that were set for the two users were placeholders, so it is necessary to specify real ones:

# ldappasswd -x -D cn=admin,dc=example,dc=com -W -S uid=kdc,ou=kerberos,ou=Services,dc=example,dc=com
New password: SECRET_A
Re-enter new password: SECRET_A 
Enter LDAP Password: SECRET
# ldappasswd -x -D cn=admin,dc=example,dc=com -W -S uid=kadmin,ou=kerberos,ou=Services,dc=example,dc=com
New password: SECRET_B
Re-enter new password: SECRET_B 
Enter LDAP Password: SECRET

Now the ACLs need to be adapted to grant appropriate permissions to these two new entities. Something like this:

ldapmodify -Q -Y EXTERNAL -H ldapi:/// <<EOF
dn: olcDatabase={1}mdb,cn=config
add: olcAccess
olcAccess: {0}to attrs=krbPrincipalKey
  by anonymous auth
  by dn.exact="uid=kdc,ou=kerberos,ou=Services,dc=example,dc=com" read
  by dn.exact="uid=kadmin,ou=kerberos,ou=Services,dc=example,dc=com" write
  by self write
  by * none
-
add: olcAccess
olcAccess: {1}to dn.subtree="cn=krbContainer,ou=kerberos,ou=Services,dc=example,dc=com"
  by dn.exact="uid=kdc,ou=kerberos,ou=Services,dc=example,dc=com" read
  by dn.exact="uid=kadmin,ou=kerberos,ou=Services,dc=example,dc=com" write
  by * none
EOF

modifying entry "olcDatabase={1}mdb,cn=config"

If you plan to let the KDC track the time of last login success and lockout status (see below), then you will need to change the lines referring to the uid=kdc,ou=kerberos,ou=Services,dc=example,dc=com entry so that it also has write access.

If you skipped creating the kdc and kadmin entities, you instead need to grant permissions to the root user:

ldapmodify -Q -Y EXTERNAL -H ldapi:/// <<EOF
dn: olcDatabase={1}mdb,cn=config
add: olcAccess
olcAccess: {0}to attrs=krbPrincipalKey
  by anonymous auth
  by dn.exact="dn:gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth"  write
  by self write
  by * none
-
add: olcAccess
olcAccess: {1}to dn.subtree="cn=krbContainer,ou=kerberos,ou=Services,dc=example,dc=com"
  by dn.exact="dn:gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth"  write
  by * none
EOF

modifying entry "olcDatabase={1}mdb,cn=config"

Note: if you plan to add principals to entries in subtrees other than cn=krbContainer,ou=kerberos,ou=Services,dc=example,dc=com, like ou=People,dc=example,dc=com, then the ACLs will need to be adapted to grant the appropriate permissions.

Next, create /etc/krb5.conf with the following basic information about your realm (this file should be copied to all clients as well):

[libdefaults]
        default_realm = EXAMPLE.COM
        dns_lookup_realm = false
        dns_lookup_kdc = false
        ticket_lifetime = 24h
        forwardable = true
        proxiable = true
        rdns = false

[realms]
        EXAMPLE.COM = {
                kdc = server.example.com
                admin_server = server.example.com
                default_domain = example.com
        }

And, put the server-specific configuration in /etc/krb5kdc/kdc.conf (which has the same format as /etc/krb5.conf):

[realms]
        EXAMPLE.COM = {
                database_module = openldap_ldapconf
        }

[dbmodules]
        openldap_ldapconf = {
                db_library = kldap

                ldap_kerberos_container_dn = cn=kerberos,ou=Services,dc=example,dc=com

                # if either of these is false, then the ldap_kdc_dn needs to
                # have write access as explained above
                disable_last_success = true
                disable_lockout = true
                ldap_conns_per_server = 5
                ldap_servers = ldapi:///

                # this object needs to have read rights on
                # the realm container, principal container and realm sub-trees
                ldap_kdc_dn = "uid=kdc,ou=kerberos,ou=Services,dc=example,dc=com"

                # this object needs to have read and write rights on
                # the realm container, principal container and realm sub-trees
                ldap_kadmind_dn = "uid=kadmin,ou=kerberos,ou=Services,dc=example,dc=com"

                # this file will be used to store plaintext passwords used
                # to connect to the LDAP server
                ldap_service_password_file = /etc/krb5kdc/service.keyfile

                # OR, comment out ldap_kdc_dn, ldap_kadmind_dn and
                # ldap_service_password_file above and enable the following
                # two lines, if you skipped the step of creating entries/users
                # for the Kerberos servers

                #ldap_kdc_sasl_mech = EXTERNAL
                #ldap_kadmind_sasl_mech = EXTERNAL
                #ldap_servers = ldapi:///
        }

And also create a /etc/krb5kdc/kadm5.acl file specifying the (future) Kerberos principal(s) which should have administrator access (traditionally, all principals ending with /admin):

*/admin@EXAMPLE.COM        *

Now, use kdb5_ldap_util to create the Kerberos realm by setting up some basic entries in the LDAP database:

# sudo kdb5_ldap_util -D cn=admin,dc=example,dc=com create -subtrees dc=example,dc=com -r EXAMPLE.COM -s -H ldapi:///
Password for "cn=admin,dc=example,dc=com": 
Initializing database for realm 'EXAMPLE.COM'
You will be prompted for the database Master Password.
It is important that you NOT FORGET this password.
Enter KDC database master key: SOME_SECRET
Re-enter KDC database master key to verify: SOME_SECRET 

And use the same utility to store the LDAP passwords which kadmin and kdc need to bind (connect) to the LDAP server (skip if you did not create these entries):

# kdb5_ldap_util -D cn=admin,dc=example,dc=com stashsrvpw -f /etc/krb5kdc/service.keyfile uid=kdc,ou=kerberos,ou=Services,dc=example,dc=com
Password for "cn=admin,dc=example,dc=com": SECRET 
Password for "uid=kdc,ou=kerberos,ou=Services,dc=example,dc=com": SECRET_A
Re-enter password for "uid=kdc,ou=kerberos,ou=Services,dc=example,dc=com": SECRET_A
# kdb5_ldap_util -D cn=admin,dc=example,dc=com stashsrvpw -f /etc/krb5kdc/service.keyfile uid=kadmin,ou=kerberos,ou=Services,dc=example,dc=com
Password for "cn=admin,dc=example,dc=com": SECRET 
Password for "uid=kadmin,ou=kerberos,ou=Services,dc=example,dc=com": SECRET_B
Re-enter password for "uid=kadmin,ou=kerberos,ou=Services,dc=example,dc=com": SECRET_B

Now it's finally time to start the Kerberos servers:

# systemctl start krb5-kdc krb5-admin-server

Try adding a Kerberos principal with the kadmin.local tool:

# kadmin.local 
Authenticating as principal root/admin@EXAMPLE.COM with password.
kadmin.local:  addprinc bob
WARNING: no policy specified for bob@EXAMPLE.COM; defaulting to no policy
Enter password for principal "bob@EXAMPLE.COM": BOB_SECRET
Re-enter password for principal "bob@EXAMPLE.COM": BOB_SECRET
Principal "bob@EXAMPLE.COM" created.
kadmin.local:  q

By default, principals will be created in the container defined with ldap_kerberos_container_dn in /etc/krb5.conf above. If you want to add a principal somewhere else in the DIT (including to an existing entry), use the -x option to addprinc:

# kadmin.local 
Authenticating as principal root/admin@EXAMPLE.COM with password.
kadmin.local:  addprinc -x uid=alice,ou=People,dc=example,dc=com alice
WARNING: no policy specified for alice@EXAMPLE.COM; defaulting to no policy
Enter password for principal "alice@EXAMPLE.COM": ALICE_SECRET
Re-enter password for principal "alice@EXAMPLE.COM": ALICE_SECRET
Principal "alice@EXAMPLE.COM" created.
kadmin.local:  q

If you want to be able to use Kerberos to secure LDAP queries from clients, you need to create an appropriate principal (ldap/server.example.com) on the server and export it to a keytab (e.g. /etc/krb5.ldap.keytab):

# kadmin.local 
Authenticating as principal root/admin@EXAMPLE.COM with password.
kadmin.local:  addprinc -randkey ldap/server.example.com
WARNING: no policy specified for ldap/server.example.com@EXAMPLE.COM; defaulting to no policy
Principal "ldap/server.example.com@EXAMPLE.COM" created.
kadmin.local:  ktadd -k /etc/krb5.ldap.keytab ldap/server.example.com
Entry for principal ldap/server.example.com with kvno 3, encryption type aes256-cts-hmac-sha1-96 added to keytab WRFILE:/etc/krb5.ldap.keytab.
Entry for principal ldap/server.example.com with kvno 3, encryption type aes128-cts-hmac-sha1-96 added to keytab WRFILE:/etc/krb5.ldap.keytab.
kadmin.local:  q

Change the permissions of the keytab so that the openldap user or group can read it, but nobody else:

# chown root:openldap /etc/krb5.ldap.keytab 
# chmod 0640 /etc/krb5.ldap.keytab

Next, edit /etc/default/slapd to tell slapd where to find the keytab by uncommenting the export KRB5_KTNAME and setting it to the location of the keytab you just created:

export KRB5_KTNAME=/etc/krb5.ldap.keytab

And restart slapd:

# systemctl restart slapd

Then create a regular expression mapping Kerberos principals to LDAP entries, something like:

# ldapmodify -H ldapi:/// -Y EXTERNAL << EOF
dn: cn=config
changetype: modify
replace: olcAuthzRegexp
olcAuthzRegexp: {0}"uid=([^/]*)/admin,(cn=example.com,)?cn=gssapi,cn=auth" "cn=admin,dc=example,dc=com"
olcAuthzRegexp: {1}"uid=([^/]*),(cn=example.com,)?cn=gssapi,cn=auth" "uid=$1,ou=People,dc=example,dc=com
EOF

Now, on the same or a different system (but one which has a suitable /etc/krb5.conf installed), initialize a Kerberos principal and try connecting with it (using the -Y GSSAPI mechanism):

# kinit alice
Password for alice@EXAMPLE.COM:
# klist
Ticket cache: FILE:/tmp/krb5cc_xyz
Default principal: alice@EXAMPLE.COM
...
# ldapwhoami -Q -Y GSSAPI -H ldapi:///
dn:uid=alice,ou=People,dc=example,dc=com

If you want to setup a secondary KDC, first setup OpenLDAP replication to the second server and then:

DNS/Bind9

OpenLDAP can also be used as a backend to store DNS zones for bind9 (although the remaining configuration is still performed using the customary config files).

First, install bind9-dyndb-ldap (bind9 will be pulled in as a dependency if necessary):

# apt install bind9-dyndb-ldap

Next, the bind schema needs to be installed from /usr/share/doc/bind9-dyndb-ldap/schema.ldif.gz. Sadly, it requires some manual editing since it is formatted for the 389 Directory Server. The lines dn:cn=dns,cn=schema,cn=config and objectClass: olcSchemaConfig need to be added at the top, the existing dn needs to be commented out, attributeTypes should be olcAttributeTypes, objectClasses should be olcObjectClasses and attributes already defined in the cosine schema should be commented out. Something like this:

# zcat /usr/share/doc/bind9-dyndb-ldap/schema.ldif.gz | sed 's/^attributeTypes:/olcAttributeTypes:/;
 s/^objectClasses:/olcObjectClasses:/;
 1,/1.3.6.1.4.1.2428.20.0.0/ {/1.3.6.1.4.1.2428.20.0.0/!s/^/#/};
 1idn: cn=dns,cn=schema,cn=config\nobjectClass: olcSchemaConfig
' >> /tmp/dns.schema

Then load the dns schema:

# ldapadd -Q -Y EXTERNAL -H ldapi:/// -f /tmp/dns.schema
adding new entry "cn=dns,cn=schema,cn=config"

Load and enable the syncrepl module (the protocol is used for communication between slapd and bind9, so it needs to be enabled even if you do not plan to do any replicated setup with multiple slapd servers):

# ldapmodify -Q -Y EXTERNAL -H ldapi:/// <<EOF
dn: cn=module{0},cn=config
changetype: modify
add: olcModuleLoad
olcModuleLoad: syncprov
EOF
Modifying entry "cn=module{0},cn=config"

# ldapmodify -Q -Y EXTERNAL -H ldapi:/// <<EOF
dn: olcOverlay=syncprov,olcDatabase={1}mdb,cn=config
changeType: add
objectClass: olcOverlayConfig
objectClass: olcSyncProvConfig
olcOverlay: syncprov
olcSpCheckpoint: 100 10
olcSpSessionLog: 100
EOF
adding new entry "olcOverlay=syncprov,olcDatabase={1}mdb,cn=config"

Now you are ready to start creating DNS zones. Here's an example (based on /usr/share/doc/bind9-dyndb-ldap/example.ldif):

# ldapadd -x -D cn=admin,dc=example,dc=com -W <<EOF
dn: ou=Services,dc=example,dc=com
objectClass: organizationalUnit
objectClass: top
ou: Services

# Top dns container
dn: ou=dns,ou=Services,dc=example,dc=com
objectClass: organizationalUnit
objectClass: top
ou: dns

# Zone example.com
dn: idnsName=example.com,ou=dns,ou=Services,dc=example,dc=com
objectClass: top
objectClass: idnsZone
objectClass: idnsRecord
idnsName: example.com
idnsUpdatePolicy: grant EXAMPLE.COM krb5-self * A;
idnsZoneActive: TRUE
idnsSOAmName: server.example.com
idnsSOArName: root.server.example.com
idnsSOAserial: 1
idnsSOArefresh: 10800
idnsSOAretry: 900
idnsSOAexpire: 604800
idnsSOAminimum: 86400
NSRecord: example.com.
ARecord: 192.168.1.1

# DNS records for zone example.com
dn: idnsName=server,idnsName=example.com,ou=dns,ou=Services,dc=example,dc=com
objectClass: idnsRecord
objectClass: top
idnsName: server
CNAMERecord: example.com.

dn: idnsName=foo,idnsName=example.com,ou=dns,ou=Services,dc=example,dc=com
objectClass: idnsRecord
objectClass: top
idnsName: foo
ARecord: 192.168.1.50
ARecord: 192.168.1.51
ARecord: 192.168.1.52

dn: idnsName=bar,idnsName=example.com,ou=dns,ou=Services,dc=example,dc=com
objectClass: idnsRecord
objectClass: top
idnsName: bar
ARecord: 192.168.1.100

dn: idnsName=baz,idnsName=example.com,ou=dns,ou=Services,dc=example,dc=com
objectClass: idnsRecord
objectClass: top
idnsName: baz
CNAMERecord: bar

dn: idnsName=_ldap._tcp,idnsName=example.com,ou=dns,ou=Services,dc=example,dc=com
objectClass: idnsRecord
objectClass: top
idnsName: _ldap._tcp
SRVRecord: 0 100 389 server

dn: idnsName=_ntp._udp,idnsName=example.com,ou=dns,ou=Services,dc=example,dc=com
objectClass: idnsRecord
objectClass: top
idnsName: _ntp._udp
SRVRecord: 0 100 123 server
EOF

adding new entry "ou=Services,dc=example,dc=com"

adding new entry "ou=dns,ou=Services,dc=example,dc=com"

adding new entry "idnsName=example.com,ou=dns,ou=Services,dc=example,dc=com"

adding new entry "idnsName=server,idnsName=example.com,ou=dns,ou=Services,dc=example,dc=com"

adding new entry "idnsName=foo,idnsName=example.com,ou=dns,ou=Services,dc=example,dc=com"

adding new entry "idnsName=bar,idnsName=example.com,ou=dns,ou=Services,dc=example,dc=com"

adding new entry "idnsName=baz,idnsName=example.com,ou=dns,ou=Services,dc=example,dc=com"

adding new entry "idnsName=_ldap._tcp,idnsName=example.com,ou=dns,ou=Services,dc=example,dc=com"

adding new entry "idnsName=_ntp._udp,idnsName=example.com,ou=dns,ou=Services,dc=example,dc=com"

Finally, configure bind9 to use this zone by editing /etc/bind/named.conf.local and adding a section like this:

dyndb "my_db_name" "/usr/lib/bind/ldap.so" {
        uri "ldapi:///";
        base "ou=dns,ou=Services,dc=example,dc=com";
        auth_method "simple";
        bind_dn "uid=admin,dc=example,dc=com";
        password "SECRET";
        server_id "server";
};

Start bind9:

# systemctl start bind9

And run a quick test:

# apt install dnsutils
[...]
# dig example.com. @localhost +short
192.168.1.1

DHCP

The ISC DHCP server has support for using a LDAP server as a backend, provided that you install the isc-dhcp-server-ldap package:

# apt install isc-dhcp-server-ldap schema2ldif

Then, convert and load the dhcp schema:

# zcat /usr/share/doc/isc-dhcp-server-ldap/dhcp.schema.gz > /tmp/dhcp.schema && schema2ldif /tmp/dhcp.schema | ldapadd -Q -Y EXTERNAL -H ldapi:///
adding new entry "cn=dhcp,cn=schema,cn=config"

Add some indexes:

# ldapmodify -Y EXTERNAL -H ldapi:/// <<EOF
dn: olcDatabase={1}mdb,cn=config
changetype: modify
add: olcDbIndex
olcDbIndex: dhcpHWAddress eq
-
add: olcDbIndex
olcDbIndex: dhcpClassData eq
EOF

And add a LDAP subtree to hold the dhcp configuration (you need to omit the ou=Services,dc=example,dc=com entry if it is already present):

# ldapadd -x -D cn=admin,dc=example,dc=com -W <<EOF
dn: ou=Services,dc=example,dc=com
objectClass: organizationalUnit
objectClass: top
ou: Services

dn: ou=dhcp,ou=Services,dc=example,dc=com
objectClass: organizationalUnit
objectClass: top
ou: dhcp
EOF
Enter LDAP Password: SECRET

adding new entry "ou=Services,dc=example,dc=com"

adding new entry "ou=dhcp,ou=Services,dc=example,dc=com"

Next, add top-level configuration and server entries (assuming the FQDN of the system is server.example.com, note that dhcpServiceDN and dhcpPrimaryDN need to point to the other entry):

# ldapadd -x -D cn=admin,dc=example,dc=com -W <<EOF
dn: cn=server.example.com,ou=dhcp,ou=Services,dc=example,dc=com
objectClass: top
objectClass: dhcpServer
cn: server.example.com
dhcpServiceDN: cn=config,ou=dhcp,ou=Services,dc=example,dc=com

dn: cn=config,ou=dhcp,ou=Services,dc=example,dc=com
cn: config
objectClass: top
objectClass: dhcpService
dhcpPrimaryDN: cn=server.example.com,ou=dhcp,ou=Services,dc=example,dc=com
dhcpStatements: ddns-update-style none
dhcpStatements: default-lease-time 600
dhcpStatements: max-lease-time 7200
EOF
Enter LDAP Password: SECRET

adding new entry "cn=server.example.com,ou=dhcp,ou=Services,dc=example,dc=com"

adding new entry "cn=config,ou=dhcp,ou=Services,dc=example,dc=com"

Now you can create one or more subnets (for a more complex example, see zless /usr/share/doc/isc-dhcp-server-ldap/README.ldap.gz):

# ldapadd -x -D cn=admin,dc=example,dc=com -W <<EOF
# First, a shared network segment (which can hold multiple subnet declarations)
dn: cn=internal,cn=config,ou=dhcp,ou=Services,dc=example,dc=com
cn: internal
objectClass: top
objectClass: dhcpSharedNetwork

# Second, a subnet declaration
dn: cn=192.168.1.0,cn=internal,cn=config,ou=dhcp,ou=Services,dc=example,dc=com
cn: 192.168.1.0
objectClass: top
objectClass: dhcpSubnet
objectClass: dhcpOptions
dhcpOption: domain-name-servers 192.168.1.1
dhcpOption: routers 192.168.1.1
dhcpOption: subnet-mask 255.255.255.0
dhcpOption: broadcast-address 192.168.1.255
dhcpNetMask: 24

# Third, a pool for the subnet
dn: cn=pool,cn=192.168.1.0,cn=internal,cn=config,ou=dhcp,ou=Services,dc=example,dc=com
cn: pool
objectClass: top
objectClass: dhcpPool
dhcpRange: 192.168.1.150 192.168.1.199
EOF
Enter LDAP Password: SECRET

adding new entry "cn=internal,cn=config,ou=dhcp,ou=Services,dc=example,dc=com"

adding new entry "cn=192.168.1.0,cn=internal,cn=config,ou=dhcp,ou=Services,dc=example,dc=com"

adding new entry "cn=pool,cn=192.168.1.0,cn=internal,cn=config,ou=dhcp,ou=Services,dc=example,dc=com"

Finally, instruct the dhcp server to use LDAP for the configuration by editing /etc/dhcp/dhcpd.conf to read:

ldap-server "localhost";
ldap-port 389;
ldap-base-dn "ou=dhcp,ou=Services,dc=example,dc=com";
ldap-dhcp-server-cn "server.example.com";
ldap-method dynamic;
ldap-debug-file "/var/log/dhcp-ldap-debug.log";

# The DHCP server needs read-only access, so an anonymous bind
# to the LDAP server is sufficient. If you want a non-anonymous
# bind (e.g. because of tight ACLs, use something like this)
#ldap-username "cn=admin,dc=example,dc=com";
#ldap-password "SECRET";

Note that the dhcp server startup scripts in Debian by default expect IPv4 and IPv6 to be configured, so either add a DHPCv6 configuration in your LDAP database and configure /etc/dhcp/dhcpd6.conf to use it (the preferred solution), or disable IPv6 by editing /etc/default/isc-dhcp-server and listing the interfaces which the dhcp server should be listening on in the INTERFACESv4 option while leaving INTERFACESv6 empty:

INTERFACESv4="eth0"
INTERFACESv6=""

Now start the server:

# systemctl start isc-dhcp-server

The configuration which isc-dhcp-server obtained via LDAP will be output to /var/log/dhcp-ldap-debug.log:

# cat /var/log/dhcp-ldap-debug.log 
ddns-update-style none;
default-lease-time 600;
max-lease-time 7200;
shared-network "internal" {
subnet 192.168.1.0 netmask 255.255.255.0 {
option domain-name-servers 192.168.1.1;
option routers 192.168.1.1;
option subnet-mask 255.255.255.0;
option broadcast-address 192.168.1.255;
pool {
range 192.168.1.150 192.168.1.199;
}
}
}

Once you're happy with your setup, you can comment out ldap-debug-file in /etc/dhcp/dhcpd.conf and restart isc-dhcp-server.

Backup

To backup your LDAP configuration and databases, you use the slapcat(8) command which outputs whole DITs in LDIF format and which can be executed while slapd is running:

# slapcat -n0 -l backup_config.ldif

The above command creates a backup of database 0 (which is the cn=config database) and writes it to backup_config.ldif. After the configuration has been backed up, you need to repeat the command for each of the data directories (in the default installation, you have one data directory, corresponding to e.g. dc=example,dc=com) by using the corresponding database number:

# slapcat -n1 -l backup_data.ldif

For a belt-and-braces approach, you might want to put each database into read-only mode before backing it up, e.g. by running:

# ldapmodify -Q -Y EXTERNAL -H ldapi:/// <<EOF
dn: olcDatabase={1}mdb,cn=config
changeType: modify
replace: olcReadOnly
olcReadOnly: TRUE
EOF
modifying entry "olcDatabase={1}mdb,cn=config"

After the backup is complete, you can disable the read-only mode as follows:

# ldapmodify -Q -Y EXTERNAL -H ldapi:/// <<EOF
dn: olcDatabase={1}mdb,cn=config
changeType: modify
replace: olcReadOnly
olcReadOnly: FALSE
EOF
modifying entry "olcDatabase={1}mdb,cn=config"

Note: do not put the configuration database (olcDatabase={0}config,cn=config) in read-only mode, since disabling read-only mode requires writing to the configuration database, you'll end up in a catch-22.

An example of a backup script which automates all of the above can be found here (courtesy of the DebOps project).

Restoration

To restore from a backup, first stop slapd:

# systemctl stop slapd

Then move any configuration directory out of the way:

# mv -i /etc/ldap/slapd.d /etc/ldap/slapd.d.old

Create new directories:

# mkdir /etc/ldap/slapd.d

Next, use slapadd(8) to restore the configuration directory (the parameters are the same as for slapcat, with the addition of -F which tells slapadd where the configuration should live):

# slapadd -n0 -l backup_config.ldif -F /etc/ldap/slapd.d

slapadd is blissfully unaware of file user/group ownership, so you need to correct them manually:

# chown -R openldap:openldap /etc/ldap/slapd.d

Finally, repeat the above steps for each data directory:

# mv -i /var/lib/ldap /var/lib/ldap.old
# mkdir /var/lib/ldap
# slapadd -n1 -l backup_data.ldif -F /etc/ldap/slapd.d
# chown -R openldap:openldap /var/lib/ldap

And start slapd again:

# systemctl start slapd

Note that if you are using any kind of replication, you need to add the -w flag to each slapadd invocation, e.g.:

# slapadd -n1 -l backup_data.ldif -F /etc/ldap/slapd.d -w

This adds additional information to the directory which allows other servers to determine that synchronization is necessary.

Replication

OpenLDAP supports several different types of replication (see the Administrator's Guide for a detailed explanation). This section will focus on one approach, called Multi-Provider.

In Multi-Provider mode, each server acts as a master/producer, meaning that any server can accept changes to the LDAP database and will propagate it to the other servers.

This means that any server can accept changes, even if one or more of the other servers are offline. However, it also means that there will be more traffic between the servers and that there is a risk of split-brain if changes are performed on different servers during a network split.

The create a Multi-Provider cluster, first install slapd (see initial installation above) on each server and edit /etc/default/slapd to list each server's own FQDN as part of the ldap:// URI. In other words, on e.g. serverA.example.com, the entry:

SLAPD_SERVICES="ldapi:// ldap://"

should read:

SLAPD_SERVICES="ldapi:// ldap://serverA.example.com

Next, configure TLS/SSL on each server.

Once TLS is up and running, restart slapd on each server in order for the changes to /etc/default/slapd and the TLS/SSL configuration to take effect:

# systemctl restart slapd

Make sure the syncprov module is loaded and enabled for the cn=config database on each server:

# ldapmodify -Q -Y EXTERNAL -H ldapi:/// <<EOF
dn: cn=module{0},cn=config
changetype: modify
add: olcModuleLoad
olcModuleLoad: syncprov

dn: olcOverlay=syncprov,olcDatabase={0}config,cn=config
changetype: add
objectClass: olcOverlayConfig
objectClass: olcSyncProvConfig
olcOverlay: syncprov
olcSpCheckpoint: 100 10
olcSpSessionLog: 100
EOF
modifying entry "cn=module{0},cn=config"

adding new entry "olcOverlay=syncprov,olcDatabase={0}config,cn=config"

Assign an ID to each server:

serverA# ldapmodify -Q -Y EXTERNAL -H ldapi:/// <<EOF
dn: cn=config
changetype: modify
add: olcServerID
olcServerID: 1
EOF
modifying entry "cn=config"

serverB# ldapmodify -Q -Y EXTERNAL -H ldapi:/// <<EOF
dn: cn=config
changetype: modify
add: olcServerID
olcServerID: 2
EOF
modifying entry "cn=config"

[...repeat for each server...]

Make sure that there is a user with read/write access to the cn=config database on each server (note: alternatively, this can be accomplished using ACLs and a separate replication user):

# slappasswd
New password: SECRET
Re-enter new password: SECRET
{SSHA}4D/nsBlxhRDzqBb028tynzJzbO+iNpnA
# ldapmodify -Q -Y EXTERNAL -H ldapi:/// <<EOF
dn: olcDatabase={0}config,cn=config
changetype: modify
replace: olcRootDN
olcRootDN: cn=admin,cn=config
-
replace: olcRootPW
olcRootPW: {SSHA}4D/nsBlxhRDzqBb028tynzJzbO+iNpnA
EOF
modifying entry "olcDatabase={0}config,cn=config"

Note: run slappasswd on each server to generate unique hashes even if you use the same password for all servers.

Finally, configure each server with knowledge about the other servers and enable syncing:

# ldapmodify -Q -Y EXTERNAL -H ldapi:/// <<EOF
dn: cn=config
changetype: modify
replace: olcServerID
olcServerID: 1 ldap://serverA.example.com/
olcServerID: 2 ldap://serverB.example.com/
[...repeat for any further servers...]

dn: olcDatabase={0}config,cn=config
changetype: modify
add: olcSyncRepl
olcSyncRepl:
    rid=001
    provider=ldap://serverA.example.com/
    bindmethod=simple
    binddn="cn=admin,cn=config" 
    credentials=SECRET
    searchbase="cn=config"
    type=refreshAndPersist
    retry="5 5 300 5"
    timeout=1
    starttls=critical
    tls_reqcert=demand
olcSyncRepl:
    rid=002
    provider=ldap://serverB.example.com/
    bindmethod=simple
    binddn="cn=admin,cn=config" 
    credentials=SECRET
    searchbase="cn=config"
    type=refreshAndPersist
    retry="5 5 300 5"
    timeout=1
    starttls=critical
    tls_reqcert=demand
[...repeat for any further servers...]
-
add: olcMirrorMode
olcMirrorMode: TRUE
EOF
modifying entry "cn=config"

modifying entry "olcDatabase={0}config,cn=config"

Note: olcSyncRepl and olcMirrorMode need to be set in the same transaction, so do not split the above modifications into several commands.

Test the replication of the cn=config database:

serverA# ldapsearch -LLLQ -Y EXTERNAL -H ldapi:/// -b olcDatabase={-1}frontend,cn=config olcSizeLimit
dn: olcDatabase={-1}frontend,cn=config
olcSizeLimit: 500

serverB# ldapsearch -LLLQ -Y EXTERNAL -H ldapi:/// -b olcDatabase={-1}frontend,cn=config olcSizeLimit
dn: olcDatabase={-1}frontend,cn=config
olcSizeLimit: 500

serverA# ldapmodify -Y EXTERNAL -H ldapi:/// <<EOF
dn: olcDatabase={-1}frontend,cn=config
changetype: modify
replace: olcSizeLimit
olcSizeLimit: 501
EOF
modifying entry "olcDatabase={-1}frontend,cn=config"

serverA# ldapsearch -LLLQ -Y EXTERNAL -H ldapi:/// -b olcDatabase={-1}frontend,cn=config olcSizeLimit
dn: olcDatabase={-1}frontend,cn=config
olcSizeLimit: 501

serverB# ldapsearch -LLLQ -Y EXTERNAL -H ldapi:/// -b olcDatabase={-1}frontend,cn=config olcSizeLimit
dn: olcDatabase={-1}frontend,cn=config
olcSizeLimit: 501

serverA# # ldapmodify -Y EXTERNAL -H ldapi:/// <<EOF
dn: olcDatabase={-1}frontend,cn=config
changetype: modify
replace: olcSizeLimit
olcSizeLimit: 500
modifying entry "olcDatabase={-1}frontend,cn=config"
EOF

As can be seen from the above example, the change to olcSizeLimit on serverA was automatically propagated to serverB.

It is now time to configure the replication of the other databases (e.g. dc=example,dc=com). Since the cn=config database is now replicated across servers, the remaining steps only have to be performed on one of the servers.

First, setup indexing for two attributes which are heavily used in the replication:

# ldapmodify -Q -Y EXTERNAL -H ldapi:/// <<EOF
dn: olcDatabase={1}mdb,cn=config
changetype:  modify
add: olcDbIndex
olcDbIndex: entryCSN eq
olcDbIndex: entryUUID eq
EOF
modifying entry "olcDatabase={1}mdb,cn=config"

Second, add the syncprov overlay to the database:

# ldapmodify -Q -Y EXTERNAL -H ldapi:/// <<EOF
dn: olcOverlay=syncprov,olcDatabase={1}mdb,cn=config
changetype: add
objectClass: olcOverlayConfig
objectClass: olcSyncProvConfig
olcOverlay: syncprov
EOF
adding new entry "olcOverlay=syncprov,olcDatabase={1}mdb,cn=config"

Third, enable replication:

Attribute name change

Starting with slapd version 2.5 (i.e. Bookworm and later), the attribute olcMirrorMode was renamed to olcMultiProvider. The example below uses the latter name.

# ldapmodify -Q -Y EXTERNAL -H ldapi:/// <<EOF
dn: olcDatabase={1}mdb,cn=config
changetype: modify
add: olcSyncRepl
olcSyncRepl:
    rid=011
    provider=ldap://serverA.example.com/
    bindmethod=simple
    binddn="cn=admin,dc=example,dc=com"
    credentials=SECRET
    searchbase="dc=example,dc=com"
    type=refreshAndPersist
    retry="5 5 300 5"
    timeout=1
    starttls=critical
    tls_reqcert=demand
olcSyncRepl:
    rid=012
    provider=ldap://serverB.example.com/
    bindmethod=simple
    binddn="cn=admin,dc=example,dc=com"
    credentials=SECRET
    searchbase="dc=example,dc=com"
    type=refreshAndPersist
    retry="5 5 300 5"
    timeout=1
    starttls=critical
    tls_reqcert=demand
[...repeat for any further servers...]
-
add: olcMultiProvider
olcMultiProvider: TRUE
EOF
modifying entry "olcDatabase={1}mdb,cn=config"

Note the changes to the dn, rid, binddn and searchbase compared to when replication was enabled for the cn=config database.

The changes to the configuration will be replicated to the other servers which will then start replicating the dc=example,dc=com database.

The above three steps need to be repeated for each additional database that you have configured (a default installation has no further databases).

Further Reading


CategorySystemAdministration | CategorySoftware | ToDo: merge information from LDAP/Kerberos