Linux user management with LDAP

This article gives instructions on how to use OpenLDAP as an backend for managing user accounts in Debian.

This article assumes that you already have a working slapd instance running and took the necessary precautions to secure your LDAP server. How to get there is explained in detail in my article about setting up and securing an OpenLDAP server, and won't be covered here.

DIT structure

The structure of your directory is quite flexible. There has to be a place to save your user account data, and another for the group information. It makes sense to define an organizational unit (objectClass: organizationalUnit) for each, users and groups, and one on top to have all accounting information at one place. It is also advised to create a user whose only permission it is to read and write the accounting information.

+ dc=example,dc=com
+----+ ou=Manager
|    |
|    +----+ cn=customerAccountAdmin
+----+ ou=Customers
     +----+ ou=Users
     |    |
     |    +----+ uid=user0
     |    |
     |    +----+ uid=user1
     |    |
     |    +----+ uid=userN
     +----+ ou=Groups
          +----+ cn=group0
          +----+ cn=group1
          +----+ cn=groupN

The corresponding LDIF (generate your own userPassword for the customerAccountAdmin with slappasswd).

dn: ou=Manager,dc=example,dc=com
objectClass: organizationalUnit
objectClass: top
ou: Manager

dn: ou=Customers,dc=example,dc=com
objectClass: organizationalUnit
objectClass: top
ou: Customers

dn: cn=customerAccountAdmin,ou=Manager,dc=example,dc=com
cn: customerAccountAdmin
objectClass: organizationalRole
objectClass: simpleSecurityObject
objectClass: top
userPassword: {SSHA}NcVCla4v1kzadmTmQaMerUwgig5Z/MHk

dn: ou=Users,ou=Customers,dc=example,dc=com
objectClass: organizationalUnit
objectClass: top
ou: Users

dn: ou=Groups,ou=Customers,dc=example,dc=com
objectClass: organizationalUnit
objectClass: top
ou: Groups

Of course you have to modify the given dn's to fit your directory's olcSuffix configuration.


The ACL on the LDAP server must be configured to

  • Allow anonymous users to authenticate against their userPassword
  • Allow authenticated users to change their userPassword (reading the userPassword is not necessary)
  • Allow authenticated users to read their account information (e.g. homeDirectory or loginShell)
  • Allow the customerAccountAdmin to read and write all groups and users (write access for ou=Customers,dc=example,dc=com subtree).
  • Allow customerAccountAdmin to read its own object (This will be needed to use this DN as rootpwmoddn when setting up NSS in one of the next sections, see this discussion for more details.)
  • Deny access to anything else

The following ACL implements these requirements.

to attrs=userPassword
	by anonymous auth
	by self =xw
	by dn.base="cn=customerAccountAdmin,ou=Manager,dc=example,dc=com" =w
	by * none

to dn.subtree="ou=Customers,dc=example,dc=com"
	by dn.base="cn=customerAccountAdmin,ou=Manager,dc=example,dc=com" write
	by self read
	by * none

to dn.children="ou=Manager,dc=example,dc=com"
	by self read
	by * none

to dn.base=""
	by users read
	by * none

to * by * none

The corresponding LDIF is

dn: olcDatabase={1}mdb,cn=config
add: olcAccess
olcAccess: {0}to attrs=userPassword by self =xw by anonymous auth by dn.base="cn=customerAccountAdmin,ou=Manager,dc=example,dc=com" =w by * none
add: olcAccess
olcAccess: {1}to dn.subtree="ou=Customers,dc=example,dc=com" by dn.base="cn=customerAccountAdmin,ou=Manager,dc=example,dc=com" write by self read by * none
add: olcAccess
olcAccess: {2}to dn.children="ou=Manager,dc=example,dc=com" by self read by * none
add: olcAccess
olcAccess: {3}to dn.base="" by users read by * none
add: olcAccess
olcAccess: {4}to * by * none

If you have other ACLs in place make sure that they will not interfere with the rules given above. At least it is likely that the indices used here need to be changed.

Creating Accounts and Groups

There are several ways to create groups and user accounts in the directory. The most basic way is to create an LDIF file, set all the information manually and add it to the directory using ldapadd.

