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?
- phpldapadmin: seemed to be bugged
- shelldap: nifty, but not much friendlier than the commandline
- ldapscripts: failed entirely to communicate with the ldap interface, and I didn't have the energy to figure out why
- tinyldap: really cool, but appeared to have some incompatibility with Ubuntu 22.04 and would not build
- lldap: works great, simpler server, for when you can't take OpenLDAP anymore
Lessons learned:
- Always blame the firewall first
- Always check the entire DN (distinguished name) is correct, very easy to forget one of its components
- The memberof overlay must be activated before the user is made a member, or it won't work
- Wiping the database to start afresh also removes overlays so you have to apply them again
- Groups apparently can't be both a posixGroup and groupOfNames at the same time; making it just groupOfNames seems to be enough
Further reading
- Critical presentation from a hacker: www.fefe.de/ccccamp2003-ldap.pdf
- As always, the archwiki is your friend: wiki.archlinux.org/title/OpenLDAP
- More helpful: ubuntu.com/server/docs/how-to-set-up-ldap-users-and-groups
- Even better: www.openldap.org/doc/admin26/guide.html#Reverse Group Membership Maintenance
- Interesting, on memberof overlays: tylersguides.com/guides/openldap-memberof-overlay