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.
Table of contents
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
).
- slapd_accounting_basic.ldif
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.
ACLs
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 theuserPassword
is not necessary) - Allow authenticated users to read their account information (e.g.
homeDirectory
orloginShell
) - Allow the
customerAccountAdmin
to read and write all groups and users (write access forou=Customers,dc=example,dc=com
subtree). - Allow
customerAccountAdmin
to read its own object (This will be needed to use this DN asrootpwmoddn
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
- slapd_account_ACL.ldif
dn: olcDatabase={1}mdb,cn=config changetype:modify 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
.
- ldap_account_addgroup.ldif
dn: cn=<groupname>,ou=Groups,ou=Customers,dc=example,dc=com objectClass: posixGroup cn: <groupname> gidNumber: <gid>
- ldap_account_adduser.ldif
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://example.com -f ldap_account_addgroup.ldif you@workstation:~$ ldapadd -ZZ -x -W -D cn=customerAccountAdmin,ou=Manager,dc=example,dc=com -H ldap://example.com -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.
- ldapscripts.conf
SERVER="ldap://example.com" SUFFIX="ou=Customers,dc=example,dc=com" GSUFFIX="ou=Groups" USUFFIX="ou=Users" SASLAUTH="" BINDDN="cn=customerAccountAdmin,ou=Manager,dc=example,dc=com" BINDPWDFILE="/etc/ldapscripts/ldapscripts.passwd" GIDSTART="10000" UIDSTART="10000" CREATEHOMES="no" LDAPBINOPTS="-ZZ"
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.
NSS
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://example.com
). 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
.
- nslcd.conf
uid nslcd gid nslcd uri ldap://example.com 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://example.com Enter LDAP Password: <newUserPassword> dn:uid=testuser0,ou=Users,ou=Customers,dc=example,dc=com
PAM
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.
Troubleshooting
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