Reinstalling Openldap in CZ.NIC

At the beginning of the year, a need arose to reinstall the LDAP, which had been running in CZ.NIC on an older OS, and I figured I did not want to miss such an interesting “challenge”. And I was very excited about it until I realized that the same server was, as a “bonus”, probably, running Freeradius and Radsecproxy with a connection to Eduroam. Of course, this was also to be rewritten as the syntax between the versions Freeradius v2 and v3 had changed a little bit. Only then I understood and admired the patience of people who set these things in CZ.NIC before me and I know that they have “enjoyed” this quite a lot. But this is a topic for another day, as this blog post should be about installing LDAP. I emphasize that this article discusses only basic LDAP settings.

LDAP or “Lightweight Directory Access Protocol” is a directory protocol optimized for fast data access. With the simplicity of the protocol and the use of directory (tree) structure and database, you can quickly search for information about users and services. You can also input data into the database, modify and delete it. LDAP allows centralized maintenance of user data. This enables all LDAP-enabled applications to access the same data. In Linux, for example, we can successfully synchronize user accounts across systems.

Each record contains a “distinguished name” or DN, which makes the record unique. Using DN, the tree path to the record is also described. Under DN, objects and attributes that make up individual records are grouped together. An object example is objectClass that defines the object type. Objects are already predefined by schemas, and it is also possible to define your own. An example of an attribute may be the “DC” attribute, which can be used to describe for example the domain “dc=nic,dc=cz” or the “O” attribute describing an organizational unit.

# Entry 1: dc=nic,dc=cz
dn: dc=nic,dc=cz
dc: nic
o: CZ.NIC, z.s.p.o
objectclass: top
objectclass: dcObject
objectclass: organization

The data can be distributed as text in the form of .ldif, binary data in .ldif (for example, different language settings or other binary blobs) is saved using encoding, most commonly base64.


By installing the Openldap package (slapd package in Debian), LDAP will be set up automatically after installation. To make the configuration a bit more complicated, since the Openldap 2.4.23-3 version, LDAP has not been set up using a text file (previously there was one configuration file called “slapd.conf”), but using the ldapadd, ldapmodify, and .ldif commands input directly into the database.

In the case of the ldapadd and ldapmodify commands, we can work with the EXTERNAL parameter. We use this parameter when we “play” with the configuration, that is, the DB LDAP setting. Or we can work with Administrator privileges (or other permissions depending on ACL). We use them especially when modifying user data. If you want to use the administrator account to change the values in the LDAP configuration itself, it can also be set.

Before we go into configuration, we should explain how to work with LDAP. After installing LDAP, you will definitely be interested in its current settings. For “sniffing out” the configuration, I use ldapsearch from the “ldap-utils” package. With its help, it is easy to uncover the topmost tree structure of the settings to see all that’s in the database. The command for ldapsearch to list the DN configuration is:

# ldapsearch -Q -LLL -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: olcBackend={0}mdb,cn=config
dn: olcDatabase={-1}frontend,cn=config
dn: olcDatabase={0}config,cn=config
dn: olcDatabase={1}mdb,cn=config

As you can see, four schemas are used in the LDAP configuration after installation. If you are interested in what modules LDAP is currently using, there is nothing easier than to “dive into” the configuration, again using ldapsearch. In a similar way, we can discuss and examine other DNs from the previous tree structure to the smallest detail. For example, we will find that no expansion modules were used in the default LDAP installation except the DB backend (which, as you can see, is also apparently a module, though not an expansion module but a database one).

# ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b cn=module{0},cn=config

dn: cn=module{0},cn=config
objectClass: olcModuleList
cn: module{0}
olcModulePath: /usr/lib/ldap
olcModuleLoad: {0}back_mdb

Using ldapsearch, we do not have to list only parts of configuration, but of course you can list it all. But then you’re facing a problem that you come across schemas that are somewhat unclear. I think examining LDAP schemas that someone else has defined is not what you want, and you do not actually have to do it.

A similar effect of configuration listing can also be achieved by the ‘slapcat’ tool, except that ‘slapcat’ actually lists everything, including data on record modifying and creating. That’s why we will make better use the of the ‘slapcat’ command for backups instead of the configuration listing (see below).

ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b cn=config
slapcat -n 0


