In part 3 we’ve looked at ACLs and how to use them to restrict privileges of directory users. Unfortunately it’s still possible to access the 389 Directory Server instance that we’ve created all the way back in part 1 anonymously (i.e. without authenticating as a directory user) which renders the ACLs somewhat pointless. So it’s time to tighten up security a bit.
5. Securing an instance
5.1. Disabling anonymous binds
Connecting to an LDAP server without a user name or password is called an anonymous bind. This is frequently used to simplify searching the DIT for common information such as group names or email addresses since it doesn’t require to authenticate with a service account first. By default the 389 Directory Server allows anonymous binds for search and read operations.
This introduces some security concerns however: Not only need the ACLs to be watertight to prevent access to sensitive information (especially if the GDPR is applicable) but anonymous binds can also be used for denial of service attacks or other attack vectors.
To disable anonymous binds we can make use of dsconf
:
dsconf localhost config replace nsslapd-allow-anonymous-access=off
5.2. TLS encryption with Let’s Encrypt
Non-encrypted communication with an LDAP server is simply just a bad idea. Even in LAN environments it’s just best practise to use TLS to meet potential threats with defense in depth.
The 389 Directory Server supports encrypted communication via the LDAPS protocol (TLS encryption is used right after the connection has been established) or STARTTLS over LDAP (the connection is not encrypted until the STARTTLS command is sent by the client). For both we need a valid certificate.
While it’s possible to use a self-signed certificate (which can be conveniently created by setting self_sign_cert
to True
in an inf answer file, see part 1) it can be quite laborious (or maybe not even possible) to ensure that the certificate authority is trusted by all applications that want to communicate with out LDAP server. And if the certificate can’t be verified, applications won’t (or at least shouldn’t) establish a connection.
Let’s encrypt is a CA that’s run by the Internet Security Research Group and is trusted by almost all applications. Certificates are free, easy to create and use and can be automatically renewed with certbot
, so we might as well just use that.
To install certbot and enable automatic certificate renewal:
# dnf install certbot # systemctl enable certbot-renew.timer Created symlink /etc/systemd/system/timers.target.wants/certbot-renew.timer → /usr/lib/systemd/system/certbot-renew.timer.
and assuming our 389ds instance is reachable on port 80 from the internet we can use the standalone mode to create a certificate (for other methods on how to create certificates, have a look at the certbot documentation):
# certbot certonly --standalone -m <your_email> --agree-tos -d ldap.example.com Saving debug log to /var/log/letsencrypt/letsencrypt.log Plugins selected: Authenticator standalone, Installer None Requesting a certificate for ldap.example.com Performing the following challenges: http-01 challenge for ldap.example.com Waiting for verification... Cleaning up challenges IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/ldap.example.com/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/ldap.example.com/privkey.pem Your certificate will expire on 2021-08-11. To obtain a new or tweaked version of this certificate in the future, simply run certbot again. To non-interactively renew *all* of your certificates, run "certbot renew" - If you like Certbot, please consider supporting our work by: Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate Donating to EFF: https://eff.org/donate-le
The certificates and private key were installed to /etc/letsencrypt/live/ldap.example.com/
so all that’s left to do is to import them into the 389ds instance and enable TLS:
dsctl localhost tls import-server-key-cert \ /etc/letsencrypt/live/ldap.example.com/fullchain.pem \ /etc/letsencrypt/live/ldap.example.com/privkey.pem dsconf localhost config replace nsslapd-securePort=636 nsslapd-security=on dsctl localhost restart
We can now communicate with our LDAP server via LDAPS on port 636 or using STARTTLS on port 389.
5.3. Requiring secure binds
So now we can communicate securely with our LDAP server. However, it’s not required and unencrypted communication is still possible on port 389. To require secure, enctypted communication, we set nsslapd-require-secure-binds
to on
dsconf localhost config replace nsslapd-require-secure-binds=on dsctl instance_name restart
Note that this only concerns authenticated binds, i.e. when a user name and password is used. If we hadn’t disabled anonymous bind, establishing an unecrypted connection would still be possible.
5.4. Password storage schemes
Passwords are not stored in the directory server database in cleartext but hashed. The algorithm that’s used to hash the passwords is called the password storage scheme.
There are two separate password storage schemes, one for the directory manager (nsslapd-rootpwstoragescheme
) and another one for user passwords (nsslapd-pwstoragescheme
). Both default to PBKDF2_SHA256
, which is considered to be susceptible to GPU or ASIC brute force attacks. Unfortunately, more modern hashing algorithms that would solve this problem by requiring larger amounts of RAM like bcrypt or scrypt are currently not supported by the 389 Directory Server (see the list of available password storage schemes)
Changing the password storage schemes is rather straighforward though. Assuming we wanted to change both, the directory manager scheme as well as the user password scheme to CRYPT-SHA512
, we can run
dsconf localhost pwpolicy set --pwdscheme=CRYPT-SHA512 dsconf localhost config replace nsslapd-rootpwstoragescheme=CRYPT-SHA512
Of course, since the clear text is unknown all passwords have to be rehashed. To rehash the directory manager password:
dsconf localhost config replace nsslapd-rootpw="mysecret"
Note that’s it’s questionable if CRYPT-SHA512
offers more resilience to brute force attacks than the default PBKDF2_SHA256
.
Now that our 389ds instance is completely configured and we’ve tightened up security it’s time to look at how to back it up and – in case of a disaster – restore it in part 5.
Don’t you need to also add the CA to the server, or is this not necessary?
Pingback: Setting Up Let's Encrypt Certificates For The 389-ds LDAP Server - RSSFeedsCloud
Well, the CA certificates are usually already present, e.g. on RHEL/CentOS/Fedora the package
ca-certificates
is almost always already installed.If not, it’ll be pulled in when installing
389-ds-base
through dnf, since that’s depending on389-ds-base-libs
which depends onopenssl-libs
which in turn depends onca-certificates
.Hello, how i can synchronize the 389-ds with Active directory my ain to pull a piece of my active directory to 389-ds without create a shcema localy ? thanks for response
Have a look at the Sync With Active Directory page from the 389ds documentation. That should get you started. Good luck!
Do you need to rerun the `dsctl tls` command each 90 days, or does that create a symlink or similar?
Yes, a renewed certificate needs to be imported again. If you’re using the
certbot-renew
service it’s probably easiest to use a deploy hook in/etc/letsencrypt/renewal-hooks/deploy/
Something similar to
should do the trick. Have a look at the certbot user guide for more details on hooks.
I faced the following error:
Error: The file ../live/sso.domain.de/fullchain.pem may be a chain file. This is not supported. Break out each certificate and key into unique files, and import them individually.
This is what I did to make it work:
first, for import-server-key-cert you need the cert-file and privkey-file, so the command should look like this
´dsctl slapd-T39 tls import-server-key-cert ../live/sso.t39.rwth-aachen.de/cert.pem ../live/sso.t39.rwth-aachen.de/privkey.pem´
second, for importing the chain, you have to split the chain.pem to seperate .pem files and import each of them:
csplit –prefix=output- –suffix-format=%d.pem ../chain.pem ‘/^—–END /1’ ‘{*}’
dsctl slapd-localhost tls import-ca output-0.pem letsencrypt2
dsctl slapd-localhost tls import-ca output-1.pem letsencrypt1
Depending on which version of 389-ds you use, you could be running into this bug: https://github.com/389ds/389-ds-base/issues/5290
This should be fixed in version 2.2.3 and later.