DEV Community

Shoban Chiddarth
Shoban Chiddarth

Posted on • Edited on

OpenLDAP home lab - Cyber Security technical write up

GitHub Repo: @ShobanChiddarth/openldap-iam-lab

Introduction

This home lab is a part of my setup for an IAM lab using KeyCloak. In this lab I set up OpenLDAP in a Virtualbox lab.

Pre-Requisites

  1. VM Intercommunication setup
  2. Pi-hole
  3. Local TLS for HTTPS

Setup Process

Let's say the fictional organization I am working in is Acme Inc. The domain name would be acme.internal. I have the VM intercommunication setup that I linked earlier ready. I also setup custom DNS records in the Pi-hole (ldap-server.acme.internal points to 192.168.57.5 and ldap-client.acme.internal points to 192.168.57.6).

Setting up LDAP server

I installed slapd and ldap-utils

sudo apt update
sudo apt install slapd ldap-utils
Enter fullscreen mode Exit fullscreen mode

And set the admin password mid installation, then configured it with

sudo dpkg-reconfigure slapd
Enter fullscreen mode Exit fullscreen mode

It asked me for organization name along with domain, I filled in with the appropriate details. And verified it with

debian@ldap-server:~$ sudo slapcat
dn: dc=acme,dc=internal
objectClass: top
objectClass: dcObject
objectClass: organization
o: Acme
dc: acme
structuralObjectClass: organization
entryUUID: 2938c77a-b601-1040-8564-d9abd6bd9b70
creatorsName: cn=admin,dc=acme,dc=internal
createTimestamp: 20260317035728Z
entryCSN: 20260317035728.729122Z#000000#000#000000
modifiersName: cn=admin,dc=acme,dc=internal
modifyTimestamp: 20260317035728Z

Enter fullscreen mode Exit fullscreen mode

Creating Directory structure

I created structure.ldif to add two organizational units - one for users and one for groups, and loaded it.

debian@ldap-server:~$ nano structure.ldif
debian@ldap-server:~$ cat structure.ldif 
dn: ou=users,dc=acme,dc=internal
objectClass: organizationalUnit
ou: users

dn: ou=groups,dc=acme,dc=internal
objectClass: organizationalUnit
ou: groups
debian@ldap-server:~$ ldapadd -x -D "cn=admin,dc=acme,dc=internal" -W -f structure.ldif
Enter LDAP Password: 
adding new entry "ou=users,dc=acme,dc=internal"

adding new entry "ou=groups,dc=acme,dc=internal"

Enter fullscreen mode Exit fullscreen mode

Adding users

I will be creating 2 users "John Smith" and "Alice Doe" with passwords Password123 and Password456 respectively. I generated the password hashes using

debian@ldap-server:~$ sudo /usr/sbin/slappasswd -s Password123
{SSHA}2sIS9koZfMFfwEjWoglS4Iu8XpCvamLk
debian@ldap-server:~$ sudo /usr/sbin/slappasswd -s Password456
{SSHA}QlqSwZdrWeINW5PS54vTx4Bpqt+6PLdi
debian@ldap-server:~$ 
Enter fullscreen mode Exit fullscreen mode

Then created 2 users in users.ldif and loaded them.

debian@ldap-server:~$ nano users.ldif
debian@ldap-server:~$ cat users.ldif 
dn: uid=jsmith,ou=users,dc=acme,dc=internal
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: jsmith
sn: Smith
givenName: John
cn: John Smith
displayName: John Smith
uidNumber: 10001
gidNumber: 10001
userPassword: {SSHA}2sIS9koZfMFfwEjWoglS4Iu8XpCvamLk
loginShell: /bin/bash
homeDirectory: /home/jsmith
mail: jsmith@acme.internal

