← Back to Presentation
Practical Work - Directory Services

LDAP & Directory Server Administration

Hands-on exercises with ApacheDS, LDIF files, and directory operations

Duration 3-4 hours
Difficulty Intermediate
Session Advanced Databases - Directory Services

Objectives

By the end of this practical work, you will be able to:

  • Set up and configure an Apache Directory Server instance using Docker
  • Navigate and manage directory entries using Apache Directory Studio
  • Create and import organizational structures using LDIF files
  • Perform LDAP searches using various filters and scopes
  • Manage users, groups, and access control
  • Use command-line tools (ldapsearch, ldapadd, ldapmodify)

Prerequisites

  • Docker and Docker Compose installed on your machine
  • Basic understanding of hierarchical data structures
  • Familiarity with command-line interfaces
  • A text editor for creating LDIF files

Software to Install

Download and install Apache Directory Studio:

Note: Apache Directory Studio requires Java Runtime Environment (JRE) 11 or higher.

Part 1: Setting Up the Environment

Step 1.1: Create Docker Compose Configuration

Create a new directory for this practical work and add the following docker-compose.yml file:

version: '3.8'

services:
  ldap:
    image: osixia/openldap:1.5.0
    container_name: openldap-server
    environment:
      LDAP_ORGANISATION: "Way-Up Training"  # (#1:Organization name)
      LDAP_DOMAIN: "wayup.local"  # (#2:Base domain for DC)
      LDAP_ADMIN_PASSWORD: "admin123"  # (#3:Admin password)
      LDAP_CONFIG_PASSWORD: "config123"
      LDAP_TLS: "false"  # (#4:Disable TLS for lab)
    ports:
      - "389:389"   # (#5:LDAP port)
      - "636:636"   # (#6:LDAPS port)
    volumes:
      - ldap_data:/var/lib/ldap
      - ldap_config:/etc/ldap/slapd.d
    networks:
      - ldap-network

  phpldapadmin:
    image: osixia/phpldapadmin:0.9.0
    container_name: phpldapadmin
    environment:
      PHPLDAPADMIN_LDAP_HOSTS: ldap
      PHPLDAPADMIN_HTTPS: "false"
    ports:
      - "8080:80"  # (#7:Web interface port)
    depends_on:
      - ldap
    networks:
      - ldap-network

volumes:
  ldap_data:
  ldap_config:

networks:
  ldap-network:
    driver: bridge

Step 1.2: Start the LDAP Server

Start the containers:

# Start in detached mode
docker-compose up -d

# Check the containers are running
docker-compose ps

# View logs if needed
docker-compose logs -f ldap

Expected: Two containers should be running: openldap-server and phpldapadmin.

Step 1.3: Verify the Installation

Test the LDAP connection using ldapsearch:

# Search for the root DSE (anonymous bind)
docker exec openldap-server ldapsearch -x -H ldap://localhost -b "" -s base

# Search for the base DN
docker exec openldap-server ldapsearch -x -H ldap://localhost \
    -D "cn=admin,dc=wayup,dc=local" \
    -w admin123 \
    -b "dc=wayup,dc=local"

You can also access phpLDAPadmin at: http://localhost:8080

Login credentials:

  • Login DN: cn=admin,dc=wayup,dc=local
  • Password: admin123

Part 2: Using Apache Directory Studio

Step 2.1: Create a New Connection

Open Apache Directory Studio and create a new LDAP connection:

  1. Go to File → New → LDAP Connection
  2. Enter connection details:
    • Connection name: WayUp Local
    • Hostname: localhost
    • Port: 389
    • Encryption: No encryption
  3. Click Next
  4. Enter authentication:
    • Authentication Method: Simple Authentication
    • Bind DN: cn=admin,dc=wayup,dc=local
    • Bind password: admin123
  5. Click Finish

Step 2.2: Explore the Directory Structure

Once connected, explore the existing structure:

  • Expand the dc=wayup,dc=local node
  • Right-click on entries to view properties
  • Examine the objectClass attributes

Note: The initial directory contains only the admin entry. We'll populate it with organizational units and users next.