dn: cn=<groupname>,ou=Groups,ou=Customers,dc=example,dc=com
objectClass: posixGroup
cn: <groupname>
gidNumber: <gid>
dn: uid=<username>,ou=Users,ou=Customers,dc=example,dc=com
objectClass: account
objectClass: posixAccount
cn: <username>
uid: <username>
uidNumber: <uid>
gidNumber: <gid>
homeDirectory: <home>
loginShell: <shell>
you@workstation:~$ ldapadd -ZZ -x -W -D cn=customerAccountAdmin,ou=Manager,dc=example,dc=com -H ldap:// -f ldap_account_addgroup.ldif
you@workstation:~$ ldapadd -ZZ -x -W -D cn=customerAccountAdmin,ou=Manager,dc=example,dc=com -H ldap:// -f ldap_account_adduser.ldif

Using ldapscripts

A more convinient way to manage your accounts from the commandline is to use ldapscripts, a set of bash scripts which, when installed will provide you with commands like ldapaddgroup, ldapadduser, ldapdeleteuser, ldaprenamegroup etc.

To install it, run

root@workstation:~# apt-get install ldapscripts

After installation, a few parameters have to be configured in /etc/ldapscripts/ldapscripts.conf. The most important are listed below.


SERVER sets the LDAP host which hosts the slapd with our account data.

The SUFFIX defines the DN where our ou=Users and ou=Groups objects live, in our case ou=Customers,dc=example,dc=com. GSUFFIX and USUFFIX define were the groups and user accounts are located beneath the SUFFIX, that means that groups will be stored in GSUFFIX,SUFFIX and users in USUFFIX,SUFFIX, so in our case it would expand to ou=Groups,ou=Customers,dc=example,dc=com for groups and to ou=Users,ou=Customers,dc=example,dc=com for users.

SASLAUTH=“” deactivates the SASL authentication and activates simple authentication using the BINDDN (here cn=customerAccountAdmin,ou=Manager,dc=example,dc=com), and the password for this BINDDN, stored in /etc/ldapscripts/ldapscripts.passwd (BINDPWDFILE).

When storing the password for the BINDDN in /etc/ldapscripts/ldapscripts.passwd, it's important to prevent a trailing newline stored along with the password, as done by most editors. A good way to store the password in the file is to use echo -n.

root@workstation:~#  echo -n 'passwordForBINDDN' > /etc/ldapscripts/ldapscripts.passwd

Make sure to prepend a whitespace to this command, to prevent your password ending up in your bash history. Also make sure to set file permissions properly to prevent the password from being stolen by someone else.

root@workstation:~# chmod 600 /etc/ldapscripts/ldapscripts.passwd

GIDSTART and UIDSTART set the minimum gid and uid used for new groups and user accounts. The numbers defined here will only be used on the first creation of a group or user. Afterwards the highest gid or uid present in the directory will be incremented. This implies that if you already have a group or account with a lower gid or uid than defined in GIDSTART or UIDSTART, new groups and accounts likely will have ids below the numbers defined here. To fix this, either empty your directory from all groups and user accounts and fresh start using the ldapscripts or manually change the gidNumber and uidNumber of your existing groups and accounts to fit your requirements.

If you run the scripts on the same machine where the users will have their home directories, you can set CREATEHOMES to yes to create them when creating the account. LDAPBINOPTS=“-ZZ” ensures that TLS is used when talking to slapd.

Once everything is set up, it's easy to manage the accounts in the directory.

root@workstation:~# ldapaddgroup testgroup0
Successfully added group testgroup0 to LDAP
root@workstation:~# ldapaddgroup testgroup1
Successfully added group testgroup1 to LDAP
root@workstation:~# ldapadduser testuser0 testgroup0
Successfully added user testuser0 to LDAP
root@workstation:~# ldapaddusertogroup testuser0 testgroup1
Successfully added user testuser0 to group cn=testgroup1,ou=Groups,ou=Customers,dc=example,dc=com
root@workstation:~# ldapid testuser0
uid=10000(testuser0) gid=10000(testgroup0) groups=10000(testgroup0),10001(testgroup1)
root@workstation:~# ldapsetpasswd testuser0
Changing password for user uid=testuser0,ou=Users,ou=Customers,dc=example,dc=com
New Password: 
Retype New Password: 
Successfully set password for user uid=testuser0,ou=Users,ou=Customers,dc=example,dc=com