Before we start working with LDAP, it’s a good idea to back up the initial configuration. Of course, the backup will be useful if you botch something up in your configuration during your testing (for example, by a malfunctioning .ldif file with bad values when setting up replication – then you really need to be careful). Or when someone else with enabled acl permissions has tinkered with your configuration. Then you will surely want to use audit logs, see LDAP modules, and if possible with everything replicated in some other place where you can find everything you need.

With the initial functional backup of the configuration, a “broken” LDAP can be repaired relatively quickly. As for backups, we should definitely back up daily. You can, of course, return to the various LDAP settings backups later or build a fast script for that.

Configuration backup

I recommend setting up cron configuration backup. We should definitely back up the settings of the database itself and the user data. Slapcat with “-n 0” switch applies for configuration listing, “-n 1 .. n” for DB user data listings. But I assume that using LDAP for multiple domains will not be so common. Backup is a non-invasive thing that we do not have to fear because it consists of simple database listing and saving into a file.

0 7 * * * root slapcat -n 0 -l /path/config_ldap_back_$(date -I)_.ldif
0 7 * * * root slapcat -n 1 -l /path/db_ldap_back_$(date -I)_.ldif

Restoring from backup

We have backed up but we still need to know how to restore the backup. Suppose we have two files, one with Database configuration and one with user data. In the beginning, we restart the database config itself, and this is done with the LDAP command slapadd, which does the same thing as when we saved the backup, just in the opposite way. Proceed as follows:

service slapd stop
mv /etc/ldap/slapd.d /etc/ldap/slapd.d-`date -I`
mkdir -p /etc/ldap/slapd.d; chown -R openldap:openldap /etc/ldap/slapd.d/
slapadd -n 0 -F /etc/ldap/slapd.d -l /path/config_ldap_back_$(date -I)_.ldif
chown -R openldap:openldap /etc/ldap/
service slapd start

You restore the user data in the Database like this:

mv /var/lib/ldap /var/lib/ldap-`date -I`
mkdir -p /var/lib/ldap; chown -R openldap:openldap /var/lib/ldap/
# pokud mame povolenou replikaci, pridame jeste "-w"
slapadd -n 1 -F /etc/ldap/slapd.d -l /path/db_ldap_back_$(date -I)_.ldif -w
chown -R openldap:openldap /var/lib/ldap/

Adding ldifs

Administrator password

Sometimes it may happen that we already come to a ready-made server with Openldap that has been configured by someone else. It is best then to know how to change the administrator password. In the first step, we generate a password hash, for example, using slappasswd (from the ldap-utils package). It’s best to use a slightly stronger password than I used in the example.

# slappasswd -h "{SSHA}"
New password:
Re-enter new password:

We put the password hash into LDAP using this ldif:

# ldapmodify -H ldapi:// -Y EXTERNAL -f passwd.ldif

dn: olcDatabase={1}mdb,cn=config
changetype: modify
replace: olcRootPW
olcRootPW: {SSHA}UVT5FQnKmCoq2X0qXWa7/dw3r+MPHQng

If the password seems correctly input, we can verify this with ldapsearch (the password hash should match):

# ldapsearch -H ldapi:// -LLL -Q -Y EXTERNAL -b "cn=config" "(olcRootPW=*)"

olcRootDN: cn=admin,dc=nic,dc=cz
olcRootPW: {SSHA}UVT5FQnKmCoq2X0qXWa7/dw3r+MPHQng

This is how we changed the LDAP password in the “cn = config” section. Now we need to do the same for the admin account in DB user data. If we did not do this, the old password could still be used; we will use the following .ldif:

# ldapadd -c -h localhost -Z -f passwd.ldif -x -D "cn=admin,dc=nic,dc=cz" -w "silne_admin_heslo"

dn: cn=admin,dc=nic,dc=cz
changetype: modify
replace: userPassword
userPassword: {SSHA}UVT5FQnKmCoq2X0qXWa7/dw3r+MPHQng


If you have your LDAP running, you may be interested in how to secure your communication. You do not want the data to be “wandering” over the network just in plain text, but you would want them to be encrypted. In addition, certificates guarantee identification, so in case of both server and client you should be sure that you do not communicate with anyone passing as someone else.

I recommend that you choose stronger encryption, but not as strong that it “kills” your entire CPU, because a lot of LDAP queries may come in a relatively short time. I added certificates to the LDAP configuration using the following .ldif, but you can also use other settings:

# ldapadd -c -Y EXTERNAL -H ldapi:// -f cznic_config_certificate.ldif

dn: cn=config
add: olcTLSCACertificateFile
olcTLSCACertificateFile: /etc/ldap/ssl/CZ.NIC2-cacert_sha2.pem
add: olcTLSCertificateFile
olcTLSCertificateFile: /etc/ldap/ssl/
add: olcTLSCertificateKeyFile
olcTLSCertificateKeyFile: /etc/ldap/ssl/
add: olcTLSDHParamFile
olcTLSDHParamFile: /etc/ldap/ssl/dhparam

dn: cn=config
changetype: modify
replace: olcLocalSSF
olcLocalSSF: 128
replace: olcSecurity
olcSecurity: update_ssf=128 simple_bind=128

In Debian, do not forget to enable listening at port 636 in /etc/default/slapd for ldaps:

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


I cannot unfortunately disclose here the precise ACL settings as we use it in CZ.NIC. However, there are loads and loads of combinations of ACL settings you can make. If you need details, I recommend the documentation. The right directives typically begin with the olcAccess attribute, followed by the ACL rule sequence, the chain which is accessed, and eventually who accesses it (there may be more than one accessing party and in that case they are separated by spaces). Do not be afraid to be restrictive, as it is necessary in ACL to avoid any problems in the future. As far as users are concerned, you can do the following:

  • External user – if you delete your rights by accident

  • Admin user – user under which you will input data

  • Reader user – you will set this user in apps you only need to read (so usually almost all of them)

  • Others (self, auth, *) – you may for example allow users to change their password

I took the liberty to think of at least one example of right settings so you don’t miss this topic; see the following .ldif:

# ldapmodify -c -h localhost -Z -f acl.ldif -x -D "cn=admin,dc=nic,dc=cz" -w "silne_admin_heslo"

dn: olcDatabase={1}mdb,cn=config
changetype: modify
replace: olcAccess
olcAccess: {0}to attrs=userPassword by dn.exact="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" manage by dn="cn=admin,dc=nic,dc=cz" manage by dn="cn=reader,dc=nic,dc=cz" read by self write by anonymous auth by * none
olcAccess: {1}to dn.subtree="dc=nic,dc=cz" by dn.exact="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" manage by dn="cn=admin,dc=nic,dc=cz" manage by dn="cn=reader,dc=nic,dc=cz" read by * none
olcAccess: {2}to * by * none

With these ACLs, you should be able to list users from the People group (you can also ask with a privileged EXTERNAL account):

ldapsearch -Z -h localhost -x -D "cn=admin,dc=nic,dc=cz" -w "silne_heslo_admina" -b ou=People,dc=nic,dc=cz 'uid=zsobotka'
# ldapsearch -H ldapi:// -LLL -Q -Y EXTERNAL -b "dc=nic,dc=cz" 'uid=zsobotka'

To ask LDAP about groups:

ldapsearch -Z -h localhost -x -D "cn=reader,dc=nic,dc=cz" -w "silne_heslo_uzivatele" -b "dc=nic,dc=cz" '(objectClass=posixGroup)'
# ldapsearch -H ldapi:// -LLL -Q -Y EXTERNAL -b "dc=nic,dc=cz" '(objectClass=posixGroup)'

Or to be able to change your own password:

ldappasswd -Z -h  -x -D "uid=zsobotka,ou=People,dc=nic,dc=cz" -w "puvodni_heslo_uzivatele" -a "puvodni_heslo_uzivatele" -s "nove_heslo_uzivatele"


We should not forget about logging settings. The olcLogLevel number is the sum of the relevant “codes” that can be debugged. As a reasonable value for the “chattiness” of logs, I would set this number at 256. If you want to debug, you can dare to set it higher. Here is the .ldif:

# ldapmodify -h localhost -Z -f ./logmodify.ldif -x -D "cn=admin,dc=nic,dc=cz" -w "silne_heslo"

dn: cn=config
changetype: modify
delete: olcLogLevel

dn: cn=config
changetype: modify
add: olcLogLevel
olcLogLevel: 256


When migrating to MDB, you should not have a speed problem. Compared to the HDB backend, the speed has increased multiple times and, among other things, it now consumes half the system resources. But if your LDAP is still “running out of breath”, you can try to “tune it up” with these DB parameters.