Part 3: Creating Organizational Structure with LDIF

Step 3.1: Understanding LDIF Format

LDIF (LDAP Data Interchange Format) is the standard format for importing/exporting LDAP data. Key rules:

  • Each entry starts with its Distinguished Name (DN)
  • Entries are separated by blank lines
  • Attributes use the format: attributeName: value
  • Multi-valued attributes have multiple lines with the same attribute name

Step 3.2: Create Organizational Units

Create a file named 01-organizational-units.ldif:

# Organizational Units for Way-Up Training
# File: 01-organizational-units.ldif

# People OU - contains all user accounts
dn: ou=People,dc=wayup,dc=local
objectClass: organizationalUnit
objectClass: top
ou: People
description: All user accounts

# Groups OU - contains all groups
dn: ou=Groups,dc=wayup,dc=local
objectClass: organizationalUnit
objectClass: top
ou: Groups
description: Security and distribution groups

# Services OU - for service accounts
dn: ou=Services,dc=wayup,dc=local
objectClass: organizationalUnit
objectClass: top
ou: Services
description: Service and application accounts

# Departments under People
dn: ou=Engineering,ou=People,dc=wayup,dc=local
objectClass: organizationalUnit
objectClass: top
ou: Engineering
description: Engineering department

dn: ou=Marketing,ou=People,dc=wayup,dc=local
objectClass: organizationalUnit
objectClass: top
ou: Marketing
description: Marketing department

dn: ou=Finance,ou=People,dc=wayup,dc=local
objectClass: organizationalUnit
objectClass: top
ou: Finance
description: Finance department

Import the LDIF file:

# Copy file to container
docker cp 01-organizational-units.ldif openldap-server:/tmp/

# Import using ldapadd
docker exec openldap-server ldapadd -x \
    -D "cn=admin,dc=wayup,dc=local" \
    -w admin123 \
    -f /tmp/01-organizational-units.ldif

Expected output: "adding new entry" messages for each OU created.

Step 3.3: Create User Accounts

Create a file named 02-users.ldif:

# User Accounts
# File: 02-users.ldif

# Engineering Users
dn: uid=jdoe,ou=Engineering,ou=People,dc=wayup,dc=local
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: jdoe
cn: John Doe
sn: Doe
givenName: John
displayName: John Doe
mail: john.doe@wayup.local
uidNumber: 10001
gidNumber: 10000
homeDirectory: /home/jdoe
loginShell: /bin/bash
userPassword: {SSHA}password123

dn: uid=asmith,ou=Engineering,ou=People,dc=wayup,dc=local
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: asmith
cn: Alice Smith
sn: Smith
givenName: Alice
displayName: Alice Smith
mail: alice.smith@wayup.local
uidNumber: 10002
gidNumber: 10000
homeDirectory: /home/asmith
loginShell: /bin/bash
userPassword: {SSHA}password123
title: Senior Developer
telephoneNumber: +33-1-23-45-67-89

# Marketing Users
dn: uid=bjohnson,ou=Marketing,ou=People,dc=wayup,dc=local
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: bjohnson
cn: Bob Johnson
sn: Johnson
givenName: Bob
displayName: Bob Johnson
mail: bob.johnson@wayup.local
uidNumber: 10003
gidNumber: 10001
homeDirectory: /home/bjohnson
loginShell: /bin/bash
userPassword: {SSHA}password123
title: Marketing Manager

# Finance Users
dn: uid=cmartin,ou=Finance,ou=People,dc=wayup,dc=local
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: cmartin
cn: Carol Martin
sn: Martin
givenName: Carol
displayName: Carol Martin
mail: carol.martin@wayup.local
uidNumber: 10004
gidNumber: 10002
homeDirectory: /home/cmartin
loginShell: /bin/bash
userPassword: {SSHA}password123
title: Financial Analyst

Import the users:

docker cp 02-users.ldif openldap-server:/tmp/
docker exec openldap-server ldapadd -x \
    -D "cn=admin,dc=wayup,dc=local" \
    -w admin123 \
    -f /tmp/02-users.ldif

