OpenLDAP with TLS and LetsEncrypt on Ubuntu 16.04

A project I’m working on requires a Kerberos and LDAP infrastructure. As with most tech, it’s easy to do something quickly, but much harder to do it properly and get it documented.
One of the biggest problems I encountered was when setting up replication between LDAP servers. We use SaltStack to build and maintain our server estate, so deployment and configuration needs to be automated.
Using LetsEncrypt to issue a certificate to each server, OpenLDAP can take the certificate and use it to encrypt and authenticate connections from other LDAP servers. It’s meant to be simple – add this LDIF file to your directory:
dn: cn=config
add: olcTLSCACertificateFile
olcTLSCACertificateFile: /etc/letsencrypt/live/ldap1.example.com/fullchain.pem
-
add: olcTLSCertificateFile
olcTLSCertificateFile: /etc/letsencrypt/live/ldap1.example.com/cert.pem
-
add: olcTLSCertificateKeyFile
olcTLSCertificateKeyFile: /etc/letsencrypt/live/ldap1.example.com/privkey.pem

This error kept cropping up:
$ sudo ldapmodify -Y EXTERNAL -H ldapi:/// -f ./ssl.ldif
SASL/EXTERNAL authentication started
SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
SASL SSF: 0
modifying entry "cn=config"
ldap_modify: Other (e.g., implementation specific) error (80)

Restarting slapd with all debugging switched on resulted in nothing, and strace-ing the daemon whilst running was similarly fruitless.
Cutting to the chase, the problem was that the openldap user didn’t have access to the certificate symlinks, nor the certificates either, and that AppArmor was blocking access to the files under /etc/letsencrypt.
So how do we solve it?
First, using setfacl, give the openldap user rx permissions on /etc/letsencrypt/live and /etc/letsencrypt/archive. This will allow slapd to follow read and follow the symbolic links to the actual files in the archive directory. Next, add the following to /etc/apparmor.d/local/usr.sbin.slapd:
/etc/letsencrypt/live/{{ grains.id }} r,
/etc/letsencrypt/archive/{{ grains.id }} r,
/etc/letsencrypt/archive/{{ grains.id }}/** r,

The trailing comma on the last line isn’t an error – this will stop AppArmor blocking slapd access to the certificate symlink and the actual certificate, as above. Remember to restart AppArmor afterwards.
And that’s it.