dn: uid=adoe,ou=users,dc=acme,dc=internal
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: adoe
sn: Doe
givenName: Alice
cn: Alice Doe
displayName: Alice Doe
uidNumber: 10002
gidNumber: 10002
userPassword: {SSHA}QlqSwZdrWeINW5PS54vTx4Bpqt+6PLdi
loginShell: /bin/bash
homeDirectory: /home/adoe
mail: adoe@acme.internal
debian@ldap-server:~$ ldapadd -x -D "cn=admin,dc=acme,dc=internal" -W -f users.ldif
Enter LDAP Password: 
adding new entry "uid=jsmith,ou=users,dc=acme,dc=internal"

adding new entry "uid=adoe,ou=users,dc=acme,dc=internal"

Enter fullscreen mode Exit fullscreen mode

Adding groups

I created groups engineering and HR, added jsmith to engineering and Alice to HR, and loaded them.

debian@ldap-server:~$ nano groups.ldif
debian@ldap-server:~$ cat groups.ldif 
dn: cn=engineers,ou=groups,dc=acme,dc=internal
objectClass: posixGroup
cn: engineers
gidNumber: 10001
memberUid: jsmith

dn: cn=hr,ou=groups,dc=acme,dc=internal
objectClass: posixGroup
cn: hr
gidNumber: 10002
memberUid: adoe
debian@ldap-server:~$ ldapadd -x -D "cn=admin,dc=acme,dc=internal" -W -f groups.ldif
Enter LDAP Password: 
adding new entry "cn=engineers,ou=groups,dc=acme,dc=internal"

adding new entry "cn=hr,ou=groups,dc=acme,dc=internal"

debian@ldap-server:~$ 

Enter fullscreen mode Exit fullscreen mode

Setting up LDAPS (using mkcert)

I created TLS certificates for ldap-server.acme.internal.

debian@debian:~/acme-certs$ mkcert -install
Created a new local CA 💥
The local CA is now installed in the system trust store! ⚡️
ERROR: no Firefox and/or Chrome/Chromium security databases found

debian@debian:~/acme-certs$ mkcert -CAROOT
/home/debian/.local/share/mkcert
debian@debian:~/acme-certs$ cp $(mkcert -CAROOT)/rootCA.pem .
debian@debian:~/acme-certs$ ls
rootCA-key.pem  rootCA.pem
debian@debian:~/acme-certs$ mkcert ldap-server.acme.internal
Note: the local CA is not installed in the Firefox and/or Chrome/Chromium trust store.
Run "mkcert -install" for certificates to be trusted automatically ⚠️

Created a new certificate valid for the following names 📜
 - "ldap-server.acme.internal"

The certificate is at "./ldap-server.acme.internal.pem" and the key at "./ldap-server.acme.internal-key.pem" ✅

It will expire on 17 June 2028 🗓

debian@debian:~/acme-certs$ ls -l
total 16
-rw------- 1 debian debian 1704 Mar 17 09:55 ldap-server.acme.internal-key.pem
-rw-r--r-- 1 debian debian 1472 Mar 17 09:55 ldap-server.acme.internal.pem
-rw-r--r-- 1 debian debian 1619 Mar 17 09:54 rootCA.pem
debian@debian:~/acme-certs$ 
Enter fullscreen mode Exit fullscreen mode

Copied those 3 files onto the server and set the required permissions.

