LDAP server with 389ds: Part 4 – Security

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

 - Congratulations! Your certificate and chain have been saved at:
   Your key file has been saved at:
   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 \
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.

9 thoughts on “LDAP server with 389ds: Part 4 – Security

  1. Pingback: Setting Up Let's Encrypt Certificates For The 389-ds LDAP Server - RSSFeedsCloud

  2. avataradmin Post author

    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 on 389-ds-base-libs which depends on openssl-libs which in turn depends on ca-certificates.

  3. avatarAnonymous

    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

  4. avatarAnonymous

    Do you need to rerun the `dsctl tls` command each 90 days, or does that create a symlink or similar?

  5. avataradmin Post author

    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

    if grep -i --quiet "ldap.example.com" <<< "$RENEWED_DOMAINS"; then
      /bin/cp -f $RENEWED_LINEAGE/{cert.pem,chain.pem,fullchain.pem} /etc/pki/tls/certs/ldap.example.com/
      /bin/cp -f $RENEWED_LINEAGE/privkey.pem /etc/pki/tls/private/ldap.example.com/
      chown root:root /etc/pki/tls/certs/ldap.example.com/*.pem
      chown root:root /etc/pki/tls/private/ldap.example.com/*.pem
      chmod 640 /etc/pki/tls/certs/ldap.example.com/*.pem
      chmod 640 /etc/pki/tls/private/ldap.example.com/*.pem
      dsctl localhost tls import-server-key-cert /etc/pki/tls/certs/ldap.example.com/fullchain.pem /etc/pki/tls/private/ldap.example.com/privkey.pem  && dsctl localhost restart || true

    should do the trick. Have a look at the certbot user guide for more details on hooks.

  6. avatarAnonymous

    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

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.