How to setup a standalone OpenLDAP service on Ubuntu 22.04

How to setup a standalone OpenLDAP service on Ubuntu 22.04

openldap_setup_ubuntu

Here in this article we will try to setup standalone LDAP service using OpenLDAP which is an OpenSource package. We will try to build, configure and install the OpenLDAP service on Ubuntu OS. We will try to setup a LDAP directory structure based on DNS naming system using the tools provided by OpenLDAP package.

Test Environment

Ubuntu 22.04

What is OpenLDAP

OpenLDAP is a specialized directory that provides the following services (ie. searching, browsing, lookup, update). It contains attribute based information in DIT tree. They do not support complex RDMS transactions and rollbacks. Directory updates are simple all or nothing change. They are tuned to provide quick response to high volume lookup or search operations. They can replicate information to other servers to provide high availability and reliability. LDAP runs over tcp/ip or other connection oriented transfer services which makes it lightweight. OpenLDAP uses an embedded key/value store like LMDB.

OpenLDAP Configuration choices

  • Local directory service
  • Local directory service with referrals
  • Replicated directory service (refreshOnly or refreshAndPersist) mode
  • Distributed local directory service

SLAPD implementation features

  • Supports ldap over ipv4, ipv6 and unix ipc
  • Supports authentication and data security using simple authentication and security layer implemented by Cyrus SASL
  • Supports cert based authentication and data security using TLS implemented by OpenSSL or GnuTLS
  • Supports access restriction at socket layer based on network topology information
  • Supports access control to entries based ldap authorization info, ip address, domain name or other criteria
  • Can be configured to serve multiple databases at the same time
  • Consists of a front end that handles protocol communication with LDAP clients and modules which handle specific tasks such as database operations
  • Frontend and Backend modules communication happen using C API which make modules customizable
  • Single multi-threaded slapd process handles all incoming requests using a pool of threads
  • Replication services using single-provider/multiple-consumer replication scheme for high-volume and distributed environments
  • Can be configured as a caching LDAP proxy service
  • It is highly configurable through a single configuration file

For more details consult OpenLDAP documentation.

If you are interested in watching the video. Here is the YouTube video on the same step by step procedure outlined below.

Procedure

Step1: Pre-requisite Package installation

As a first we are going to install the gcc complier and make tools which are the required tools for building and installing OpenLDAP package.

ubadmin@ubscratch:~$ sudo apt-get install gcc
ubadmin@ubscratch:~$ sudo apt-get install make

Step2: Download and Extract OpenLDAP Package

In this step we are going to download the OpenLDAP source package from OpenLDAP Download page – https://www.openldap.org/software/download/ and extract its content using the tar utility as shown below.

ubadmin@ubscratch:~$ wget https://www.openldap.org/software/download/OpenLDAP/openldap-release/openldap-2.6.7.tgz
ubadmin@ubscratch:~$ tar -xzvf openldap-2.6.7.tgz

Step3: Configure OpenLDAP Software Package

Now, let’s switch to the extracted folder and configure the OpenLDAP software. The configure script allows us to provide different options to enable or disable a particular feature in OpenLDAP. Many of the features of OpenLDAP can be enabled or disabled using this method.

ubadmin@ubscratch:~$ cd openldap-2.6.7
ubadmin@ubscratch:~/openldap-2.6.7$ ./configure

Once you have run the configure script the last line of output should be “Please “make depend” to build dependencies”. If the last line of output does not match, configure has failed, and you will need to review its output to determine what went wrong.

Step4: Build Software Package

Once the configure step is completed successfully its time to compile the dependencies used by OpenLDAP and once that is done succesfully we can build the OpenLDAP pacakge.

ubadmin@ubscratch:~/openldap-2.6.7$ make depend
ubadmin@ubscratch:~/openldap-2.6.7$ make

Step5: Test Build Package

In this step there are test cases executed against the build package. The complete list of test cases scripts are available at the following location. Tests which apply to your configuration will run and they should pass.

ubadmin@ubscratch:~/openldap-2.6.7$ ls -ltr tests/scripts/

Now, its time to execute the test cases against our standalone LDAP service as shown below.

ubadmin@ubscratch:~/openldap-2.6.7$ make test

Step6: Install OpenLDAP Package

Once’s the test cases have been executed successfully we can install the OpenLDAP package as shown below.

ubadmin@ubscratch:~/openldap-2.6.7$ sudo make install

Now we are ready with the OpenLDAP pacakge installed at the following default location “/usr/local”.