debian@ldap-server:~/acme-certs$ ls
ldap-server.acme.internal-key.pem  ldap-server.acme.internal.pem  rootCA.pem
debian@ldap-server:~/acme-certs$ sudo mkdir -p /etc/ldap/tls
debian@ldap-server:~/acme-certs$ sudo cp ldap-server.acme.internal.pem /etc/ldap/tls/ldap-server.crt
debian@ldap-server:~/acme-certs$ sudo cp ldap-server.acme.internal-key.pem /etc/ldap/tls/ldap-server.key
debian@ldap-server:~/acme-certs$ sudo cp rootCA.pem /etc/ldap/tls/rootCA.pem
debian@ldap-server:~/acme-certs$ sudo chown -R openldap:openldap /etc/ldap/tls
debian@ldap-server:~/acme-certs$ sudo chmod 640 /etc/ldap/tls/ldap-server.key
debian@ldap-server:~/acme-certs$ sudo chmod 644 /etc/ldap/tls/ldap-server.crt /etc/ldap/tls/rootCA.pem
debian@ldap-server:~/acme-certs$ ls -l /etc/ldap/tls/
total 12
-rw-r--r-- 1 openldap openldap 1472 Mar 17 10:51 ldap-server.crt
-rw-r----- 1 openldap openldap 1704 Mar 17 10:51 ldap-server.key
-rw-r--r-- 1 openldap openldap 1619 Mar 17 10:51 rootCA.pem
debian@ldap-server:~/acme-certs$ ls -ld /etc/ldap/tls/
drwxr-xr-x 2 openldap openldap 4096 Mar 17 10:51 /etc/ldap/tls/
debian@ldap-server:~/acme-certs$ 

Enter fullscreen mode Exit fullscreen mode

Then I created tls conf file (tls.ldif) and applied it.

debian@ldap-server:~$ nano tls.ldif
debian@ldap-server:~$ cat tls.ldif 
dn: cn=config
changetype: modify
add: olcTLSCACertificateFile
olcTLSCACertificateFile: /etc/ldap/tls/rootCA.pem
-
add: olcTLSCertificateFile
olcTLSCertificateFile: /etc/ldap/tls/ldap-server.crt
-
add: olcTLSCertificateKeyFile
olcTLSCertificateKeyFile: /etc/ldap/tls/ldap-server.key
debian@ldap-server:~$ sudo ldapmodify -Y EXTERNAL -H ldapi:/// -f tls.ldif
SASL/EXTERNAL authentication started
SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
SASL SSF: 0
modifying entry "cn=config"

debian@ldap-server:~$ 

Enter fullscreen mode Exit fullscreen mode

Then I set SLAPD_SERVICES in /etc/default/slapd to use LDAPS

SLAPD_SERVICES="ldapi:/// ldaps:///"
Enter fullscreen mode Exit fullscreen mode

And specified LDAP client tools on the server to use the certificate

debian@ldap-server:~$ echo "TLS_CACERT /etc/ldap/tls/rootCA.pem" | sudo tee -a /etc/ldap/ldap.conf
TLS_CACERT /etc/ldap/tls/rootCA.pem
debian@ldap-server:~$ 

Enter fullscreen mode Exit fullscreen mode

Then I restarted slapd service and checked if LDAPS is working

debian@ldap-server:~$ sudo systemctl restart slapd
debian@ldap-server:~$ ldapsearch -x -H ldaps://ldap-server.acme.internal \
  -D "cn=admin,dc=acme,dc=internal" -W \
  -b "dc=acme,dc=internal" "(objectClass=*)"
Enter LDAP Password: 
# extended LDIF
#
# LDAPv3
# base <dc=acme,dc=internal> with scope subtree
# filter: (objectClass=*)
# requesting: ALL
#

# acme.internal
dn: dc=acme,dc=internal
objectClass: top
objectClass: dcObject
objectClass: organization
o: Acme
dc: acme

# users, acme.internal
dn: ou=users,dc=acme,dc=internal
objectClass: organizationalUnit
ou: users

# groups, acme.internal
dn: ou=groups,dc=acme,dc=internal
objectClass: organizationalUnit
ou: groups

# jsmith, users, acme.internal
dn: uid=jsmith,ou=users,dc=acme,dc=internal
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: jsmith
sn: Smith
givenName: John
cn: John Smith
displayName: John Smith
uidNumber: 10001
gidNumber: 10001
userPassword:: e1NTSEF9MnNJUzlrb1pmTUZmd0VqV29nbFM0SXU4WHBDdmFtTGs=
loginShell: /bin/bash
homeDirectory: /home/jsmith
mail: jsmith@acme.internal

