Configuring LDAP services (revision 1.1)

Introduction

Think about the traditional Unix user, password and group definitions:

  • they are stored as flat file databases (i.e. text files in which one line is one record) in /etc/passwd, /etc/shadow and /etc/group
  • they are simple text files, therefore …
  • … we can modify them with a text editor (e.g. vi, emacs).

But with LDAP:

  • they are stored in a hierarchical database, known in LDAP parlance as a Directory Information Tree (‘DIT’) under /var/lib/ldap
  • we are not concerned with how the data is encoded, therefore …
  • … we may only modify the database with commands that do understand how the data is encoded (e.g. ldapadd, ldapmodify, ldapdelete).

DITs are identified by their base Distinguished Name (‘DN’). The base DN is something you decide for yourself, but typically it is the ‘dc’-prefixed and comma-separated list of labels in the fully qualified domain name of an organisation. For example, my LDAP server is ‘ziti.pasta.net’, so my home network’s domain is ‘pasta.net’, so my base DN should probably be ‘dc=pasta,dc=net’. That particular base DN is referenced in all the commands below, so remember to adjust to suit your own requirements!

The nodes in the DIT are referred to as Organisation Units (‘OU’); these are a bit like the component directory name in a pathname. No! Wikedia says that the DN’s components are like the pathname; so I think I’ve got that wrong about the role of OUs! OUs must have been created before we attempt to put things into them.

LDAP configuration data (e.g. the LDAP admin’s password, the SASL authentication mechanisms to allow, the name of the DIT) can be stored in one of two ways:

  • in files under /etc/ldap/slapd.conf and is managed by editing these files and restart the LDAP daemon (slapd)
  • in a separate DIT called the Runtime Configuration (‘RTC’) DIT (typically with base DN set to ‘cn=config’) and, like the regular DIT, is managed using commands (e.g. ldapadd, ldapmodify).

While the first option looks like it might be easier to manage, the second option seems to be what most people are using now, so I will use the first option. We will return to the two DITs as the regular DIT and the RTC DIT. (There is an additional third DIT called ‘frontend’; see section 5.2.5.1 of this document for details.)

