DEV Community 👩‍💻👨‍💻

Cover image for A brief introduction to LDAP
Donald Sebastian Leung
Donald Sebastian Leung

Posted on

A brief introduction to LDAP

This article assumes basic familiarity with the command line and system administration, ideally on a Unix or Unix-like operating system such as Linux; otherwise, it may be difficult to follow through the hands-on section on OpenLDAP. For reference, the author is a Linux Foundation Certified System Administrator (LFCS) as of September 2021.

Accounts on multi-user operating systems are created and managed locally by default. For example, a desktop system shared by a family may have multiple accounts configured on that system, each with their own files, resources and permissions etc. If the family later decides to purchase a new computer, there is no direct way to reuse the same accounts on the new system. The new system could be manually configured with the same accounts along with their corresponding permissions, but in general the "same" accounts on both systems are not associated with each other and there is no direct way to ensure that they remain consistent across both systems.

While local accounts may be sufficient for home use and perhaps for small businesses comprising no more than a dozen employees (for example), this approach does not scale well to medium and large enterprises. If each employee is granted their own computing device with their own local account on that device, it would be infeasible to enforce company-wide access controls on various company resources such as shared documents. On the other hand, if computing devices are managed centrally and employees are to be able to log in to their own account on any such company-owned device, it would be effectively impossible to maintain a consistent set of local accounts and corresponding permissions on each device. Therefore, enterprises tend to employ centralized authentication for their systems.

What is centralized authentication?

Centralized authentication refers to the management of a single set of accounts on a logically centralized server through which end systems authenticate against. This enables authentication and access control to be performed in a consistent manner across different end systems as long as they are connected to the same logical server. In reality, due to load and performance requirements, the single logical authentication server may actually consist of multiple physical servers, possibly hidden behind a load balancer, that are kept in sync.

Lightweight Directory Access Protocol (LDAP)

LDAP is an open protocol that follows a client-server architecture. It provides a means of storing and manipulating data as a tree of entries known as a Directory Information Tree (DIT), and thus can be treated as a form of NoSQL database, though LDAP itself predates the term NoSQL. However, the most common use of LDAP is to provide a means of centralized authentication and access control, by storing information such as user accounts and groups within the DIT.

Thus far, we have not defined the terms DIT and entry. Below are some of the key concepts within LDAP:

  • Directory server: an LDAP server
  • DIT: the tree of entries stored within a directory server
  • Entry: a node within the DIT consisting of one or more attributes. An entry is analogous to an object in OOP languages
  • Object class: a collection of required (MUST) and optional (MAY) attributes. Object classes are further subdivided into STRUCTURAL and AUXILIARY classes, and they may inherit from each other. Each entry must be an instance of exactly one STRUCTURAL class and zero or more AUXILIARY classes. In this sense, a STRUCTURAL class is analogous to a class in OOP languages, and an AUXILIARY class is analogous to a trait in OOP languages that support them
  • Attribute: a key-value pair, analogous to object properties (or instance variables, etc.) in an OOP language
  • Distinguished name (DN): an identifier that uniquely identifies the given entry and its position within the DIT. Analogous to absolute path in filesystems, except the most specific component of the DN appears to the left, unlike filesystem paths where the most specific component appears to the right
  • Relative DN (RDN): an identifier that uniquely identifies an entry under its immediate parent entry. Also the leftmost component of an DN. Analogous to relative path in filesystems

For a more detailed explanation of these concepts, one may consult an excellent article written by DigitalOcean. Alternatively, to those willing to learn the full specification inside-out, look no further than the official LDAP webpage.

LDAP entry: a simple example

To better understand these concepts, let's look at an example. Suppose we have an organization "DSL Tech Ltd" with a domain name of dsl-tech.org. Under the organization, we have people (employees) and groups (departments), which are represented under LDAP as organizational units. We also have the following people:

  • Alice Walker
  • Bob Ross
  • Carol Pettifar

And the following groups:

  • infotech
  • management

An entry for Bob Ross could be as below, written in LDAP Data Interchange Format (LDIF). LDIF is a human-readable textual representation of LDAP data that can also be imported to or exported from directory servers.

dn: uid=bob,ou=people,dc=dsl-tech,dc=org
objectClass: inetOrgPerson
cn: Bob Ross
sn: Ross
uid: bob
mobile: 12345678
mail: bob@dsl-tech.org
Enter fullscreen mode Exit fullscreen mode