# adoe, users, acme.internal
dn: uid=adoe,ou=users,dc=acme,dc=internal
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: adoe
sn: Doe
givenName: Alice
cn: Alice Doe
displayName: Alice Doe
uidNumber: 10002
gidNumber: 10002
userPassword:: e1NTSEF9UWxxU3daZHJXZUlOVzVQUzU0dlR4NEJwcXQrNlBMZGk=
loginShell: /bin/bash
homeDirectory: /home/adoe
mail: adoe@acme.internal

# engineers, groups, acme.internal
dn: cn=engineers,ou=groups,dc=acme,dc=internal
objectClass: posixGroup
cn: engineers
gidNumber: 10001
memberUid: jsmith

# hr, groups, acme.internal
dn: cn=hr,ou=groups,dc=acme,dc=internal
objectClass: posixGroup
cn: hr
gidNumber: 10002
memberUid: adoe

# search result
search: 2
result: 0 Success

# numResponses: 8
# numEntries: 7
debian@ldap-server:~$ 

Enter fullscreen mode Exit fullscreen mode

But there was one small problem with this. The userPassword's base64 encoded SSHA has is visible. I am going to fix that using ACLs.

I created acls.ldif and set the contents and loaded it and verified it.

debian@ldap-server:~$ nano acls.ldif
debian@ldap-server:~$ cat acls.ldif 
dn: olcDatabase={1}mdb,cn=config
changetype: modify
replace: olcAccess
olcAccess: {0}to attrs=userPassword
  by self write
  by anonymous auth
  by dn="cn=admin,dc=acme,dc=internal" write
  by * none
olcAccess: {1}to attrs=shadowLastChange
  by self write
  by * read
olcAccess: {2}to *
  by self write
  by dn="cn=admin,dc=acme,dc=internal" write
  by * read
debian@ldap-server:~$ sudo ldapmodify -Y EXTERNAL -H ldapi:/// -f acls.ldif
SASL/EXTERNAL authentication started
SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
SASL SSF: 0
modifying entry "olcDatabase={1}mdb,cn=config"

debian@ldap-server:~$ ldapsearch -x -H ldaps://ldap-server.acme.internal \
  -b "dc=acme,dc=internal" "(uid=jsmith)" userPassword
# extended LDIF
#
# LDAPv3
# base <dc=acme,dc=internal> with scope subtree
# filter: (uid=jsmith)
# requesting: userPassword 
#

# jsmith, users, acme.internal
dn: uid=jsmith,ou=users,dc=acme,dc=internal

# search result
search: 2
result: 0 Success

# numResponses: 2
# numEntries: 1
debian@ldap-server:~$ 

Enter fullscreen mode Exit fullscreen mode

Setting up LDAP client

I copied rootCA.pem to the LDAP client and trusted it.

debian@ldap-client:~/acme-certs$ ls
rootCA.pem
debian@ldap-client:~/acme-certs$ sudo cp rootCA.pem /usr/local/share/ca-certificates/acme-rootCA.crt
[sudo] password for debian: 
debian@ldap-client:~/acme-certs$ sudo update-ca-certificates
Updating certificates in /etc/ssl/certs...
rehash: warning: skipping ca-certificates.crt, it does not contain exactly one certificate or CRL
1 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d...
done.
debian@ldap-client:~/acme-certs$ 

Enter fullscreen mode Exit fullscreen mode

Then I installed System Security Services

sudo apt install -y sssd libpam-sss libnss-sss
Enter fullscreen mode Exit fullscreen mode

I now created the configuration for it, set the permissions and enabled the service.

debian@ldap-client:~$ sudo nano /etc/sssd/sssd.conf
debian@ldap-client:~$ sudo cat /etc/sssd/sssd.conf
[sssd]
services = nss, pam
config_file_version = 2
domains = acme.internal