Writemap – programmable memory is used instead of reading memory, which increases the writing speed. However, the database will thus become potentially vulnerable to “data corruption” in the event of an unforeseen server crash.

Nometasync – skipping metadata synchronization. This mode is faster than full synchronization, but if the operating system fails, the last transaction may be lost.

# ldapmodify -c -Y EXTERNAL -H ldapi:// -f speed.ldf

dn: olcDatabase={1}mdb,cn=config
changetype: modify
add: olcDbEnvFlags
olcDbEnvFlags: writemap
add: olcDbEnvFlags
olcDbEnvFlags: nometasync



I’ve already mentioned the benefits of the audit.log and I certainly recommend everyone to turn it on. Audit.log automatically tracks the changes that are made to LDAP and stores them in a .ldif form into a specified file. The log shows the timestamp of the event change, the user name, and the IP address from which the change came. If a problem occurs, you have the opportunity to “crack down” on it with the audit.log; you do not have to worry about missing something. In addition, you can replicate the log file using rsyslog to the servers where you are accustomed to store your logs.

# ldapadd -c -Y EXTERNAL -H ldapi:// -f auditlog.ldif

dn: cn=module{0},cn=config
changetype: modify
add: olcModuleLoad

dn: olcOverlay=auditlog,olcDatabase={1}mdb,cn=config
objectClass: olcOverlayConfig
objectClass: olcAuditlogConfig
olcOverlay: auditlog
# chmod 755 /var/log/slapd/; chown openldap:openldap /var/log/slapd/
olcAuditlogFile: /var/log/slapd/auditlog.log


Replication allows you to synchronize database with user data for “special circumstances”. When a server crashes, it quickly “switches” traffic to another server. Each machine is set to the same rid, different provider (IP of the opposite server) and different olcServerID. If you have set up encryption in your LDAP, you should turn it on for replication, in .ldif for example via starttls. Further details about replication can be found in the logs and documentation.

# ldapmodify -c -Y EXTERNAL -H ldapi:// -f cznic_config_replication.ldif

dn: olcDatabase={1}mdb,cn=config
changetype: modify
#add: olcSyncrepl
replace: olcSyncrepl
olcSyncrepl: rid=001 provider=ldap:// bindmethod=simple binddn="cn=admin,dc=nic,dc=cz" credentials="silne_heslo_admina" starttls=yes searchbase="dc=nic,dc=cz" schemachecking=on type=refreshAndPersist retry="30 +" network-timeout=5 timeout=30
#add: olcMirrorMode
replace: olcMirrorMode
olcMirrorMode: TRUE
#add: olcDbIndex
replace: olcDbIndex
olcDbIndex: entryUUID eq
olcDbIndex: entryCSN eq

dn: cn=config
changeType: modify
add: olcServerID
#Make sure you use different olcServerID's for different servers, in example 0, 1
olcServerID: 0

dn: cn=module{0},cn=config
changetype: modify
add: olcModuleLoad
olcModuleLoad: syncprov

On both servers, the user under which replication will be run should be set up. The admin user is used in the example, but a different user may be used as well.

Password policy

Password policy (ppolicy) is a module controlling the “strength” of user passwords. The module enforces a password policy by not allowing passwords shorter than a predefined number of characters, letters, and numbers. Using the module, we can ensure that the user does not always rotate the same passwords. The module saves modified password hashes in its history in the user DB.

We can find more of these modules; I have used the PqChecker module in LDAP. The home page of the project is If you’re brave, you can try compiling it. But it is better to just download the install package. It will unpack the “” module directly into the path. The ppolicy behavior values are set in the user DB, for example with the following .ldif.

Module introduction settings:

# ldapmodify -c -Y EXTERNAL -H ldapi:// -f "pwd_policy01.ldif"

dn: cn=module{0},cn=config
add: olcModuleLoad

Setting the configuration of the module (you must load the relevant schema from /etc/ldap/schema/ppolicy.ldif beforehand):

# ldapadd -c -Y EXTERNAL -H ldapi:// -f "pwd_policy02.ldif"

# ppolicy jeste nastavit v mdb, req. - melo by byt zavedeno schema -> /etc/ldap/schema/ppolicy.ldif
dn: olcOverlay=ppolicy,olcDatabase={1}mdb,cn=config
objectClass: olcPPolicyConfig
olcPPolicyDefault: cn=default,ou=policies,dc=nic,dc=cz

