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
- 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.)
- 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’.)
- 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:
- omit OpenLDAP server configuration: accept the default (i.e. the server configuration will be completed).
- DNS domain name: accept the default
- Organization name: accept the default (the same as the DNS domain name)
- set the password for the LDAP administrator (actually, I think this means: set the password for the administrator of the regular DIT)
- database backend to use: accept the default (MDB)
- remove database when slapd is purged: accept the default (no)
- move the old database: accept the default (yes)
- Test as follows:
- 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.)
- 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#
- 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
- commands accessing the RTC DIT will use bind options
- 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#
- Run:
- 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.
- To enable SASL authentication with LDAP I did the following but I’m not sure that any of this is actually necessary:
- We will need a test user to check this, so delay doing this procecdure until one normal user has been created.
- 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
- 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
- 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
- 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.
- Before adding groups or users we need to add containers for these things. Do this as follows:
- 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
- If autofs support is required then:
- 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
- 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
- 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
- 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
- Add this to the same file (with a blank line separating it from any existing content in the file):
- Run:
ldapadd -x -D cn=admin,dc=pasta,dc=net -W -f /tmp/containers.ldif
- Create /tmp/containers.ldif containing:
- 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
- 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
- Other useful packages are shelldap and ldapvi which are easier to use than ldapmodify.
Creating a normal user
- 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.
- 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
- Create the user by running:
ldapadd -x -D cn=admin,dc=pasta,dc=net -W -f /tmp/alexis.ldif
- 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"
- Check if SASL authentication is available by running:
ldapwhoami -U alexis
- 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!
- 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).
- At the ‘Configuring nslcd’ window, when prompted ‘LDAP server URI’, enter ‘ldap://<ldap-server-ip-address>/’.
- 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’)
- At the ‘Configuring libnss-ldapd’, when prompted ‘Name services to configure’, enable passwd, group and shadow services.
- Test by running:
getent passwd alexis apt-get install ldap-utils ldapwhoami -x apt-get remove ldap-utils
Resetting the LDAP administrator’s password
- Log in to the LDAP server.
- Change the password for the RTC DIT as follows:
-
- 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
.
- the use of
- 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#
- 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>
- Apply this by running:
ldapmodify -H ldapi:// -Y EXTERNAL -f /tmp/config-admin-passwd.ldif
- Get the LDIF-record specifying the current password, as in this example:
- Change the password for the regular DIT as follows:
- 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>
- Apply this by running:
ldapmodify -H ldap:// -x -D "cn=admin,dc=pasta,dc=net" -W -f /tmp/normal-admin-passwd.ldif
- and note:
- the use of
-H ldap:// -x
to do simple authentication, using the admin password just set above.
- the use of
- Create a new LDIF file called /tmp/normal-admin-passwd.ldif containing:
Determining which SASL authentication mechanisms are enabled
- Run:
ldapsearch -LLL -H ldapi:// -Y EXTERNAL -D dc=pasta,dc=net -s base -b "" "(objectclass=*)" supportedSASLMechanisms
(This is taken from here.)
- 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!
- Run:
apt-get install ldapscripts
- 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 ‘>’.)
- Run:
echo -n '<the-ldap-password-you-set-earlier>' > /etc/ldapscripts/ldapscripts.passwd chmod 600 /etc/ldapscripts/ldapscripts.passwd
- Test by running:
lsldap
List contents of DITs
- 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=*)"
- 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
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
- Computing
- Securing openLDAP SASL authentication
- OpenLDAPServer at help.ubuntu.com
- How To Configure OpenLDAP and Perform Administrative LDAP Tasks at digitalocean.com
-
How To Change Account Passwords on an OpenLDAP Server at digitalocean.com
- AutofsLDAP at help.ubuntu.com
- Use ppolicy_hash_cleartext to keep OpenLDAP from storing and returning plain text passwords (includes a section on decoding the base64-encoded passwords returned by ldapsearch)