LDAP & Directory Server Administration
Hands-on exercises with ApacheDS, LDIF files, and directory operations
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:
- Apache Directory Studio - Cross-platform LDAP browser and editor
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:
- Go to File → New → LDAP Connection
- Enter connection details:
- Connection name: WayUp Local
- Hostname: localhost
- Port: 389
- Encryption: No encryption
- Click Next
- Enter authentication:
- Authentication Method: Simple Authentication
- Bind DN: cn=admin,dc=wayup,dc=local
- Bind password: admin123
- Click Finish
Step 2.2: Explore the Directory Structure
Once connected, explore the existing structure:
- Expand the
dc=wayup,dc=localnode - 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:
- All users in the Engineering department with a title
- All groups that have more than one member
- All users whose email ends with
@wayup.local - All entries modified today (hint: use operational attributes)
Exercise 3: Group Management
Perform these operations:
- Create a new "developers" group
- Add John Doe and Alice Smith to the developers group
- Remove John Doe from the admins group
- 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=Engineering
- ou=Groups
- cn=engineering
- cn=marketing
- cn=finance
- cn=employees
- cn=admins
- cn=developers (from exercise)
- cn=hr (from exercise)
- ou=Services
- ou=People
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