Parameter settings for this module (already in the database). For example, if you have PhpLdapAdmin installed, you can change ppolicy parameters directly there.

# ldapadd -Z -h localhost -f pwd_policy03.ldif -x -D "cn=admin,dc=nic,dc=cz" -w "silne_heslo"

dn: ou=policies,dc=nic,dc=cz
objectClass: organizationalUnit
objectClass: top
ou: policies

dn: cn=default,ou=policies,dc=nic,dc=cz
cn: default
objectClass: top
objectClass: device
objectClass: pwdPolicy
objectClass: pwdPolicyChecker
pwdAllowUserChange: TRUE
pwdAttribute: userPassword
pwdCheckQuality: 2
pwdFailureCountInterval: 300
pwdGraceAuthNLimit: 5
pwdInHistory: 10
pwdLockout: TRUE
pwdLockoutDuration: 180
pwdMaxFailure: 10
pwdMinAge: 0
pwdMinLength: 8

User data

I mentioned that our LDAP user database has been around for more than 12 years. Therefore, when I installed LDAP for the needs of CZ.NIC, I did not need to create a new DB, but I used what had been there for a long time. However, because I do not want to leave the most interesting bit out of this blog post, I add an example of a user data init.

# ldapadd -Z -h localhost -f user.ldif -x -D "cn=admin,dc=nic,dc=cz" -w "silne_heslo"

dn: dc=nic,dc=cz
dc: nic
o: CZ.NIC, z.s.p.o
objectClass: top
objectclass: dcObject
objectclass: organization

## FIRST Level hierarchy - People
dn: ou=People,dc=nic,dc=cz
ou: People
description: All people in
objectClass: top
objectClass: organizationalUnit

# Test user
dn: cn=trepka,ou=People,dc=nic,dc=cz
objectClass: person
objectClass: inetOrgPerson
cn: trepka
gn: Tomas
sn: Repka
uid: trepka
# heslo
userPassword: {SSHA}UVT5FQnKmCoq2X0qXWa7/dw3r+MPHQng
description: The Example

## FIRST Level hierarchy - Group
dn: ou=Group,dc=nic,dc=cz
ou: Group
description: All groups in
objectClass: top
objectClass: organizationalUnit

## FIRST Level hierarchy - Hosts
dn: ou=Hosts,dc=nic,dc=cz
ou: Hosts
description: All hosts in
objectClass: top
objectClass: organizationalUnit

About LDAP in CZ.NIC

We have been running LDAP in CZ.NIC for many years, and during this period of time it has been naturally reinstalled several times as part of the software replacement. The reinstallation of our LDAP is linked to its transfer to Ansible, or to writing a role for it. This will help us reinstall it during the next replacement cycle. In our ticketing system, the oldest mention of LDAP goes about 12 years back. But it is possible that it has been there even longer. Even after such a long time, however, it cannot be said that our LDAP in CZ.NIC is particularly large in volume. We have roughly 200 user accounts, the size of the cached database is about 1 GB, and the number of accepted connections is around 50,000 queries a day.

If you are interested in what LDAP can do, I can summarize what we do with it. We have there, of course, defined users, including their groups. In groups, there are people, linked groups of people are defined in their own groups via memberUid. We do not use Kerberos, so passwords are hashed directly in DB. In addition to users, we also have there the passwords of our mail server’s mailboxes. Because our users are using Eduroam wifi, we have defined service groups for Radius that sort users into VLAN of the corresponding departments where they work, and which Freeradius verifies against LDAP. Then there are data related to Samba, the ppolicy module, the calendar accounts of the SOGo calendar session room, and finally the test accounts.

We use the mirror type for LDAP replication (in our case, no more than two LDAP servers are needed). In the event of a server crash, the IP addresses are switched to the backup server using Ucarp. As for additional schemas, we have the Samba, Radius, SOGo (SOGo Resource Configuration) and PPolicy schemas.

As a GUI for fast crawling, deleting, and adding trivial things, we are satisfied with the currently no longer developed (but still functional) PhpLdapAdmin. We use our scripts to deal with the remaining matters, i.e. adding users, groups, changing passwords, and other things that are added to the user DB using .ldif.

As for other services, almost everything is linked to LDAP, from login to systems and system groups and an internal employee system, to embedded external applications such as Jabber, a calendar solution and many more.


Leave a comment

All fields are required. Email won't be shown.