[domain/acme.internal]
id_provider = ldap
auth_provider = ldap
ldap_uri = ldaps://ldap-server.acme.internal
ldap_search_base = dc=acme,dc=internal
ldap_tls_cacert = /etc/ssl/certs/ca-certificates.crt
ldap_schema = rfc2307
cache_credentials = true
enumerate = true
debian@ldap-client:~$ sudo chmod 600 /etc/sssd/sssd.conf
debian@ldap-client:~$ sudo systemctl enable --now sssd
Synchronizing state of sssd.service with SysV service script with /usr/lib/systemd/systemd-sysv-install.
Executing: /usr/lib/systemd/systemd-sysv-install enable sssd
debian@ldap-client:~$ 
Enter fullscreen mode Exit fullscreen mode

The only thing left is automatic home dir creation.

sudo pam-auth-update --enable mkhomedir
Enter fullscreen mode Exit fullscreen mode

This will make sure a home directory is automatically created when john ssh-es into the ldap client.

LDAP Client Verification

debian@ldap-client:~$ id jsmith
uid=10001(jsmith) gid=10001(engineers) groups=10001(engineers)
debian@ldap-client:~$ getent passwd jsmith
jsmith:*:10001:10001:John Smith:/home/jsmith:/bin/bash
debian@ldap-client:~$ getent group engineers
engineers:*:10001:jsmith
debian@ldap-client:~$ 

Enter fullscreen mode Exit fullscreen mode

LDAP client can see the users and groups set in the LDAP server.

SSH verification

From another desktop, copied rootCA.pem and trusted it.

debian@debian:~/acme-certs$ ls
rootCA.pem
debian@debian:~/acme-certs$ sudo cp rootCA.pem /usr/local/share/ca-certificates/acme-rootCA.crt
[sudo] password for debian: 
debian@debian:~/acme-certs$ sudo update-ca-certificates
Updating certificates in /etc/ssl/certs...
rehash: warning: skipping ca-certificates.crt, it does not contain exactly one certificate or CRL
1 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d...
done.
debian@debian:~/acme-certs$ 

Enter fullscreen mode Exit fullscreen mode

Now I tried to ssh login

debian@debian:~/acme-certs$ ssh jsmith@ldap-client.acme.internal
The authenticity of host 'ldap-client.acme.internal (192.168.57.6)' can't be established.
ED25519 key fingerprint is SHA256:wo67g9IGEfMUrZBC1KzzKlHS1G41PidIUGXZ5kTGmV0.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'ldap-client.acme.internal' (ED25519) to the list of known hosts.
jsmith@ldap-client.acme.internal's password: 
Creating directory '/home/jsmith'.
Linux ldap-client.acme.internal 6.12.73+deb13-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.12.73-1 (2026-02-17) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
jsmith@ldap-client:~$ pwd
/home/jsmith
jsmith@ldap-client:~$ 

Enter fullscreen mode Exit fullscreen mode

It worked. Also the home dir was automatically created.

Stopping anonymous bind and setup a service account for LDAP client

I need a password hash for the service account first. So I ran

debian@ldap-server:~$ sudo /usr/sbin/slappasswd -s SSSDPass123
[sudo] password for debian: 
{SSHA}ASFdTu5vaW4YSytPSFTpTc1StjoC+giz
debian@ldap-server:~$ 

Enter fullscreen mode Exit fullscreen mode

Then I put it in service accounts conf file and then load it

debian@ldap-server:~$ nano service-accounts.ldif
debian@ldap-server:~$ cat service-accounts.ldif 
dn: ou=service-accounts,dc=acme,dc=internal
objectClass: organizationalUnit
ou: service-accounts

dn: cn=sssd,ou=service-accounts,dc=acme,dc=internal
objectClass: simpleSecurityObject
objectClass: organizationalRole
cn: sssd
description: Read-only service account for SSSD
userPassword: {SSHA}ASFdTu5vaW4YSytPSFTpTc1StjoC+giz
debian@ldap-server:~$ ldapadd -x -H ldaps://ldap-server.acme.internal \
  -D "cn=admin,dc=acme,dc=internal" -W -f service-accounts.ldif