See man ldapscripts for more commands. Error messages like Error setting password for user uid=<user>… or Cannot resolve uid <user> to user : not found indicate insufficient access privileges of the customerAccountAdmin. Test if this is the case by using the olcRootDN and olcRootPW of your directory in the ldapscripts configuration. If everything works with root access, review and edit your ACLs until the commands work with the other account as expected. Do not use the LDAP root account to just manage your user accounts. This might be a security risk if the password gets stolen by someone else.

System integration

To make the groups and user accounts stored in the LDAP directory available to the operating system there are two relevant software components that need to be installed and configured.

The first component is a module to NSS to do name lookups to our LDAP directory. This is for example used by commands like id, passwd or groups to determine or manipulate user account information.

The second component needed is a module to PAM to actually perform authentication against the LDAP directory.

Both parts come in one software called nss-pam-ldapd but are split into seperate packages in Debian.


To setup the NSS module we have to install libnss-ldapd.

root@loginhost~:# apt-get install libnss-ldapd

During installation, you will be asked for the URL of the LDAP server. Even though the dialogue advises you to use the server's IP address instead of its domain, when running your slapd with TLS the use of the domainname specified in the servers certificate is essential (e.g. ldap:// You also have to provide the base DN of your directory where the search for groups and users will be started. In our case, this is ou=Customer,dc=example,dc=com. Then we need to provide the DN and password of our customerAccountAdmin. Ultimately you have to select the services for which to enable libnss-ldapd. For our purpose passwd, group and shadow will be sufficient (this can later be changed in /etc/nsswitch.conf).

After installation is done, some more configuration has to be done in /etc/nslcd.conf.

uid nslcd
gid nslcd
uri ldap://
base ou=Customers,dc=example,dc=com
binddn cn=customerAccountAdmin,ou=Manager,dc=example,dc=com
bindpw <password>
rootpwmoddn cn=customerAccountAdmin,ou=Manager,dc=example,dc=com
rootpwmodpw <password>
ssl start_tls
tls_cacertfile /etc/ldap/tls/CA.pem
tls_reqcert hard

uid, gid, uri, base, binddn, bindpw were already set during installation. rootpwmoddn and rootpwmodow could be set to the same values as binddn and bindpw. These credentials will be used when updating user's passwords through the passwd command.

ssl start_tls makes sure that TLS is used when talking to the LDAP server. tls_cacertfile defines the location of the CA certificate with which the server's certificate was signed. tls_reqcert hard ensures that the server's certificate is requested and checked against the CA certificate defined in tls_cacertfile. If this fails, the connection to the LDAP server will be terminated.

After editing /etc/nslcd.conf, the nslcd service has to be restarted.

root@loginhost:~# service nslcd restart

Now check whether your groups and user accounts stored in your directory are available on your system and try to change a user's password using passwd.

root@loginhost:~# id testuser0
uid=10000(testuser0) gid=10000(testgroup0) groups=10001(testgroup1),10000(testgroup0)
root@loginhost:~# groups testuser0
testuser0 : testgroup0 testgroup1
root@loginhost:~# passwd testuser0
New password: <newUserPassword>
Retype new password: <newUserPassword>
passwd: password updated successfully
root@loginhost:~# ldapwhoami -x -W -D uid=testuser0,ou=Users,ou=Customers,dc=example,dc=com -ZZ -H ldap://
Enter LDAP Password: <newUserPassword>


To install the PAM module, install the libpam-ldapd package.

root@loginhost:~# apt-get install libpam-ldapd

It also uses the parameters defined in /etc/nslcd.conf we already configured for libnss-ldapd to communicate with our LDAP server.

To activate LDAP authentication on your system, run pam-auth-update and enable LDAP Authentication in the dialogue shown. Afterwards you could tune the configuration generated in /etc/pam.d/common-*. The minimum_uid is set to 1000 by default. You might want to change this to the UIDSTART specified in /etc/ldapscripts.conf.

Now try to login with a user account stored in the directory. If you use SSH to test your configuration, make sure to set PasswordAuthentication yes and UsePAM yes in /etc/ssh/sshd_config to make sshd use PAM.


While setting things up, it might help to increase the loglevel of your slapd to at least include ACL and filter. If things don't work as expected tail -f the logfiles to see what happens when performing an action.

tail -f /var/log/syslog | grep slapd
tail -f /var/log/syslog | grep nslcd
tail -f /var/log/auth.log