No description
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-06-08 08:44:43 +02:00
bootstrap Inital commit 2026-06-08 08:44:43 +02:00
introduction Inital commit 2026-06-08 08:44:43 +02:00
.gitignore Inital commit 2026-06-08 08:44:43 +02:00
docker-compose.yaml Inital commit 2026-06-08 08:44:43 +02:00
README.md Inital commit 2026-06-08 08:44:43 +02:00

An Introduction to LDAP (Hands-On)

A practical, CLI-first introduction to LDAP built around a Docker sandbox. Every command in these lessons is meant to be copy-pasted against the server running from the docker-compose.yaml. All output shown was captured from this exact setup, so what you see should match what you get.

Who this is for

You've heard of LDAP and you want to actually understand it by poking at a sandbox server with command-line tools. No prior LDAP knowledge assumed.

The lessons

Work through them in order. Each builds on the last.

# File What you'll learn
1 01-concepts.md What LDAP is: the tree, entries, DNs, attributes, object classes
2 02-connecting-and-binding.md Connecting, binding (logging in), anonymous vs authenticated, the Root DSE
3 03-searching.md ldapsearch in depth: base, scope, filters, attribute selection
4 04-entries-and-schema.md Object classes, MUST/MAY attributes, browsing the schema
5 05-modifying.md LDIF, ldapadd, ldapmodify, ldapdelete, ldapmodrdn
6 06-groups-and-membership.md Groups, members, and the memberOf reverse-lookup overlay
7 07-passwords-and-access-control.md Passwords, ldappasswd, hashing, and why ACLs hid things from you
8 08-cheatsheet.md A one-page reference for everything above

The lab environment

This sandbox runs vegardit/openldap plus phpLDAPadmin (a web UI at http://localhost:8080, handy for visualising the tree — but we'll live in the terminal).

Connection details

Thing Value
Host / URI ldap://localhost:389
Base DN (the "root" of our tree) dc=example,dc=com
Admin bind DN (full read/write) uid=admin,dc=example,dc=com
Admin password adminpassword
Regular employee uid=alice,ou=Users,dc=example,dc=com / alicepassword
Regular employee uid=bob,ou=Users,dc=example,dc=com / bobpassword
Service account uid=svc-ldap-bind,ou=TechnicalAccounts,ou=Users,dc=example,dc=com
External contractor uid=ext-max,ou=External,ou=Users,dc=example,dc=com / ext-maxpassword

Note on the admin DN: many tutorials use cn=admin,.... This image uses uid=admin,dc=example,dc=com instead. If a command fails with Invalid credentials (49), check you used the right bind DN.

The directory tree (what's seeded)

dc=example,dc=com
├── ou=Users                        52 employees (alice, bob + 50 more)
│   ├── uid=alice  uid=bob  uid=charlie  … uid=zoe
│   ├── ou=Internal                 (empty — reserved for future use)
│   ├── ou=External                 ext-max, ext-sara, ext-john  (contractors)
│   └── ou=TechnicalAccounts        svc-ldap-bind, svc-backup, svc-monitoring,
│                                   svc-mail, svc-jenkins
├── ou=Groups                       14 groups
│   ├── cn=developers               engineering team (14 members)
│   ├── cn=engineering              same membership as developers
│   ├── cn=hr / cn=finance / cn=marketing / cn=sales / cn=legal
│   ├── cn=operations / cn=it-support
│   ├── cn=managers                 10 department heads + executives
│   ├── cn=sysadmins                5 technical staff with elevated access
│   ├── cn=vpn-users                23 staff with VPN access
│   ├── cn=all-employees            all 52 regular employees
│   └── cn=ldap-readonly            svc-ldap-bind + svc-monitoring
└── ou=Policies
    ├── cn=DefaultPasswordPolicy    created by the image (8-char min, 3 failures)
    └── cn=StrictPasswordPolicy     from seed.ldif (12-char min, 90-day expiry,
                                    5-password history)

Everything under ou=Users, ou=Groups, and cn=StrictPasswordPolicy comes from bootstrap/seed.ldif. The OU skeleton and DefaultPasswordPolicy are created by the image itself.

Do you have the client tools?

These lessons use the OpenLDAP command-line clients: ldapsearch, ldapadd, ldapmodify, ldapdelete, ldapwhoami, ldappasswd. Check:

ldapsearch -VV

If you get "command not found", either install them (sudo apt install ldap-utils on Debian/Ubuntu) or run every command inside the container by prefixing it with docker exec ldap-server, e.g.:

docker exec ldap-server ldapsearch -x -H ldap://localhost \
  -D "uid=admin,dc=example,dc=com" -w adminpassword \
  -b "dc=example,dc=com" -LLL

You'll repeat the same connection flags constantly. Export them once per terminal session so you can shorten commands in the lessons:

export LDAPURI="ldap://localhost:389"
export LDAPBINDDN="uid=admin,dc=example,dc=com"
export LDAPBASE="dc=example,dc=com"
# Then a full search becomes just:
ldapsearch -x -w adminpassword -LLL "(uid=alice)"

ldapsearch and friends read LDAPURI, LDAPBINDDN, and LDAPBASE from the environment automatically. The lessons show the full commands (no env vars) so they always work, but feel free to use the shortcuts.

Resetting the lab

Lessons 5+ change the directory (adding/deleting entries). Because this sandbox uses no persistent volumes, you can wipe everything back to the seeded state at any time:

docker compose down && docker compose up -d

Give it ~10 seconds to re-seed, then carry on.


Ready? Start with 01-concepts.md.