Step7: Update default database configuration file

Here in this step we are going to use the default “/usr/local/etc/openldap/slapd.ldif” LDAP configuration file provided by the OpenLDAP package and update it as per our requirement. The slapd configuration is stored as a special LDAP directory with a predefined schema and DIT.

We are updating only the section related to “LMDB database definitions”. The directory tree that we are trying to establish with this configuration is for “stack.com” domain. For more details on the configuration details, please refer “Configuring LDAP“.

ubadmin@ubscratch:~/openldap-2.6.7$ sudo cat /usr/local/etc/openldap/slapd.ldif
#
# See slapd-config(5) for details on configuration options.
# This file should NOT be world readable.
#
dn: cn=config
objectClass: olcGlobal
cn: config
#
#
# Define global ACLs to disable default read access.
#
olcArgsFile: /usr/local/var/run/slapd.args
olcPidFile: /usr/local/var/run/slapd.pid
#
# Do not enable referrals until AFTER you have a working directory
# service AND an understanding of referrals.
#olcReferral:	ldap://root.openldap.org
#
# Sample security restrictions
#	Require integrity protection (prevent hijacking)
#	Require 112-bit (3DES or better) encryption for updates
#	Require 64-bit encryption for simple bind
#olcSecurity: ssf=1 update_ssf=112 simple_bind=64


#
# Load dynamic backend modules:
#
dn: cn=module,cn=config
objectClass: olcModuleList
cn: module
olcModulepath:	/usr/local/libexec/openldap
olcModuleload:	back_mdb.la
#olcModuleload:	back_ldap.la
#olcModuleload:	back_passwd.la

dn: cn=schema,cn=config
objectClass: olcSchemaConfig
cn: schema

include: file:///usr/local/etc/openldap/schema/core.ldif

# Frontend settings
#
dn: olcDatabase=frontend,cn=config
objectClass: olcDatabaseConfig
objectClass: olcFrontendConfig
olcDatabase: frontend
olcAccess: to * by * read
#
# Sample global access control policy:
#	Root DSE: allow anyone to read it
#	Subschema (sub)entry DSE: allow anyone to read it
#	Other DSEs:
#		Allow self write access
#		Allow authenticated users read access
#		Allow anonymous users to authenticate
#
#olcAccess: to dn.base="" by * read
#olcAccess: to dn.base="cn=Subschema" by * read
#olcAccess: to *
#	by self write
#	by users read
#	by anonymous auth
#
# if no access controls are present, the default policy
# allows anyone and everyone to read anything but restricts
# updates to rootdn.  (e.g., "access to * by * read")
#
# rootdn can always read and write EVERYTHING!
#


#######################################################################
# LMDB database definitions
#######################################################################
#
#dn: olcDatabase=mdb,cn=config
#objectClass: olcDatabaseConfig
#objectClass: olcMdbConfig
#olcDatabase: mdb
#olcDbMaxSize: 1073741824
#olcSuffix: dc=my-domain,dc=com
#olcRootDN: cn=Manager,dc=my-domain,dc=com
# Cleartext passwords, especially for the rootdn, should
# be avoided.  See slappasswd(8) and slapd-config(5) for details.
# Use of strong authentication encouraged.
#olcRootPW: secret
# The database directory MUST exist prior to running slapd AND 
# should only be accessible by the slapd and slap tools.
# Mode 700 recommended.
#olcDbDirectory:	/usr/local/var/openldap-data
# Indices to maintain
#olcDbIndex: objectClass eq

dn: olcDatabase=mdb,cn=config
objectClass: olcDatabaseConfig
objectClass: olcMdbConfig
olcDatabase: mdb
OlcDbMaxSize: 1073741824
olcSuffix: dc=stack,dc=com
olcRootDN: cn=Manager,dc=stack,dc=com
olcRootPW: secret
olcDbDirectory: /usr/local/var/openldap-data
olcDbIndex: objectClass eq
olcDbIndex: uid pres,eq
olcDbIndex: cn,sn pres,eq,approx,sub
olcAccess: to attrs=userPassword
  by self write
  by anonymous auth
  by dn.base="cn=Admin,dc=stack,dc=com" write
  by * none
olcAccess: to *
  by self write
  by dn.base="cn=Admin,dc=example,dc=com" write
  by * read

dn: olcDatabase=monitor,cn=config
objectClass: olcDatabaseConfig
olcDatabase: monitor
olcRootDN: cn=config
olcMonitoring: FALSE