Step 3.4: Create Groups

Create a file named 03-groups.ldif:

# Groups
# File: 03-groups.ldif

# Engineering Group
dn: cn=engineering,ou=Groups,dc=wayup,dc=local
objectClass: groupOfNames
objectClass: top
cn: engineering
description: Engineering team members
member: uid=jdoe,ou=Engineering,ou=People,dc=wayup,dc=local
member: uid=asmith,ou=Engineering,ou=People,dc=wayup,dc=local

# Marketing Group
dn: cn=marketing,ou=Groups,dc=wayup,dc=local
objectClass: groupOfNames
objectClass: top
cn: marketing
description: Marketing team members
member: uid=bjohnson,ou=Marketing,ou=People,dc=wayup,dc=local

# Finance Group
dn: cn=finance,ou=Groups,dc=wayup,dc=local
objectClass: groupOfNames
objectClass: top
cn: finance
description: Finance team members
member: uid=cmartin,ou=Finance,ou=People,dc=wayup,dc=local

# All Employees Group
dn: cn=employees,ou=Groups,dc=wayup,dc=local
objectClass: groupOfNames
objectClass: top
cn: employees
description: All company employees
member: uid=jdoe,ou=Engineering,ou=People,dc=wayup,dc=local
member: uid=asmith,ou=Engineering,ou=People,dc=wayup,dc=local
member: uid=bjohnson,ou=Marketing,ou=People,dc=wayup,dc=local
member: uid=cmartin,ou=Finance,ou=People,dc=wayup,dc=local

# Administrators Group
dn: cn=admins,ou=Groups,dc=wayup,dc=local
objectClass: groupOfNames
objectClass: top
cn: admins
description: System administrators
member: uid=jdoe,ou=Engineering,ou=People,dc=wayup,dc=local

Import the groups:

docker cp 03-groups.ldif openldap-server:/tmp/
docker exec openldap-server ldapadd -x \
    -D "cn=admin,dc=wayup,dc=local" \
    -w admin123 \
    -f /tmp/03-groups.ldif

Part 4: LDAP Search Operations

Step 4.1: Basic Searches

Practice these search queries:

# List all entries in the directory
docker exec openldap-server ldapsearch -x \
    -D "cn=admin,dc=wayup,dc=local" \
    -w admin123 \
    -b "dc=wayup,dc=local" \
    "(objectClass=*)"

# List all organizational units
docker exec openldap-server ldapsearch -x \
    -D "cn=admin,dc=wayup,dc=local" \
    -w admin123 \
    -b "dc=wayup,dc=local" \
    "(objectClass=organizationalUnit)" \
    ou description

# List all users (inetOrgPerson)
docker exec openldap-server ldapsearch -x \
    -D "cn=admin,dc=wayup,dc=local" \
    -w admin123 \
    -b "ou=People,dc=wayup,dc=local" \
    "(objectClass=inetOrgPerson)" \
    uid cn mail title

Step 4.2: Search Filters

Practice with different filter types:

# Equality filter - Find user by uid
docker exec openldap-server ldapsearch -x \
    -D "cn=admin,dc=wayup,dc=local" \
    -w admin123 \
    -b "dc=wayup,dc=local" \
    "(uid=jdoe)"

# Presence filter - Find entries with email
docker exec openldap-server ldapsearch -x \
    -D "cn=admin,dc=wayup,dc=local" \
    -w admin123 \
    -b "dc=wayup,dc=local" \
    "(mail=*)" \
    cn mail

# Substring filter - Find users whose name contains "Smith"
docker exec openldap-server ldapsearch -x \
    -D "cn=admin,dc=wayup,dc=local" \
    -w admin123 \
    -b "dc=wayup,dc=local" \
    "(cn=*Smith*)"

# AND filter - Engineering users with title
docker exec openldap-server ldapsearch -x \
    -D "cn=admin,dc=wayup,dc=local" \
    -w admin123 \
    -b "ou=Engineering,ou=People,dc=wayup,dc=local" \
    "(&(objectClass=inetOrgPerson)(title=*))" \
    cn title

