Notes from trying to install and configure OpenLDAP

So, one fine day one of our company's fine products added LDAP integration. I saw that Ubuntu comes with an "OpenLDAP" right in the official repository, and boldly stepped forth. It should be pretty easy to get a local LDAP server running to test the integration against; free software for the win!

I did get a basic user/group setup working... four 10-hour days later.

Note: This is a warning example of how not to get OpenLDAP running. Stop searching for piecemeal internet solutions by strange people like myself who don't fully understand OpenLDAP. Go straight to the official docs instead. They're more likely to be correct than anything else around the web. Unfortunately, they tend to lack usable examples.

Further note: Considering what a dumpster fire the user experience on OpenLDAP is, I strongly recommend looking at alternatives. LLDAP is well-regarded and clearly easier to operate.

The Setup

To start with, I installed slapd and ldap-tools, available right from the regular repository. (This is Ubuntu 22.04 LTS.)

The slapd service started running, and could be contacted within the virtual machine itself, but not from my Windows workstation. It took some time to figure this out, since there are a few different powershell scripts on the internet for pinging an LDAP service, and none of them seemed to do anything.

It took a few tries to find how to enable slapd logging, since there's more than one way to try to apply configuration changes. Some internet advice including the official docs refer to a slapd.conf, but I had no such thing, since it's been deprecated in favor of a dn: cn=config. So a lot of random internet solutions no longer work, and the error message for attempting to configure things the wrong way is an unhelpful (50) Insufficient access.

Anyway, this worked:

sudo ldapmodify -Y EXTERNAL -H ldapi:/// -f logging.ldif

Where the file logging.ldif contains:

dn: cn=config
changetype: modify
replace: olcLogLevel
olcLogLevel: stats

It's also possible to get extra logging from any commandline command by including the parameter -d <level>, for example -d-1.

In this case, standard LDAP port 389 was blocked by the company firewall, so the scripts attempting to connect were rightly stonewalled. I configured slapd to listen on a different port. This worked halfway; the server received my LDAP bind attempt, but no response came back. Possibly some deep packet inspection by the firewall, making sure no unauthorised LDAP-looking stuff could be sent on any port.

But LDAP traffic works both ways when connecting from one of my testing VMs. Good enough! I'll just use one of those for testing.

The Configuration

To get started, /etc/ldap/ldap.conf needed some basic details:

BASE    dc=my,dc=domain,dc=com
URI     ldap://uwu.my.domain.com:8989

Let's call the admin user admin.my.domain.com, and the password secret.

Adding a basic user is easy enough, just follow random online instructions:

ldapadd -x -D cn=admin,dc=my,dc=domain,dc=com -w secret -f base.ldif

Contents of base.ldif:

dn: ou=People,dc=my,dc=domain,dc=com
objectClass: organizationalUnit
ou: People
dn: ou=Groups,dc=my,dc=domain,dc=com
objectClass: organizationalUnit
ou: Groups
dn: uid=beavel,ou=People,dc=my,dc=domain,dc=com
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: beavel
sn: Beaver
givenName: Link
cn: Link Beaver
displayName: Link Beaver
mail: link@lostwoods.hy
uidNumber: 5001
gidNumber: 5001
userPassword: {SSHA}vX/TiqXAF7ML4ItDU61IT41ttRmcO7xL
gecos: Link Beaver
loginShell: /bin/bash
homeDirectory: /home/beavel

But here's the real problem – I have to test that our application reads an LDAP user's memberOf value correctly. But how can I make a memberOf show up? I can't just specify it among the other user values, that produces an error.

I created a group and tried various commands to make my user join. Ultimately, I found OpenLDAP accepts this incantation to specify membership when creating the group:

dn: cn=devs,ou=Groups,dc=my,dc=domain,dc=com
objectClass: groupOfNames
cn: devs
member: uid=beavel,ou=People,dc=my,dc=domain,dc=com

The current database state can be queried at any time with this:

ldapsearch -x -D cn=admin,dc=my,dc=domain,dc=com -w secret

However, memberOf does not appear anywhere, although member does. I still can't add memberOf explicitly to the user.

Further research reveals that memberOf is managed automatically by an "overlay", which is like a plugin for extra functionality. It sounds like I need the memberof and refint overlays for this to work. (I am later advised that for this use case, only memberof is necessary.) This must be loaded and applied explicitly:

sudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f memberof.ldif

Where memberof.ldif contains:

dn: cn=module{0},cn=config
changetype: modify
add: olcModuleLoad
olcModuleLoad: memberof.la
dn: olcOverlay=memberof,olcDatabase={1}mdb,cn=config
changetype: add
objectClass: olcOverlayConfig
objectClass: olcMemberOf
olcOverlay: memberof
olcMemberOfRefint: TRUE

Helpfully, you can see which overlays are presently loaded and applied:

sudo slapcat -n 0 | grep olcModuleLoad
sudo slapcat -n 0 | grep olcOverlay

There is another, longer command that supposedly lets you see the loaded stuff as well, but it didn't work for me, while the above simple stupid command did.

Unfortunately, even though the overlays were activated, the group only showed my user as a member, but my user did not admit to being a memberOf.

That's because memberships defined before the overlays are loaded don't count. It seemed appropriate at this junction to wipe the whole LDAP database in disgust and start over, with this:

sudo dpkg-reconfigure slapd

Just tell the helpful dpkg TUI installer to throw away the database. That done, I re-created the user and group. Still no memberOf however.

Wiping the database unloads all the overlays, which I failed to notice, so that one's on me. I re-loaded the overlays, then re-created the user and group.

Still no memberOf.

Actually, because memberOf isn't a normal user property, you can only see it if you specifically ask for it:

ldapsearch -LL -Y EXTERNAL -H ldapi:/// "(uid=beavel)" -b dc=my,dc=domain,dc=com memberOf

Result:

SASL/EXTERNAL authentication started
SASL username: gidNumber=1000+uidNumber=1000,cn=peercred,cn=external,cn=auth
SASL SSF: 0
version: 1
dn: uid=beavel,ou=People,dc=my,dc=domain,dc=com
memberOf: cn=devs,ou=Groups,dc=my,dc=domain,dc=com

And there we have it, folks. I was finally able to proceed with testing. May the designer of OpenLDAP step on a lego.

Epilogue

I did find two bugs in our application's LDAP integration, so it wasn't all for naught.

Bonus section: TLS

I also had to enable TLS to check if an LDAPS connection would also work.

With a known good CA certificate, and a freshly-generated program certificate and key, the steps to enable LDAPS are theoretically straightforward. All certificate files apparently must be placed in /etc/ldap, the files must be owned by openldap:openldap, and their access rights must all be 0640. Failing any of these requirements just produces an unhelpful error 80.

Using certinfo.ldif:

dn: cn=config
add: olcTLSCACertificateFile
olcTLSCACertificateFile: /etc/ldap/ca.crt
add: olcTLSCertificateKeyFile
olcTLSCertificateKeyFile: /etc/ldap/server.key
add: olcTLSCertificateFile
olcTLSCertificateFile: /etc/ldap/server.pem

Also, edit /etc/ldap/ldap.conf and add an LDAPS address with you desired port on the URI line. Separate the LDAPS address from the LDAP address with a single space if you want to have both kinds available. It may or may not be necessary to add some kind of certificate references in here as well, beside the TLS_CACERT entry.

To verify STARTTLS connection is working (using whatever port you set it to):

ldapwhoami -x -ZZ -H ldap://uwu.my.domain.com:8989

To verify an LDAPS TLS connection is working (using whatever port you set it to):

ldapwhoami -x -H ldaps://uwu.my.domain.com:8686
Closing thoughts

My engineering pride kicked in so I stubbornly bruteforced a solution to this, but it really shouldn't have been so difficult. More descriptive error messages would have been helpful, as well as further improvements to the official documentation.

A real problem is that it really is harder to find useful information on the internet now. A lot of solutions may have been correct at one time, but don't work on new versions or on different OS distributions. Add some AI slop on top and it's a right mess. Every answer is subtly or blatantly incorrect, so you have to synthesize information from multiple searches.

Trying to seek help from other users or even the developers themselves is of course always an option, but it comes with the risk of being called out for not having read the manual closely enough.

It's pretty painful to manage OpenLDAP through non-interactive commands... Isn't there a friendlier interface?

Lessons learned:

Further reading