Let's break this down:

  • The DN of this entry is uid=bob,ou=people,dc=dsl-tech,dc=org. Its RDN is uid=bob and its immediate parent entry has DN ou=people,dc=dsl-tech,dc=org. The base DN, i.e. the entry representing the organization, is dc=dsl-tech,dc=org
  • The object class of this entry is inetOrgPerson. This is a structural object class, and entries of this class contain general information about a person such as his / her name and contact info
  • The entry has attributes cn (for canonical name), sn (for surname), uid (for user ID), mobile and mail with respective values Bob Ross, Ross, bob, 12345678 and bob@dsl-tech.org. Note that the uid is used in the RDN (and DN) of this entry; thus, the uid attribute is unique among entries directly under the entry with DN ou=people,dc=dsl-tech,dc=org

Two things to notice:

  • We said the base DN is dc=dsl-tech,dc=org and that the DN is constructed from unique attributes in the entry and its ancestors. Here, we see that the dc attribute has two values, dsl-tech and org. In general, attributes can have multiple values unless otherwise specified
  • While the DN is constructed from unique attributes in the entry and its ancestors, the DN itself is not an attribute of the entry!

Before we move on to the hands-on section on OpenLDAP, allow me to introduce the LDAP wiki which contains a wealth of information about LDAP entities such as the definitions of various object classes. For example, here is the page for the inetOrgPerson object class.

Hands-on section: OpenLDAP server and client on Ubuntu 20.04 LTS

LDAP itself is just a protocol, it has many implementations, including but not limited to:

  • Active Directory, a proprietary implementation by Microsoft often used in Windows-only environments or mixed Windows-Linux environments
  • Apple Directory, an implementation by Apple
  • OpenLDAP, a leading open-source LDAP implementation

In this hands-on section, we will be using OpenLDAP for both server and client on Ubuntu 20.04 LTS.

Preparation and remarks

Two virtual machines or cloud instances running Ubuntu 20.04 LTS server; you may also use physical machines if you wish. Note that Docker containers and Windows Subsystem for Linux will not make the cut.

Allocate at least 512 MB of RAM and 8 GB of disk space (1+ GB RAM, 16+ GB disk space recommended) for each virtual machine or cloud instance. If you are using Ubuntu 20.04 LTS desktop, you may need to increase the RAM allocation (and possibly the disk allocation) accordingly. Before proceeding with the hands-on section, ensure that both VMs can see each other through the network. A good way to do this is to determine their private (if in the same subnet) or public (if in different subnets) IP addresses and see if they can ping each other.

You can try to follow the hands-on section using your Linux distribution of choice if you prefer not to use Ubuntu; however, you may have to adapt the instructions accordingly due to differences in initial configuration, file locations, etc. If you decide to use OpenLDAP on another operating system entirely (such as macOS or Windows), you're on your own ;-)

Configuring the OpenLDAP server

After you have chosen one VM to act as the OpenLDAP server, install the following packages:

  • slapd: OpenLDAP directory server daemon
  • ldap-utils: Utility programs for interacting with the directory server such as ldapadd (add LDAP entries), ldapmodify (modify LDAP entries) and ldapdelete (remove LDAP entries)
$ sudo apt update && sudo apt install slapd ldap-utils
Enter fullscreen mode Exit fullscreen mode

You will be prompted to create a password for the LDAP administrator.

Check that slapd is running:

$ systemctl status slapd
Enter fullscreen mode Exit fullscreen mode

(press q to exit)

Let's see what port slapd binds to (nmap is used for port scanning):

$ sudo apt update && sudo apt install nmap
$ nmap localhost | grep ldap
389/tcp open  ldap
Enter fullscreen mode Exit fullscreen mode

As can be seen from the nmap output, slapd binds to TCP port 389 which is the default port number for LDAP. Note that LDAP is unencrypted by default - in a real production setting, you may wish to use LDAPS (LDAP over SSL/TLS) instead which defaults to TCP port 636. However, for the purposes of this section, we will not cover LDAPS.

We can view the DIT in LDIF format using slapcat:

$ sudo slapcat
dn: dc=nodomain
objectClass: top
objectClass: dcObject
objectClass: organization
o: nodomain
dc: nodomain
structuralObjectClass: organization
entryUUID: a816fb36-a268-103b-9c6c-5dff1f88b6d1
creatorsName: cn=admin,dc=nodomain
createTimestamp: 20210905074308Z
entryCSN: 20210905074308.130478Z#000000#000#000000
modifiersName: cn=admin,dc=nodomain
modifyTimestamp: 20210905074308Z

