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.
Contents
Initial Installation
Install the following packages:
slapd - the OpenLDAP server
ldap-utils - tools for interacting with, querying and modifying entries in local or remote LDAP servers
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
the cn=config root entry contains global settings
cn=module{0},cn=config stores a list of dynamically loaded modules
cn=schema,cn=config contains the built-in system schema definitions with user schema as child entries
the olcDatabase={-1}frontend,cn=config entry contains further global settings (but ones which might affect or be affected by dynamically loadable modules)
the remaining olcDatabase={id}type,cn=config entries contain per-database settings.
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
In Buster and earlier releases, the password configured during installation is additionally saved as a userPassword for the administrator's account in the directory. In newer releases, this account is missing and only olcRootDN and olcRootPW is set at olcDatabase={1}mdb,cn=config
To change the password, both values must be updated. If only one is changed, the old password can still be used. This is fixed in newer releases; 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"
If needed, 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.
Note: If you plan to use replication on the configuration database (cn=config), care needs to be taken regarding the certificate/key files - see Replication for more information.
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 new CA key, and a self-signed certificate into the current working directory:
# 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
update TLSCertificateKeyFile and TLSCertificateFile options with your key/certificate locations in /etc/ldap/slapd.conf
comment out TLSCACertificateFile and change TLSVerifyClient to never.
restart slapd systemctl restart slapd
Since this certificate is self-signed, certificate verification must be disabled on LDAP clients:
In /etc/ldap/ldap.conf, comment out TLS_CACERT and change TLS_REQCERT to never.
configure your client to use the ldaps:// URL.
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=krbContainer,ou=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:
install krb5-kdc-ldap (not the admin server)
copy over /etc/krb5.conf, /etc/krb5kdc/service.keyfile and /etc/krb5kdc/stash files from the primary KDC
(optionally) create a kerberos principal and keytab on the secondary KDC and restart slapd
start the secondary KDC (systemctl start krb5-kdc)
If you want to extend the krbtgt or be able to renew it, your attempt to manipulate LDAP using kadmin.local will be met with permission denied when attempting to change the krbtgt/EXAMPLE.COM@EXAMPLE.COM principal. If you create a krbtgt-ticket-life.ldif containing the following:
dn: krbPrincipalName=krbtgt/EXAMPLE.COM@EXAMPLE.COM,cn=EXAMPLE.COM,cn=kerberos,ou=Services,dc=example,dc=com changetype: modify replace: krbMaxTicketLife krbMaxTicketLife: 432000 dn: krbPrincipalName=krbtgt/EXAMPLE.COM@EXAMPLE.COM,cn=EXAMPLE.COM,cn=kerberos,ou=Services,dc=example,dc=com changetype: modify replace: krbMaxRenewableAge krbMaxRenewableAge: 431999 dn: krbPrincipalName=krbtgt/EXAMPLE.COM@EXAMPLE.COM,cn=EXAMPLE.COM,cn=krbContainer,ou=kerberos,ou=Services,dc=example,dc=com changetype: modify replace: krbMaxTicketLife krbMaxTicketLife: 432000 dn: krbPrincipalName=krbtgt/EXAMPLE.COM@EXAMPLE.COM,cn=EXAMPLE.COM,cn=krbContainer,ou=kerberos,ou=Services,dc=example,dc=com changetype: modify replace: krbMaxRenewableAge krbMaxRenewableAge: 431999
and then apply it to the LDAP records with
# ldapmodify -H ldapi:// -D "cn=admin,dc=example,dc=com" -W -f krbtgt-ticket-life.ldif Enter LDAP Password: modifying entry "krbPrincipalName=krbtgt/EXAMPLE.COM@EXAMPLE.COM,cn=EXAMPLE.COM,cn=kerberos,ou=Services,dc=example,dc=com" modifying entry "krbPrincipalName=krbtgt/EXAMPLE.COM@EXAMPLE.COM,cn=EXAMPLE.COM,cn=kerberos,ou=Services,dc=example,dc=com" modifying entry "krbPrincipalName=krbtgt/EXAMPLE.COM@EXAMPLE.COM,cn=EXAMPLE.COM,cn=krbContainer,ou=kerberos,ou=Services,dc=example,dc=com" modifying entry "krbPrincipalName=krbtgt/EXAMPLE.COM@EXAMPLE.COM,cn=EXAMPLE.COM,cn=krbContainer,ou=kerberos,ou=Services,dc=example,dc=com" #
you can now enjoy a krbtgt with a life up to 5 days and it is renewable for 4 days, 23 hours, 59 minutes and 59 seconds. You will also need to modify /etc/krb5kdc/kdc.conf and update the [realms] section to include your new max_life and max_renewable_life. Then you need to update /etc/krb5.conf and the [libdefaults] section to state a ticket_lifetime and a renew_lifetime. Whichever number is the smallest in these locations will win out and cap your krbtgt lifetime.
Also discovered that you can do this with kdb5_ldap_util:
$ sudo kdb5_ldap_util -D "cn=admin,dc=example,dc=com" -r EXAMPLE.COM -H ldapi:// modify -maxtktlife '5 days' -maxrenewlife '4 days 12 hours' Password for "cn=admin,dc=example,dc=com": $
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.
Note: Replication of the configuration database will, by definition, force all machines to use identical configurations, including the olcTLS* options. If your certificate / key files do not have identical pathnames on each machine, enabling replication will break TLS on any machines where this is the case. An alternative workaround to renaming your certificates / keys is to add exattrs=olcTLSCertificateFile,olcTLSCertificateKeyFile,olcTLSCACertificateFile to the syncrepl lines used below, to exclude these settings from replication.
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 EOF modifying entry "olcDatabase={-1}frontend,cn=config"
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
/usr/share/doc/slapd/README.Debian.gz (Debian-specific setup and configuration)
man pages: slapd-config.5, slapcat.1, ldapsearch.1, ldapmodify.1, and ldif.1, slapd.1, slapd.8, slapd.access.5, slapd.backends.5, slapd.overlays.5.
The DebOps project's Ansible role for OpenLDAP
RFC 4510 - Lightweight Directory Access Protocol (LDAP)
Pkg-openldap-devel Debian mailing list
CategorySystemAdministration | CategorySoftware | ToDo: merge information from LDAP/Kerberos