The database directory MUST exist prior to running slapd AND should only be accessible by the slapd and slap tools.

ubadmin@ubscratch:~/openldap-2.6.7$ sudo mkdir -p /usr/local/var/openldap-data

Step8: Import the configuration database

Ensure the following configuration directory exists.

ubadmin@ubscratch:~/openldap-2.6.7 mkdir -p /usr/local/etc/slapd.d

Now, let’s try to import the slapd configuration using slapadd utility as shown below.

ubadmin@ubscratch:~/openldap-2.6.7$ sudo /usr/local/sbin/slapadd -n 0 -F /usr/local/etc/slapd.d -l /usr/local/etc/openldap/slapd.ldif
Closing DB...

Options

-l: ldif file location
-n: Add entries to the dbnum-th database listed in the configuration file
-F: specify a config directory

If the import is successful, you should be able to see the following LDIF configuration database structure created as shown below.

ubadmin@ubscratch:~/openldap-2.6.7$ sudo ls -ltr /usr/local/etc/slapd.d/cn\=config
total 28
-rw------- 1 root root  472 Apr 15 13:27 'olcDatabase={-1}frontend.ldif'
-rw------- 1 root root  627 Apr 15 13:27 'olcDatabase={0}config.ldif'
-rw------- 1 root root  378 Apr 15 13:27 'cn=schema.ldif'
drwxr-x--- 2 root root 4096 Apr 15 13:27 'cn=schema'
-rw------- 1 root root  453 Apr 15 13:27 'cn=module{0}.ldif'
-rw------- 1 root root  450 Apr 15 13:27 'olcDatabase={2}monitor.ldif'
-rw------- 1 root root  880 Apr 15 13:27 'olcDatabase={1}mdb.ldif'

The root of the tree is named cn=config and contains global configuration settings. Additional settings are contained in separate child entries.

  • Root directory configuration (cn=config) – objectClass: olcGlobal
  • Dynamically loaded modules (cn=module) – objectClass: olcModuleList
  • Schema definitions (cn=schema) – objectClass: olcSchemaConfig
  • Backend-specific configuration – objectClass: olcBackendConfig
  • Database-specific configuration – objectClass: olcDatabaseConfig

Step9: Start LDAP daemon

Now its time to start the LDAP service using the configuration available at “/usr/local/etc/slapd.d” as shown below.

ubadmin@ubscratch:~/openldap-2.6.7$ sudo /usr/local/libexec/slapd -F /usr/local/etc/slapd.d

Step10: Validate slapd configuration and LDAP server

We can validate the LDAP configuration database using slaptest as shown below.

ubadmin@ubscratch:~/openldap-2.6.7$ sudo slaptest -F /usr/local/etc/slapd.d/
config file testing succeeded

Once the test is successful, you can try to query the LDAP directory service using ldapsearch utility as shown below.

ubadmin@ubscratch:~/openldap-2.6.7$ ldapsearch -x -b '' -s base '(objectclass=*)' namingContexts
# extended LDIF
#
# LDAPv3
# base <> with scope baseObject
# filter: (objectclass=*)
# requesting: namingContexts 
#

#
dn:
namingContexts: dc=stack,dc=com

# search result
search: 2
result: 0 Success

# numResponses: 2
# numEntries: 1

Options

-x: Use simple authentication instead of SASL
-b: Use searchbase as the starting point for the search instead of the default
-s: Specify  the scope of the search to be one of base, one, sub, or children

Step11: Create LDIF database entries file

In this step we are going to create an LDIF database file consisting on entries that we want to add to the LDAP directory tree.

We are adding entries related to following objectclass (ie. organization, organizationalUnit, organizationalRole) as shown below.

ubadmin@ubscratch:~/openldap-2.6.7$ cat ~/ldapfiles/stack.com.ldif 
dn: dc=stack,dc=com
objectclass: dcObject
objectclass: organization
o: stack company
dc: stack

dn: cn=Manager,dc=stack,dc=com
objectclass: organizationalRole
cn: Manager

dn: cn=Admin,dc=stack,dc=com
objectclass: organizationalRole
cn: Admin

dn: ou=devops,dc=stack,dc=com
objectclass: organizationalUnit
ou: devops

dn: cn=mark,ou=devops,dc=stack,dc=com
objectclass: person
cn: mark
sn: m
userPassword: mark@1234

dn: cn=bob,ou=devops,dc=stack,dc=com
objectclass: person
cn: bob
sn: b
userPassword: bob@1234