# OR filter - Find by multiple uids
docker exec openldap-server ldapsearch -x \
    -D "cn=admin,dc=wayup,dc=local" \
    -w admin123 \
    -b "dc=wayup,dc=local" \
    "(|(uid=jdoe)(uid=asmith))" \
    cn mail

# NOT filter - Users without a title
docker exec openldap-server ldapsearch -x \
    -D "cn=admin,dc=wayup,dc=local" \
    -w admin123 \
    -b "ou=People,dc=wayup,dc=local" \
    "(&(objectClass=inetOrgPerson)(!(title=*)))" \
    cn

Step 4.3: Search Scopes

Understand the three search scopes:

# BASE scope - only the specified entry
docker exec openldap-server ldapsearch -x \
    -D "cn=admin,dc=wayup,dc=local" \
    -w admin123 \
    -b "ou=People,dc=wayup,dc=local" \
    -s base \
    "(objectClass=*)"

# ONE scope - immediate children only
docker exec openldap-server ldapsearch -x \
    -D "cn=admin,dc=wayup,dc=local" \
    -w admin123 \
    -b "ou=People,dc=wayup,dc=local" \
    -s one \
    "(objectClass=*)" \
    dn

# SUB scope (default) - entire subtree
docker exec openldap-server ldapsearch -x \
    -D "cn=admin,dc=wayup,dc=local" \
    -w admin123 \
    -b "ou=People,dc=wayup,dc=local" \
    -s sub \
    "(objectClass=*)" \
    dn
Scope Description Use Case
base Only the base DN entry Check if entry exists
one Direct children only List items in a container
sub All descendants Search entire subtree

Part 5: Modify Operations

Step 5.1: Modify an Entry (ldapmodify)

Create a file named 04-modify-user.ldif:

# Modify John Doe's entry
# File: 04-modify-user.ldif

# Add a new attribute
dn: uid=jdoe,ou=Engineering,ou=People,dc=wayup,dc=local
changetype: modify
add: title
title: Lead Developer
-
add: telephoneNumber
telephoneNumber: +33-6-12-34-56-78
-
add: description
description: Team lead for backend services
docker cp 04-modify-user.ldif openldap-server:/tmp/
docker exec openldap-server ldapmodify -x \
    -D "cn=admin,dc=wayup,dc=local" \
    -w admin123 \
    -f /tmp/04-modify-user.ldif

Step 5.2: Replace and Delete Attributes

Create a file named 05-replace-delete.ldif:

# Replace and delete attributes
# File: 05-replace-delete.ldif

# Replace an existing attribute
dn: uid=jdoe,ou=Engineering,ou=People,dc=wayup,dc=local
changetype: modify
replace: title
title: Senior Lead Developer

# In another operation, delete an attribute
dn: uid=asmith,ou=Engineering,ou=People,dc=wayup,dc=local
changetype: modify
delete: telephoneNumber
docker cp 05-replace-delete.ldif openldap-server:/tmp/
docker exec openldap-server ldapmodify -x \
    -D "cn=admin,dc=wayup,dc=local" \
    -w admin123 \
    -f /tmp/05-replace-delete.ldif

Step 5.3: Add Member to Group

Create a file named 06-add-group-member.ldif:

# Add Alice Smith to admins group
# File: 06-add-group-member.ldif

dn: cn=admins,ou=Groups,dc=wayup,dc=local
changetype: modify
add: member
member: uid=asmith,ou=Engineering,ou=People,dc=wayup,dc=local
docker cp 06-add-group-member.ldif openldap-server:/tmp/
docker exec openldap-server ldapmodify -x \
    -D "cn=admin,dc=wayup,dc=local" \
    -w admin123 \
    -f /tmp/06-add-group-member.ldif

# Verify the change
docker exec openldap-server ldapsearch -x \
    -D "cn=admin,dc=wayup,dc=local" \
    -w admin123 \
    -b "cn=admins,ou=Groups,dc=wayup,dc=local" \
    "(objectClass=*)" \
    member

Part 6: Exercises

Important: Complete these exercises on your own before checking the solutions!

Exercise 1: Create a New Department