dn: cn=admin,dc=nodomain
objectClass: simpleSecurityObject
objectClass: organizationalRole
cn: admin
description: LDAP administrator
userPassword:: e1NTSEF9TUplZVhUUW1FRjdzbHpoNXN3UDJMZFVnUk42OGgzVzU=
structuralObjectClass: organizationalRole
entryUUID: a81888d4-a268-103b-9c6d-5dff1f88b6d1
creatorsName: cn=admin,dc=nodomain
createTimestamp: 20210905074308Z
entryCSN: 20210905074308.140725Z#000000#000#000000
modifiersName: cn=admin,dc=nodomain
modifyTimestamp: 20210905074308Z

Enter fullscreen mode Exit fullscreen mode

(learn about the organization and organizationalRole object classes)

This is the output I get. My base DN is dc=nodomain but yours may differ depending on hostname. You can also change the base DN by running

$ sudo dpkg-reconfigure slapd
Enter fullscreen mode Exit fullscreen mode

and specifying the domain name to use. For example, if you specified a domain name of dsl-edu.org, your base DN would be dc=dsl-edu,dc=org.

Here we have the organization dc=nodomain and the LDAP administrator cn=admin,dc=nodomain, but we do not have any other data yet. Let's change that.

Our organization does not yet have any organizational units. Let's add two: people and groups. Create a file units.ldif using your favorite text editor of choice. Mine is Vim, but feel free to use Emacs, Nano or whatever:

$ vim units.ldif
Enter fullscreen mode Exit fullscreen mode

Fill it will the following contents (edit the base DN accordingly to match yours):

dn: ou=people,dc=nodomain
objectClass: organizationalUnit
ou: people

dn: ou=groups,dc=nodomain
objectClass: organizationalUnit
ou: groups
Enter fullscreen mode Exit fullscreen mode

This is also a good time to read up on the organizationalUnit object class.

Now import the contents of the LDIF file using ldapadd (again, edit the base DN accordingly), entering the LDAP administrator password when prompted:

$ sudo ldapadd -x -D cn=admin,dc=nodomain -W -f units.ldif
Enter LDAP Password: 
adding new entry "ou=people,dc=nodomain"

adding new entry "ou=groups,dc=nodomain"

Enter fullscreen mode Exit fullscreen mode

View the updated DIT to confirm the changes:

$ sudo slapcat
dn: dc=nodomain
objectClass: top
objectClass: dcObject
objectClass: organization
o: nodomain
dc: nodomain
structuralObjectClass: organization
entryUUID: a816fb36-a268-103b-9c6c-5dff1f88b6d1
creatorsName: cn=admin,dc=nodomain
createTimestamp: 20210905074308Z
entryCSN: 20210905074308.130478Z#000000#000#000000
modifiersName: cn=admin,dc=nodomain
modifyTimestamp: 20210905074308Z

dn: cn=admin,dc=nodomain
objectClass: simpleSecurityObject
objectClass: organizationalRole
cn: admin
description: LDAP administrator
userPassword:: e1NTSEF9TUplZVhUUW1FRjdzbHpoNXN3UDJMZFVnUk42OGgzVzU=
structuralObjectClass: organizationalRole
entryUUID: a81888d4-a268-103b-9c6d-5dff1f88b6d1
creatorsName: cn=admin,dc=nodomain
createTimestamp: 20210905074308Z
entryCSN: 20210905074308.140725Z#000000#000#000000
modifiersName: cn=admin,dc=nodomain
modifyTimestamp: 20210905074308Z

dn: ou=people,dc=nodomain
objectClass: organizationalUnit
ou: people
structuralObjectClass: organizationalUnit
entryUUID: b9c27ad2-a26c-103b-93ba-2589af8e1899
creatorsName: cn=admin,dc=nodomain
createTimestamp: 20210905081215Z
entryCSN: 20210905081215.762597Z#000000#000#000000
modifiersName: cn=admin,dc=nodomain
modifyTimestamp: 20210905081215Z

dn: ou=groups,dc=nodomain
objectClass: organizationalUnit
ou: groups
structuralObjectClass: organizationalUnit
entryUUID: b9c66d54-a26c-103b-93bb-2589af8e1899
creatorsName: cn=admin,dc=nodomain
createTimestamp: 20210905081215Z
entryCSN: 20210905081215.788534Z#000000#000#000000
modifiersName: cn=admin,dc=nodomain
modifyTimestamp: 20210905081215Z

Enter fullscreen mode Exit fullscreen mode

Now suppose our organization is a school with the following people:

  • Jack Doe (teacher)
  • Jane Doe (student)
  • John Doe (student)

Create another LDIF file users.ldif containing our user information:

$ vim users.ldif
Enter fullscreen mode Exit fullscreen mode