dn: cn=alice,ou=devops,dc=stack,dc=com
objectclass: person
cn: alice
sn: a
userPassword: alice@1234

NOTE: As per the mdb database configuration file userPassword is a protected attribute which has restrictive access as defined in the olcaccess directive.

Step12: Add entries to LDAP directory

Now we will try to add those entried defined in the LDIF file using the ldapadd utility as shown below.

ubadmin@ubscratch:~/openldap-2.6.7$ sudo ldapadd -x -D "cn=Manager,dc=stack,dc=com" -W -f ~/ldapfiles/stack.com.ldif 
Enter LDAP Password: 
adding new entry "dc=stack,dc=com"

adding new entry "cn=Manager,dc=stack,dc=com"

adding new entry "cn=Admin,dc=stack,dc=com"

adding new entry "ou=devops,dc=stack,dc=com"

adding new entry "cn=mark,ou=devops,dc=stack,dc=com"

adding new entry "cn=bob,ou=devops,dc=stack,dc=com"

adding new entry "cn=alice,ou=devops,dc=stack,dc=com"

Step13: Search LDAP entries

Let’s now try to search for an entry with “cn=bob” using the bindDN “cn=bob,ou=devops,dc=stack,dc=com”. As we are trying authentication using the bob credentials using bindDN and bob is the owner of his own entry we are able to look at the protected field “userPassword”.

Simple authentication consists of sending the LDAP server the fully qualified DN of the client (user) and the client’s clear-text password (see RFC 2251 and RFC 2829). This mechanism has security problems because the password can be read from the network.

ubadmin@ubscratch:~/openldap-2.6.7$ ldapsearch -x -b 'dc=stack,dc=com' -D 'cn=bob,ou=devops,dc=stack,dc=com' '(cn=bob)' -W
Enter LDAP Password: 
# extended LDIF
#
# LDAPv3
# base <dc=stack,dc=com> with scope subtree
# filter: (cn=bob)
# requesting: ALL
#

# bob, devops, stack.com
dn: cn=bob,ou=devops,dc=stack,dc=com
objectClass: person
cn: bob
sn: b
userPassword:: Ym9iQDEyMzQ=

# search result
search: 2
result: 0 Success

# numResponses: 2
# numEntries: 1

Also note, OpenLDAP tools like ldapsearch and slapcat display userPassword in base64-encoded format, a format designed to represent binary values as text. A double colon after the attribute name indicates that the value is base64-encoded. We can decode the password in base64 encoded format as shown below.

ubadmin@ubscratch:~/openldap-2.6.7$ echo "Ym9iQDEyMzQ=" | base64 -d
bob@1234

Now let’s try to search to search for an entry with “cn=alice” using the bindDN “cn=bob,ou=devops,dc=stack,dc=com”. As bob is not the owner of the entry “cn=alice”, he is not able to retrieve the protected attribute “userPassword” for alcie as shown in the output below.

This is how we can ensure that the protected attributes are accessible by only authorized identities in LDAP.

ubadmin@ubscratch:~/openldap-2.6.7$ ldapsearch -x -b 'dc=stack,dc=com' -D 'cn=bob,ou=devops,dc=stack,dc=com' '(cn=alice)' -W
Enter LDAP Password: 
# extended LDIF
#
# LDAPv3
# base <dc=stack,dc=com> with scope subtree
# filter: (cn=alice)
# requesting: ALL
#

# alice, devops, stack.com
dn: cn=alice,ou=devops,dc=stack,dc=com
objectClass: person
cn: alice
sn: a

# search result
search: 2
result: 0 Success

# numResponses: 2
# numEntries: 1

Now let’s try to search the LDAP directory tree for all entires related to objectClass=person using the bindDN of bob user. As you can see from the output bob is able to retrieve all the entries related to objectClass=person from the directory tree but he can only see his own entries protected attribute (ie. userPassword).

ubadmin@ubscratch:~/openldap-2.6.7$ ldapsearch -x -b 'dc=stack,dc=com' -D 'cn=bob,ou=devops,dc=stack,dc=com' '(objectClass=person)' -W
Enter LDAP Password: 
# extended LDIF
#
# LDAPv3
# base <dc=stack,dc=com> with scope subtree
# filter: (objectClass=person)
# requesting: ALL
#

# mark, devops, stack.com
dn: cn=mark,ou=devops,dc=stack,dc=com
objectClass: person
cn: mark
sn: m

