Tuesday, July 27, 2010

LDAP for authentication

Authenticating to a LDAP serverReasons for authenticating to an LDAP server.

We assume that you would like to create a web server where a client can log in and then retrieve their e-mails via internet and/or send e-mails etc. (example: www.gmx.dewww.web.de orhttp://linuxali.dyndns.org:4141 ).

Therefore the client has to become a user on the web server. That means they have to run the web server as root (not recommended) to be able to use the commands useradd and groupadd. Your second option is to put all users into a database, where the system looks at every login and controls individual access if the user exists.

This second opportunity is safer as you have one single location in the network where all users log in (like the NDS from Novell); you can administrate the users at a central point (Single Point of Administration).

Necessary software

OpenLDAP 2.x.x (http://www.openldap.org/software/download/) (In this tutorial OpenLDAP 2.0.12 is used)

Nss_ldap (http://www.padl.com/nss_ldap.html)

Pam_ldap (http://www.padl.com/pam_ldap.html)

Pam-devel (http://www.tuxfinder.com) (only necessary if you did not compile PAM yourself)

Debian users only need the package libpam0g-dev ("apt-get install libpam0g-dev")

OpenLDAP should already be completly configured; if it is not and you have problems look for the tutorial by Thomas Kroll (http://www.linuxnetmag.com/de/issue6/m6ldap 1.html)

Installing the software

First, decompress the packages nss_ldap and pam_ldap by:

>> tar xvfz nss_ldap....tar.gz
>> tar xvfz pam_ldap....tar.gz

Then compile and install them by:
>> ./configure
>> make
>> make install

in each directory.

Installation time will depend on your computer.

Configuring the software

In order to store the following objects, for the LDAP account, you have to adapt the file slapd.conf ( it is in the configuration directory of OpenLDAP).

It should look like this:

Slapd.conf

include /etc/openldap/schema/core.schema
include /etc/openldap/schema/cosine.schema
include /etc/openldap/schema/nis.schema
include /etc/openldap/schema/inetorgperson.schema

# These are the files which define the objects
# that are included before starting the server.
# These entries must be changed.

# The following files should already be present,
# otherwise the LDAP server would not work properly.

pidfile /usr/local/var/slapd.pid
argsfile /usr/local/var/slapd.args

# This data is necessary for starting the LDAP server.

database ldbm
suffix "dc=alkronet,dc=de"

# This entry determines the highest object in your LDAP database.
# This value must be adapted.

rootdn "cn=Manager,dc=alkronet,dc=de"

# This entry determines a person who has all permissions
# for the following object in the LDAP database.
# This value must be adapted.

rootpw test

# The root password.

directory /usr/local/var/openldap-ldbm

# Directory with the LDAP database.

defaultaccess write

# Standard permissions for every user.

# Indices to maintain
index objectClass eq

The file /etc/ldap.conf must also be adapted because the programs nss_ldap and pam_ldap are accessing it (Be careful, do not edit the file: /etc/openldap/ldap.conf). It is also possible that the files are in a different place. If you use the option -sysconfdir= ... at configuration time, the files will reside in the corresponding directory.


Ldap.conf
host 127.0.0.1
# host where you can reach the LDAP server

base dc=alkronet,dc=de

# the base of the LDAP server

pam_filter objectclass=posixAccount

# At log in all objects which are contained in the object class
# posixAccount are searched for the user

pam_login_attribute uid

# also those which have the attribute uid

nss_base_passwd o=auth_user,dc=alkronet,dc=de?one
nss_base_shadow o=auth_user,dc=alkronet,dc=de?one
nss_base_group o=auth_group,dc=alkronet,dc=de?one

# names the LDAP place where the account data must be

sslno

# ssl connections = no

Afterwards a file should be created where an organizations container object is put in. This file could look like the following:

User.ldif
dn: o=auth_user, dc=alkronet, dc=de
o: auth_user
objectclass: organization

# these lines create an organizations object
# which is named "auth_user". Later, new
# users will be inserted in this object.

dn: o=auth_group, dc=alkronet, dc=de
o: auth_group
objectclass: organization

dn: cn=user, o=auth_group, dc=alkronet, dc=de
objectClass: posixGroup
objectClass: top
cn: user
userPassword: {crypt}x
gidNumber: 10

# here the group "user" with the number 10 is created

dn: uid=tester, o=auth_user, dc=alkronet, dc=de
uid: tester
cn: Test Tester
objectclass: account
objectclass: posixAccount
objectclass: top
objectclass: shadowAccount
userPassword: test
shadowLastChange: 11472
shadowMax: 99999
shadowWarning: 7
uidNumber: 1000
gidNumber: 10
homeDirectory: /home/tester
loginShell: /bin/bash

# uid = user- und login name
# cn = christian name, surname would be sn
# afterwards the object classes are defined
# for the quite tricky values with shadow*
# the manpages of passwd, useradd and
# shadow should probably be consulted
# uidNumber = user number or user id
# gidNumber = group number or id the user belongs to
# homeDirectory = home directory
# loginShell = login shell


After this file is created it can be added to the LDAP server.

This is done with the command ldapadd.

>> ldapadd -x -D "cn=manager, dc=alkronet, dc=de" -W -f User.ldif

Now the user is included in the LDAP database but the database is not accessed during log in.

So the PAM service must be adapted to the LDAP server.

Preparing the system for authenticating to a LDAP server

First /etc/nsswitch.conf must be edited to tell the system that group-, user- and password information is not only held in files but also on a LDAP server.

This could look like the following:

/etc/nsswitch.conf
passwd: ldap files
group: ldap files
shadow: ldap files

# ldap was added here

hosts: files dns
networks: files
protocols: db files
services: db files
ethers: db files
rpc: db files
netgroup: nis


If you compiled the packages nss_ldap and pam_ldap yourself, a file named ldap.conf should exist in the directory /usr/local/etc. If it is not, the option -sysconfdir was used at compile time. You should look in the directory you chose then.

Debian users who have worked with apt-get own the two files pam-ldap.conf and libnss-ldap.conf. These files are the same and you could also create a link (e.g.: ln -snf /etc/pam-ldap.conf /etc/libnss-ldap.conf).

The content of this file determines which LDAP server to authenticate to and which objects contain the user- and password information.

It could look like the following:

Ldap.conf oder ldap-pam.conf

host 127.0.0.1
# IP des LDAP Servers

base dc=alkronet,dc=de
# base object of the server

# binddn cn=proxyuser,dc=padl,dc=com
# bindpw secret
# rootbinddn cn=manager,dc=padl,dc=com
# port 389

# if you have to authenticate to the LDAP server to be able
# to browse data, the user and password have to be
# named here

# timelimit 30
# sets how long a user is allowed to browse the LDAP server

# bind_timelimit 30
# sets how long a user is allowed to be connected
# to the LDAP server

# idle_timelimit 3600
# sets the time the connection is automatically cut
# when the user is idle

pam_filter objectclass=posixAccount
# search all entries where the object class equals posixAccount

pam_login_attribute uid
# the username is stored in the attribute uid

nss_base_passwd o=auth_user, dc=alkronet,dc=de?one
nss_base_shadow o=auth_user, dc=alkronet,dc=de?one
nss_base_group o=auth_group, dc=alkronet,dc=de?one

# sets the path to the passwords, the shadow entries and the
# group information
# ?one means, that only one entry may be used
# if there is more than one entry the first found
# password is used

sslno
# SSL connections are not supported

Furthermore the configuration files of every service that is running on the system that will authenticate to the LDAP server must be adapted.

The configuration files reside in /etc/pam.d. Some examples are already included with the PAM software and can be found in example.

If you did not compile PAM yourself they should be in /usr/share/doc/pam, /usr/share/doc/packages/pam or /usr/share/doc/libpam.

The file that is accessed during log in is named login and could look like this:

/etc/pam.d/login
auth required /lib/security/pam_securetty.so
auth required /lib/security/pam_nologin.so
auth sufficient /lib/security/pam_ldap.so use_first_pass
auth required /lib/security/pam_unix_auth.so try_first_pass
account sufficient /lib/security/pam_ldap.so
account required /lib/security/pam_unix_acct.so
password required /lib/security/pam_cracklib.so
password required /lib/security/pam_ldap.so
password required /lib/security/pam_pwdb.so use_first_pass
session required /lib/security/pam_unix_session.so

# /lib/security/pam_ldap.so should be available
# for every section (auth, account, password) now

# use_first_pass means that the first entered password is used
# and the files (shadow and passwd) are omitted

The other files in the directory can also be adapted this way; or you could take the example files from PAM.

Now logging in should be working, but I had to reboot (perhaps some services must be restarted).

PHP script for adding users

add_user.php
$username = testuser;
$password = testuser;
$user_id = 1005;

$ldap_server = "127.0.0.1";
$ldap_base = "dc=alkronet,dc=de";

# Attention: Double user ids could lead to authenticating errors

$entries["uid"]=strtolower($username);
$entries["cn"]=$username;
$entries["objectclass"][0]="account";
$entries["objectclass"][1]="posixAccount";
$entries["objectclass"][2]="top";
$entries["objectclass"][3]="shadowAccount";
$entries["userPassword"]=$password;
$entries["shadowLastChange"]="11472";
$entries["shadowMax"]="99999";
$entries["shadowWarning"]="7";
$entries["uidNumber"]=$user_id;
$entries["gidNumber"]="10";
$entries["homeDirectory"]="/home/".$username;
$entries["loginShell"]="/bin/false";

$connect = ldap_connect($ldap_server);
$bind = ldap_bind($connect, "cn=manager, ".$ldap_base, "test");

if (!$bind || !$connect) {
echo "Connection could not be established.";
exit;
}

ldap_add($connect, "uid=".strtolower($username).", o=auth_user, ".$ldap_base, $entries);

if (ldap_error($connect) != "Success") {
echo "

".ldap_error($connect)."
";
}