There are various vendor-specific conventions regarding the names of OUs within the regular DIT (e.g. some put user records under a ‘People’ node, others put them under a ‘Users’ node. There is no fixed rule except that you need to be consistent in your approach: if you use ‘Users’ on the server then you will need to tell clients to use ‘Users’ too. This document will put:

  • all user records under ‘ou=Users’
  • all associated group records under ‘ou=Groups’
  • all associated automounter records under ‘ou=admin,ou=automounter’ (which seems to be what the automounter is configured to expect by default)

This document is heavily based on the work of others; see the links at the bottom of the page for details.

This document was written for Debian 10, which uses LDAP server version 2.4.7, so remember to adjust to suit your own requirements!

Server configuration

  1. Install the LDAP server and tools without being asked any questions by running:
    DEBIAN_FRONTEND=noninteractive apt-get install slapd ldap-utils

    (We skip the questions because otherwise it would have asked only a subset of the questions we want to answer! The full list will be answered shortly.)

  2. Check the name of the configuration DIT by running:
    ldapsearch -H ldap:// -x -s base -b "" -LLL "configContext"
    

    (Probably that reports that the configuration DIT is called ‘cn=config’.)

  3. Check the name of the regular DIT by running:
    ldapsearch -Y EXTERNAL -H ldapi:/// -s base -b "" -LLL "namingContexts"

    and if that reports nothing or something other than ‘dc=pasta,dc=net’ then answer the above-avoided questions plus some more by running:

    dpkg-reconfigure slapd 

    answering the questions as follows:

    1. omit OpenLDAP server configuration: accept the default (i.e. the server configuration will be completed).
    2. DNS domain name: accept the default
    3. Organization name: accept the default (the same as the DNS domain name)
    4. set the password for the LDAP administrator (actually, I think this means: set the password for the administrator of the regular DIT)
    5. database backend to use: accept the default (MDB)
    6. remove database when slapd is purged: accept the default (no)
    7.  move the old database: accept the default (yes)
  4. Test as follows:
    1. Run:
      ziti# ldapwhoami -x
      anonymous
      ziti#

      and verify the output is as shown. (It reports ‘anonymous’ as root has not been properly authenticated when using this command.)

    2. Attempt the same command using SASL authentication:
      ziti# ldapwhoami -U admin
      SASL/DIGEST-MD5 authentication started
      Please enter your password: 
      ldap_sasl_interactive_bind_s: Invalid credentials (49)
      	additional info: SASL(-13): user not found: no secret in database
      ziti#
    3. Note, as described here, that:

      Some documentation and examples in the wild assume the existence of the entry cn=admin,cn=config in the RTC DIT and use this as the root Distinguished Name (DN). However, the default installation does not create any RootDN/RootPW entries in the RTC DIT. You must use the EXTERNAL mechanism to bind and manage the RTC DIT in the default installation. To manage the RTC DIT with tools such as slapadd or ldapmodify, bind with -Y EXTERNAL -H ldapi:///:

      #test if OpenLDAP/slapd is running correctly:
      sudo ldapsearch -Y EXTERNAL -H ldapi:/// -b cn=config

      So from here on:

      • commands accessing the RTC DIT will use bind options -Y EXTERNAL -H ldapi:///
      • commands accessing the regular DIT will use bind options -x -D cn=admin,dc=pasta,dc=net -W
    4. So now attempt the same command using SASL authentication over the IPC channel:
      ziti# ldapwhoami -Y EXTERNAL -H ldapi:///
      SASL/EXTERNAL authentication started
      SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
      SASL SSF: 0
      dn:gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
      ziti#
  5. Some documents on the internet say to add additional schemas by running:
    ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/ldap/schema/cosine.ldif
    ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/ldap/schema/inetorgperson.ldif
    ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/ldap/schema/nis.ldif

    but by running:

    ldapsearch -Y EXTERNAL -H ldapi:// -b "cn=schema,cn=config" -s one -Q -LLL dn

    I verified that these seemed already to be present.

  6. To enable SASL authentication with LDAP I did the following but I’m not sure that any of this is actually necessary:
    1. We will need a test user to check this, so delay doing this procecdure until one normal user has been created.
    2. When an LDAP client (e.g. ldapwhoami) wants to authenticate a user using SASL, then it needs to know where in the DIT the authentication data is stored, but this is site-specific (e.g. under ‘ou=People’ or ‘ou=Users’ or ‘ou=Customers’ or ‘ou=Staff’ or whatever). I am guessing that for this reason the location is not specified in the RTC DIC; we need to add it outselves. Create a file /tmp/authz-regexp.ldif containing:
      dn: cn=config
      changetype: modify
      add: olcAuthzRegexp
      olcAuthzRegexp: uid=([^,]*),cn=digest-md5,cn=auth ldap:///dc=pasta,dc=net??sub?(uid=$1)

      and then apply this change by running:

      ldapmodify -Y EXTERNAL -H ldapi:/// -f  /tmp/authz-regexp.ldif 
      
    3. Passwords need to be stored in clear test for SASL authentication to work. Reset a test user’s password to be stored in plain text by creating the file /tmp/<username>-passwd.ldif containing:
      dn: uid=alexis,ou=Users,dc=pasta,dc=net
      changetype: modify
      replace: userpassword
      userpassword: <put-the-plaintext-password-here>

      and run:

      ldapmodify -x -D cn=admin,dc=pasta,dc=net -W -f  /tmp/<username>-passwd.ldif
    4. Check if SASL authentication is now working for that single user by running:
      ldapwhoami -U alexis

      and checking the looks like this:

      SASL/DIGEST-MD5 authentication started
      Please enter your password: 
      SASL username: alexis
      SASL SSF: 128
      SASL data security layer installed.
      dn:uid=alexis,ou=users,dc=pasta,dc=net
  7. If autofs support is required then, as per instructions here, define the automountMap object class by running:
    wget -O /etc/ldap/schema/autofs-ldap.ldif https://launchpadlibrarian.net/55451730/autofs.ldif
    ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/ldap/schema/autofs-ldap.ldif

    Note that:

    • In case the LDIF files disappears from that location, here is a copy.
    • The object class can also be defined by installing the autofs-ldap package but that pulls in a lot of dependencies, including autofs, which is probably not wanted on the LDAP server.
  8. Before adding groups or users we need to add containers for these things. Do this as follows:
    1. Create /tmp/containers.ldif containing:
      dn: ou=Users,dc=pasta,dc=net
      objectClass: organizationalUnit
      ou: Users
      
      dn: ou=Groups,dc=pasta,dc=net
      objectClass: organizationalUnit
      ou: Groups
    2. If autofs support is required then:
      1. Add this to the same file (with a blank line separating it from any existing content in the file):
        dn: ou=admin,dc=pasta,dc=net
        ou: admin
        objectClass: top
        objectClass: organizationalUnit
        
        dn: ou=automount,ou=admin,dc=pasta,dc=net
        ou: automount
        objectClass: top
        objectClass: organizationalUnit
        
      2. If you want to store the auto.master map itself in LDAP (I do not!) then add this to the same file (with a blank line separating it from any existing content in the file):
        dn: ou=auto.master,ou=automount,ou=admin,dc=pasta,dc=net
        ou: auto.master
        objectClass: top
        objectClass: automountMap
        
      3. If you want to store the auto.home map in LDAP (I do!) then add this to the same file (with a blank line separating it from any existing content in the file):
        dn: ou=auto.home,ou=automount,ou=admin,dc=pasta,dc=net
        ou: auto.home
        objectClass: top
        objectClass: automountMap
        
      4. If you want to store the auto.staging map in LDAP (I do!) then add this to the same file (with a blank line separating it from any existing content in the file):
        dn: ou=auto.staging,ou=automount,ou=admin,dc=pasta,dc=net
        ou: auto.staging
        objectClass: top
        objectClass: automountMap
    3. Run:
      ldapadd -x -D cn=admin,dc=pasta,dc=net -W -f /tmp/containers.ldif
  9. If you want to add an entry to auto.staging for the mailserver to automount the mail directory, using a symlink pointing from /var/mail to /staging/mail (I do!) then create /tmp/mail-autofs.ldif containing:
    dn: cn=mail,ou=auto.staging,ou=automount,ou=admin,dc=pasta,dc=net
    cn: mail
    objectClass: top
    objectClass: automount
    automountInformation: -nordirplus,noatime,nodiratime,nfsvers=3,nolock,proto=tcp filer-on-storage-net.pasta.net,filer-on-public-net.pasta.net:/vol/small/mail
    

    and run:

    ldapadd -x -D cn=admin,dc=pasta,dc=net -W -f /tmp/mail-autofs.ldif
  10. If you want to add an entry to auto.staging for any machine to automount./pub, using a symlink pointing from /pub to /staging/pub (I do!) then create /tmp/pub-autofs.ldif containing:
    dn: cn=pub,ou=auto.staging,ou=automount,ou=admin,dc=pasta,dc=net
    cn: pub
    objectClass: top
    objectClass: automount
    automountInformation: -nordirplus,noatime,nodiratime,nfsvers=3,nolock,proto=tcp filer-on-storage-net.pasta.net,filer-on-public-net.pasta.net:/vol/pub

    and run:

    ldapadd -D cn=admin,dc=pasta,dc=net -W -f /tmp/pub-autofs.ldif 
  11. Other useful packages are shelldap and ldapvi which are easier to use than ldapmodify.

Creating a normal user

  1. Create /tmp/alexis.ldif containing:
    dn: cn=alexis,ou=Groups,dc=pasta,dc=net
    objectClass: posixGroup
    cn: alexis
    gidNumber: 1000
    description: Group account
    
    dn: uid=alexis,ou=Users,dc=pasta,dc=net
    objectClass: account
    objectClass: posixAccount
    cn: alexis
    uid: alexis
    uidNumber: 1000
    gidNumber: 1000
    homeDirectory: /home/alexis
    loginShell: /bin/bash
    gecos: Alexis Huxley
    description: User account
    

    which is deliberately missing the userPassword attribute.

  2. If using the automounter (I do!) then add this to the same file (with a blank line separating it from any existing content in the file):
    dn: cn=alexis,ou=auto.home,ou=automount,ou=admin,dc=pasta,dc=net
    cn: alexis
    objectClass: top
    objectClass: automount
    automountInformation: -nordirplus,noatime,nodiratime,nfsvers=3,nolock,proto=tcp filer-on-storage-net.pasta.net,filer-on-public-net.pasta.net:/vol/small/home/alexis
  3. Create the user by running:
    ldapadd -x -D cn=admin,dc=pasta,dc=net -W -f /tmp/alexis.ldif
  4. Set the user’s password by running:
    ldappasswd -x -D cn=admin,dc=pasta,dc=net  -W -S "uid=alexis,ou=Users,dc=pasta,dc=net"
  5. Check if SASL authentication is available by running:
    ldapwhoami -U alexis
  6. If SASL authentication is available then check if the password is stored in plaintext or not If SASL authentication is needed with passwords stored in plaintext (see the section about SASL authentication above) then get the base64-encoded password from LDAP and decode it, as in this example:
    ziti# ldapsearch -x -D cn=admin,dc=pasta,dc=net -W -b dc=pasta,dc=net -LLL 'uid=alexis' userPassword 
    Enter LDAP Password: 
    dn: uid=alexis,ou=Users,dc=pasta,dc=net
    userPassword:: c2VjcmV0
    ziti# 
    
    rotelle# php -r 'var_dump(base64_decode("c2VjcmV0"));'
    string(6) "secret"
    rotelle#

    (Note the :: which indicates that the value is base64-encoded.)

Client configuration

Note that pcms takes care of this for me!

  1. Run:
    apt-get install libnss-ldapd nslcd libldap-common

    (libnss-ldapd and nslcd are required for programs that consult nssswitch.conf, e.g. ‘getent’, ‘id’; libldap-common is required for programs that consult /etc/ldap/ldap.conf and then contact the LDAP server directly, e.g. ldapsearch).

  2. At the ‘Configuring nslcd’ window, when prompted ‘LDAP server URI’, enter ‘ldap://<ldap-server-ip-address>/’.
  3. At the ‘Configuring nslcd’ window, when prompted ‘LDAP server search base’, enter the correct search base as set earlier (e.g. ‘dc=pasta,dc=net’)
  4. At the ‘Configuring libnss-ldapd’, when prompted ‘Name services to configure’, enable passwd, group and shadow services.
  5. Test by running:
    getent passwd alexis
    apt-get install ldap-utils
    ldapwhoami -x
    apt-get remove ldap-utils

Resetting the LDAP administrator’s password

  1. Log in to the LDAP server.
  2. Change the password for the RTC DIT as follows:
    1. Get the LDIF-record specifying the current password, as in this example:
      ziti# ldapsearch -H ldapi:// -LLL -Q -Y EXTERNAL -b "cn=config" "(olcRootDN=*)" dn olcRootDN olcRootPW
      dn: olcDatabase={0}config,cn=config
      olcRootDN: cn=admin,cn=config
      
      dn: olcDatabase={1}mdb,cn=config
      olcRootDN: cn=admin,dc=pasta,dc=net
      olcRootPW: {SSHA}SEdA8EFr5wpS/vLyspPzAtuViaARtdUB
      
      ziti# 
      

      and note:

      • the use of -H ldapi:// -Y EXTERNAL to communicate over a channel that obviates the need to provide credentials in order to authenticate oneself
      • the hash type used to store the old password, which in this case is {SSHA}
      • the database format, which in this case is mdb.
    2. Use slappasswd, possibly with the -h option to use the same hash type as before, to generate the hash of the new password, e.g.:
      ziti# slappasswd -h '{SSHA}'
      New password: 
      Re-enter new password: 
      {SSHA}JCh1Ec89olTXRCe9HV1OJW7OId1B5JCq
      ziti#
    3. Create a new LDIF file called /tmp/config-admin-passwd.ldif containing:
      dn: olcDatabase={1}<database-format-e.g.-mdb>,cn=config
      changetype: modify
      replace: olcRootPW
      olcRootPW: <hashed-password-including-hash-type-prefix-as-reported-by-slappasswd-above>
      
    4. Apply this by running:
      ldapmodify -H ldapi:// -Y EXTERNAL -f /tmp/config-admin-passwd.ldif
  1. Change the password for the regular DIT as follows:
    1. Create a new LDIF file called /tmp/normal-admin-passwd.ldif containing:
      dn: cn=admin,dc=pasta,dc=net
      changetype: modify
      replace: userPassword
      userPassword: <hashed-password-including-hash-type-prefix-as-reported-by-slappasswd-above>
      
    2. Apply this by running:
      ldapmodify -H ldap:// -x -D "cn=admin,dc=pasta,dc=net" -W -f /tmp/normal-admin-passwd.ldif
      
    3. and note:
      • the use of -H ldap:// -x to do simple authentication, using the admin password just set above.

Determining which SASL authentication mechanisms are enabled

  1. Run:
    ldapsearch -LLL -H ldapi:// -Y EXTERNAL -D dc=pasta,dc=net -s base -b "" "(objectclass=*)" supportedSASLMechanisms

    (This is taken from here.)

  2. For reasons I don’t yet understand the following command gives different results:
    ldapsearch -LLL -x -s base -b "" "(objectclass=*)" supportedSASLMechanisms

    (This is based on something taken from here.)

ldapscripts

We deliberately use only ldapadd, ldapdelete and ldapmodify as these should work everywhere and keep the software stack to a minimum, but there is a package that will allow some shortcuts, especially for creating groups and users. I do not use it!

  1. Run:
    apt-get install ldapscripts
  2. Edit /etc/ldapscripts/ldapscripts.conf and set the following, being sure to ignore the Debian-specific ways of doing things:
    SERVER="ldap://localhost"
    SUFFIX="dc=pasta,dc=net"
    GSUFFIX="ou=Groups"
    USUFFIX="ou=Users"
    MSUFFIX="ou=Machines"
    SASL_AUTH=""
    BINDDN="cn=admin,dc=pasta,dc=net"
    PASSWORDGEN="<ask>"

    (That’s a literal ‘<‘ and ‘>’.)

  3. Run:
    echo -n '<the-ldap-password-you-set-earlier>' > /etc/ldapscripts/ldapscripts.passwd
    chmod 600 /etc/ldapscripts/ldapscripts.passwd
  4. Test by running:
    lsldap

List contents of DITs

  1. To list the whole contents of the regular DIT run
    ldapsearch -z 0 -W \
        -b "dc=pasta,dc=net" \
        -D "cn=admin,dc=pasta,dc=net" \
        "(objectclass=*)"
  2. To list the whole contents of the RTC DIT run:
    ldapsearch -W -Y EXTERNAL -H ldapi:/// -z 0 \
        -b "cn=config" \
        "(objectclass=*)"

Backing up and restoring DITs

How To Backup and Restore OpenLDAP

Stuff for me to investigate further

  • chsh probably doesn’t work
  • force changing password see https://serverfault.com/questions/255603/ldap-force-user-to-change-password

See also