# bob, devops, stack.com
dn: cn=bob,ou=devops,dc=stack,dc=com
objectClass: person
cn: bob
sn: b
userPassword:: Ym9iQDEyMzQ=

# alice, devops, stack.com
dn: cn=alice,ou=devops,dc=stack,dc=com
objectClass: person
cn: alice
sn: a

# search result
search: 2
result: 0 Success

# numResponses: 4
# numEntries: 3

Step14: Modify entries

Here in this step we are going to update the userPassword attribute for the entry with DN “cn=bob,ou=devops,dc=stack,dc=com” using LDIF file below.

ubadmin@ubscratch:~/openldap-2.6.7$ cat ~/ldapfiles/bob_update_password.ldif 
dn: cn=bob,ou=devops,dc=stack,dc=com
changetype: modify
replace: userPassword
userPassword: bob@4321

Let’s try to apply the changes to the bob entry attribute using ldapmodify as shown below.

ubadmin@ubscratch:~/openldap-2.6.7$ ldapmodify -x -D 'cn=Manager,dc=stack,dc=com' -W -f ~/ldapfiles/bob_update_password.ldif 
Enter LDAP Password: 
modifying entry "cn=bob,ou=devops,dc=stack,dc=com"

Now lets try to search ldap directory for all the entries using the bob’s updated password.

ubadmin@ubscratch:~/openldap-2.6.7$ ldapsearch -x -b 'dc=stack,dc=com' -D 'cn=bob,ou=devops,dc=stack,dc=com' '(objectClass=person)' -W
Enter LDAP Password: 
# extended LDIF
#
# LDAPv3
# base <dc=stack,dc=com> with scope subtree
# filter: (objectClass=person)
# requesting: ALL
#

# mark, devops, stack.com
dn: cn=mark,ou=devops,dc=stack,dc=com
objectClass: person
cn: mark
sn: m

# bob, devops, stack.com
dn: cn=bob,ou=devops,dc=stack,dc=com
objectClass: person
cn: bob
sn: b
userPassword:: Ym9iQDQzMjE=

# alice, devops, stack.com
dn: cn=alice,ou=devops,dc=stack,dc=com
objectClass: person
cn: alice
sn: a

# search result
search: 2
result: 0 Success

# numResponses: 4
# numEntries: 3

The new password as encoded in the base64 format can be decoded again as shown below.

ubadmin@ubscratch:~/openldap-2.6.7$ echo "Ym9iQDQzMjE=" | base64 -d
bob@4321

Step15: Delete entries

As a last step let’s try to delete an entry from the LDAP directory tree. Here we are going to delete an entry with DN “cn=alice,ou=devops,dc=stack,dc=com” using LDIF file as shown below.

ubadmin@ubscratch:~/openldap-2.6.7$ cat ~/ldapfiles/alice_delete.ldif 
dn: cn=alice,ou=devops,dc=stack,dc=com
changetype: delete

Let’s apply the change using ldapmodify utility as shown below.

ubadmin@ubscratch:~/openldap-2.6.7$ ldapmodify -x -D 'cn=Manager,dc=stack,dc=com' -W -f ~/ldapfiles/alice_delete.ldif
Enter LDAP Password: 
deleting entry "cn=alice,ou=devops,dc=stack,dc=com"

If we now try to search for the entries in the LDAP directory tree we will only have 2 entries instead of 3 as shown below.

ubadmin@ubscratch:~/openldap-2.6.7$ ldapsearch -x -b 'dc=stack,dc=com' -D 'cn=bob,ou=devops,dc=stack,dc=com' '(objectClass=person)' -W
Enter LDAP Password: 
# extended LDIF
#
# LDAPv3
# base <dc=stack,dc=com> with scope subtree
# filter: (objectClass=person)
# requesting: ALL
#

# mark, devops, stack.com
dn: cn=mark,ou=devops,dc=stack,dc=com
objectClass: person
cn: mark
sn: m

# bob, devops, stack.com
dn: cn=bob,ou=devops,dc=stack,dc=com
objectClass: person
cn: bob
sn: b
userPassword:: Ym9iQDQzMjE=

# search result
search: 2
result: 0 Success

# numResponses: 3
# numEntries: 2

So here in this article we tried to attempt to build, configure, test and install OpenLDAP package. Then we utilized the LDAP utilities to prepare our LDAP directory database configuration and have seen how we can manage entries in the directory tree.

Hope you enjoyed reading this article. Thank you..