With the following content:

dn: uid=jack,ou=people,dc=nodomain
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
cn: Jack Doe
sn: Doe
userPassword: {SSHA}rXEx7lMQI7saMOrr8ws/otQljj6UIQbx
loginShell: /bin/bash
uidNumber: 2000
gidNumber: 2000
homeDirectory: /home/jack

dn: uid=jane,ou=people,dc=nodomain
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
cn: Jane Doe
sn: Doe
userPassword: {SSHA}8ae2oxWy6vLhi9ucsBWIH1OnsAsQP57h
loginShell: /bin/bash
uidNumber: 2001
gidNumber: 2001
homeDirectory: /home/jane

dn: uid=john,ou=people,dc=nodomain
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
cn: John Doe
sn: Doe
userPassword: {SSHA}Xz3CS+RH7pU/EPlTA0ppPLtz/xeCddG2
loginShell: /bin/bash
uidNumber: 2002
gidNumber: 2002
homeDirectory: /home/john
Enter fullscreen mode Exit fullscreen mode

(inetOrgPerson, posixAccount, shadowAccount)

Here, the userPassword attributes are generated from slappasswd based on the entered password. For example, here's what I get when entering jackP@ssw0rd:

$ slappasswd
New password:
Re-enter new password:
{SSHA}p4ijcrTUzF466xEqyOmjchZOPMFCuBKD
Enter fullscreen mode Exit fullscreen mode

For simplicity, I set the passwords of the three users as jackP@ssw0rd, janeP@ssw0rd and johnP@ssw0rd respectively, but feel free to change them to your liking, using slappasswd to convert them to the correct format.

Note also the following:

  • The loginShell attribute is optional. If omitted, the default login shell for useradd as specified in /etc/default/useradd is used (/bin/sh in Ubuntu):
  $ grep SHELL= /etc/default/useradd
  SHELL=/bin/sh
Enter fullscreen mode Exit fullscreen mode
  • We assign Jack, Jane and John UIDs and GIDs of 2000, 2001 and 2002 respectively. This assumes user accounts have UIDs starting from 1000 (inclusive) as specified in /etc/login.defs and we won't have anywhere near 1000 local users accounts beforehand so assigning UIDs for LDAP accounts from 2000 upwards should not conflict with local accounts

Now import the LDIF file:

$ sudo ldapadd -x -D cn=admin,dc=nodomain -W -f users.ldif
Enter LDAP Password: 
adding new entry "uid=jack,ou=people,dc=nodomain"

adding new entry "uid=jane,ou=people,dc=nodomain"

adding new entry "uid=john,ou=people,dc=nodomain"

Enter fullscreen mode Exit fullscreen mode

The paranoid administrator can confirm the changes again using slapcat.

Now add the teachers and students groups (with GIDs of 10000 and 10001 respectively, to avoid clashing with those of private groups belonging to local and LDAP user accounts), such that Jack is a teacher and Jane, John are students. We also add private groups for each LDAP user:

$ vim groups.ldif
Enter fullscreen mode Exit fullscreen mode

With the following content:

dn: cn=jack,ou=groups,dc=nodomain
objectClass: posixGroup
cn: jack
gidNumber: 2000
memberUid: jack

dn: cn=jane,ou=groups,dc=nodomain
objectClass: posixGroup
cn: jane
gidNumber: 2001
memberUid: jane

dn: cn=john,ou=groups,dc=nodomain
objectClass: posixGroup
cn: john
gidNumber: 2002
memberUid: john

dn: cn=teachers,ou=groups,dc=nodomain
objectClass: posixGroup
cn: teachers
gidNumber: 10000
memberUid: jack

dn: cn=students,ou=groups,dc=nodomain
objectClass: posixGroup
cn: students
gidNumber: 10001
memberUid: jane
memberUid: john
Enter fullscreen mode Exit fullscreen mode

(posixGroup object class)

Remember that attributes can be multi-valued unless otherwise specified. Here the memberUid attribute of the students group has two values, jane and john.

Import it into the DIT:

$ sudo ldapadd -x -D cn=admin,dc=nodomain -W -f groups.ldif
Enter LDAP Password: 
adding new entry "cn=jack,ou=groups,dc=nodomain"

adding new entry "cn=jane,ou=groups,dc=nodomain"

adding new entry "cn=john,ou=groups,dc=nodomain"

adding new entry "cn=teachers,ou=groups,dc=nodomain"

adding new entry "cn=students,ou=groups,dc=nodomain"

Enter fullscreen mode Exit fullscreen mode

Now we're ready to move on to the next VM and configure the OpenLDAP client. Make sure to keep your directory server running.