Create a new "Human Resources" department with the following structure:

  • Create an OU: ou=HR,ou=People,dc=wayup,dc=local
  • Create a user: Emily Wilson (uid: ewilson)
  • Create a group: cn=hr,ou=Groups,dc=wayup,dc=local
  • Add Emily to the hr group and employees group

Exercise 2: Search Queries

Write LDAP search commands to find:

  1. All users in the Engineering department with a title
  2. All groups that have more than one member
  3. All users whose email ends with @wayup.local
  4. All entries modified today (hint: use operational attributes)

Exercise 3: Group Management

Perform these operations:

  1. Create a new "developers" group
  2. Add John Doe and Alice Smith to the developers group
  3. Remove John Doe from the admins group
  4. Verify your changes with search queries

Exercise 4: Bulk User Import

Create an LDIF file to import 5 new users across different departments. Include:

  • All required inetOrgPerson attributes
  • At least one optional attribute (title, phone, description)
  • Proper group membership

Expected Output

After completing this practical work, you should have:

Directory Structure

  • dc=wayup,dc=local
    • ou=People
      • ou=Engineering
        • uid=jdoe
        • uid=asmith
      • ou=Marketing
        • uid=bjohnson
      • ou=Finance
        • uid=cmartin
      • ou=HR (from exercise)
        • uid=ewilson
    • ou=Groups
      • cn=engineering
      • cn=marketing
      • cn=finance
      • cn=employees
      • cn=admins
      • cn=developers (from exercise)
      • cn=hr (from exercise)
    • ou=Services

Key Learnings

  • Understanding of LDAP directory hierarchy (DIT)
  • Proficiency with LDIF format for imports/exports
  • Mastery of LDAP search filters and scopes
  • Ability to manage users and groups via command line
  • Experience with Apache Directory Studio GUI

Deliverables

  • LDIF Files: All 6+ LDIF files created during the practical
  • Exercise Solutions: LDIF files for exercises 1, 3, and 4
  • Search Queries: Document with all search commands from exercise 2
  • Screenshots: Apache Directory Studio showing final directory structure
  • Export: Full LDIF export of your final directory state

How to Export Your Directory

# Export entire directory to LDIF
docker exec openldap-server ldapsearch -x \
    -D "cn=admin,dc=wayup,dc=local" \
    -w admin123 \
    -b "dc=wayup,dc=local" \
    -LLL \
    "(objectClass=*)" > my-directory-export.ldif

Bonus Challenges

  • Challenge 1: Set up TLS encryption for LDAP connections (LDAPS on port 636)
  • Challenge 2: Configure password policies (min length, expiration, history)
  • Challenge 3: Create a Python script using ldap3 library to automate user creation
  • Challenge 4: Set up Access Control Lists (ACLs) to restrict who can modify what
  • Challenge 5: Configure replication between two LDAP servers

Python Script Starter

from ldap3 import Server, Connection, ALL, MODIFY_ADD

# Connect to LDAP server
server = Server('localhost', port=389, get_info=ALL)
conn = Connection(
    server,
    user='cn=admin,dc=wayup,dc=local',
    password='admin123',
    auto_bind=True
)

# Search for users
conn.search(
    search_base='ou=People,dc=wayup,dc=local',
    search_filter='(objectClass=inetOrgPerson)',
    attributes=['cn', 'mail', 'uid']
)

for entry in conn.entries:
    print(f"User: {entry.cn} - {entry.mail}")

# Add a new user
conn.add(
    'uid=newuser,ou=Engineering,ou=People,dc=wayup,dc=local',
    ['inetOrgPerson', 'posixAccount', 'shadowAccount'],
    {
        'cn': 'New User',
        'sn': 'User',
        'uid': 'newuser',
        'uidNumber': 10010,
        'gidNumber': 10000,
        'homeDirectory': '/home/newuser',
        'mail': 'new.user@wayup.local'
    }
)

conn.unbind()

Cleanup

When you're done with the practical work:

# Stop and remove containers
docker-compose down

# To also remove the data volumes (complete reset)
docker-compose down -v

Resources