Enter LDAP Password: 
adding new entry "ou=service-accounts,dc=acme,dc=internal"

adding new entry "cn=sssd,ou=service-accounts,dc=acme,dc=internal"

debian@ldap-server:~$ 

Enter fullscreen mode Exit fullscreen mode

And then I create another ACL to make sure I only allow that specific account and disallow anonymous binds

debian@ldap-server:~$ nano acls2.ldif
debian@ldap-server:~$ cat acls2.ldif 
dn: olcDatabase={1}mdb,cn=config
changetype: modify
replace: olcAccess
olcAccess: {0}to attrs=userPassword
  by self write
  by anonymous auth
  by dn="cn=admin,dc=acme,dc=internal" write
  by * none
olcAccess: {1}to attrs=shadowLastChange
  by self write
  by * read
olcAccess: {2}to *
  by self write
  by dn="cn=admin,dc=acme,dc=internal" write
  by dn="cn=sssd,ou=service-accounts,dc=acme,dc=internal" read
  by * none
debian@ldap-server:~$ sudo ldapmodify -Y EXTERNAL -H ldapi:/// -f acls2.ldif
SASL/EXTERNAL authentication started
SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
SASL SSF: 0
modifying entry "olcDatabase={1}mdb,cn=config"

debian@ldap-server:~$ 

Enter fullscreen mode Exit fullscreen mode

Now I set the LDAP client to use those accounts and restart the service

debian@ldap-client:~$ sudo cat /etc/sssd/sssd.conf
[sssd]
services = nss, pam
config_file_version = 2
domains = acme.internal

[domain/acme.internal]
id_provider = ldap
auth_provider = ldap
ldap_uri = ldaps://ldap-server.acme.internal
ldap_search_base = dc=acme,dc=internal
ldap_tls_cacert = /etc/ssl/certs/ca-certificates.crt
ldap_schema = rfc2307
cache_credentials = true
enumerate = true
ldap_default_bind_dn = cn=sssd,ou=service-accounts,dc=acme,dc=internal
ldap_default_authtok = SSSDPass123
debian@ldap-client:~$ sudo systemctl restart sssd
Enter fullscreen mode Exit fullscreen mode

Note: ldap_default_authtok is stored in plaintext, but sssd.conf is locked to root only via the chmod 600 set earlier, so it is not readable by other users on the system.

Verifying again

LDAP client records fetching:

debian@ldap-client:~$ id jsmith
uid=10001(jsmith) gid=10001(engineers) groups=10001(engineers)
debian@ldap-client:~$ getent passwd adoe
adoe:*:10002:10002:Alice Doe:/home/adoe:/bin/bash
debian@ldap-client:~$ 

Enter fullscreen mode Exit fullscreen mode

SSH verification:

debian@debian:~/acme-certs$ ssh jsmith@ldap-client.acme.internal
jsmith@ldap-client.acme.internal's password: 
Linux ldap-client.acme.internal 6.12.73+deb13-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.12.73-1 (2026-02-17) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Tue Mar 17 11:43:45 2026 from 192.168.57.104
jsmith@ldap-client:~$ 

Enter fullscreen mode Exit fullscreen mode

It is working. Only the LDAP client can call the LDAP servers and anonymous binds are disabled.

Screenshots

VBoxManager

001

ALL VMs

002

Conclusion

OpenLDAP is now running as the centralized identity backend for acme.internal. The directory is secured with LDAPS using the lab's mkcert CA. ACLs enforce least-privilege access - password hashes are not exposed to anyone except the admin, and anonymous binds are disabled entirely. A dedicated read-only service account is the only thing allowed to query the directory, which is what SSSD on ldap-client uses. Users that exist only in LDAP can SSH into ldap-client with their home directories created automatically on first login.

This is the identity foundation for the next part of this series - connecting Keycloak to this LDAP directory as its user backend.

Top comments (0)