Configuring the OpenLDAP client

In the other VM, install the libpam-ldap package which provides the pam_ldap.so module for PAM authentication. For the purposes of this section, knowing the following about PAM should suffice:

  • PAM stands for Pluggable Authentication Modules
  • PAM separates privilege-granting software (such as login and su) from the authentication mechanism(s) used by such software (e.g. simple trust, password authentication, fingerprint recognition) and allows the administrator to (re)configure the authentication mechanism(s) used without recompiling and reinstalling related software. It does this through the use of modules

However, the interested reader is encouraged to read through the System Administrator's Guide to PAM and skim through the references for commonly used PAM modules.

$ sudo apt update && sudo apt install libpam-ldap
Enter fullscreen mode Exit fullscreen mode

During the installation of the LDAP module, you will be prompted for the following items:

  • LDAP server URI: use ldap://192.168.122.45, replacing 192.168.122.45 with the IP address of your directory server. Also note the protocol used is ldap://, not ldapi://
  • DN of the search base: use the base DN in your directory server. Mine is dc=nodomain, but yours may be different
  • LDAP version: 3
  • Make local root database admin: no. We do not want a compromised end system to be able to perform arbitrary changes to our directory server
  • Does the LDAP database require login: no

Edit /etc/nsswitch.conf such that the passwd and group entries include ldap (man 5 nsswitch.conf):

$ sudo vim /etc/nsswitch.conf
Enter fullscreen mode Exit fullscreen mode

Alternatively, you may use sed, awk, etc. if you're proficient with them to avoid firing up a text editor.

$ grep -e passwd: -e group: /etc/nsswitch.conf | grep ldap
passwd:         files systemd ldap
group:          files systemd ldap
Enter fullscreen mode Exit fullscreen mode

Finally, run the following command to append the line session optional pam_mkhomedir.so to /etc/pam.d/common-session:

$ sudo bash -c "echo \"session optional pam_mkhomedir.so\" >> /etc/pam.d/common-session"
Enter fullscreen mode Exit fullscreen mode

This ensures that the home directory of LDAP users are created if they do not already exist.

Now let's try logging in as Jack:

$ su - jack
Password: 
Creating directory '/home/jack'.
$ whoami
jack
$ echo $HOME
/home/jack
$ groups
jack teachers
$ exit
logout
Enter fullscreen mode Exit fullscreen mode

Similarly for Jane:

$ su - jane
Password: 
Creating directory '/home/jane'.
$ whoami
jane
$ echo $HOME
/home/jane
$ groups
jane students
$ exit
logout
Enter fullscreen mode Exit fullscreen mode

And John:

$ su - john
Password: 
Creating directory '/home/john'.
$ whoami
john
$ echo $HOME
/home/john
$ groups
john students
$ exit
logout
Enter fullscreen mode Exit fullscreen mode

This concludes our hands-on section, though you are encouraged to explore further possibilities:

  • Configure a second client and a third, fourth, etc., to authenticate against your directory server
  • Try to add your own LDAP users and groups apart from those provided in this section. What happens if you change the UID and GID numbers? What if you don't create a private group for each corresponding LDAP user?
  • We haven't explored much on access controls yet. Can you configure some shared resource only accessible to a subset of the LDAP users you defined?

Conclusion

Hopefully you found this hands-on introduction to LDAP helpful in understanding the basics of LDAP and how it works. Feel free to leave your comments below. Interested readers may wish to tinker around with their OpenLDAP server to explore further possibilities - perhaps one can mount an NFS share that grants permissions to particular LDAP users and / or groups only?

References

  1. Official LDAP website: https://ldap.com/
  2. An explanation of LDAP from DigitalOcean: https://www.digitalocean.com/community/tutorials/understanding-the-ldap-protocol-data-hierarchy-and-entry-components
  3. LDAP wiki: https://ldapwiki.com
  4. Official OpenLDAP website: https://www.openldap.org/
  5. OpenLDAP tutorial by ComputingForGeeks which the hands-on section is based on: https://computingforgeeks.com/install-and-configure-openldap-server-ubuntu/
  6. OpenLDAP client configuration (ComputingForGeeks): https://computingforgeeks.com/how-to-configure-ubuntu-as-ldap-client/
  7. A system administrator's guide to PAM: http://www.linux-pam.org/Linux-PAM-html/Linux-PAM_SAG.html
  8. man 5 nsswitch.conf: https://man7.org/linux/man-pages/man5/nsswitch.conf.5.html

Top comments (0)

🌚 Life is too short to browse without dark mode