openLDAP – Self Service Password and Adhoc LDAP utilities

This entry is part 8 of 8 in the series Openldap Tutorial

In this LDAP utilities section we will see how to provide a way for end users to manage their passwords using Self Service Password, Password unlock procedure, Automate Password expiry notification and  LDAP backup automation.

 

Self Service Password

Self Service Password is a PHP application that allows users to change their password in an LDAP directory. This section will just be an excerpt on minimum configuration that is needed. For more details on self service password you can refer their site

 

Install SSP

In the LDAP server, create a  file /etc/apt/sources.list.d/ltb-project.list and add the below content

deb [arch=amd64] https://ltb-project.org/debian/jessie jessie main

Import repository key:

wget -O - https://ltb-project.org/wiki/lib/RPM-GPG-KEY-LTB-project | sudo apt-key add -

Update package repositry

apt-get update

Install SSP

apt-get install self-service-password

SSP requires php mbstring, so install it,

apt-get install php7.0-mbstring

 

 

Webserver Configuration

Since we already have  an existing webserver configuration which we created during phpldapadmin configuration, we can make use of that. We just need to update few things in the Virtual Host

Make sure the authz_ldap module is enabled,

a2enmod authz_ldap
<IfModule mod_ssl.c>
	<VirtualHost _default_:443>
		ServerAdmin [email protected]
		ServerName ldap.devopsideas.com

		DocumentRoot /var/www/html
		Alias /ssp /usr/share/self-service-password

		<Directory /usr/share/self-service-password>
                	AuthType Basic
                	AuthName " LDAP Auth"
                	AuthBasicProvider ldap
                	AuthLDAPURL "ldap://ldap.devopsideas.com:389/ou=people,dc=devopsideas,dc=com?uid" TLS
                	AuthLDAPBindDN "cn=serverid,ou=service_ids,dc=devopsideas,dc=com"
                	AuthLDAPBindPassword "<serverid_password>"
                	Require ldap-group cn=ssp,ou=basic_authentication,ou=group,dc=devopsideas,dc=com
        	</Directory>

		ErrorLog ${APACHE_LOG_DIR}/error.log
		CustomLog ${APACHE_LOG_DIR}/access.log combined

		SSLEngine on

		SSLCertificateFile	/etc/ssl/certs/devopsideas.crt
		SSLCertificateKeyFile /etc/ssl/private/devopsideas.key


		<FilesMatch "\.(cgi|shtml|phtml|php)$">
				SSLOptions +StdEnvVars
		</FilesMatch>
		<Directory /usr/lib/cgi-bin>
				SSLOptions +StdEnvVars
		</Directory>
	</VirtualHost>
</IfModule>

We are creating an Alias for self service password. Then we have just enabled basic authentication for SSP and provided access to people who belong to ssp group ( Assuming you have already created a group called ssp and added users to it ).

 

Edit the configuration file /usr/share/self-service-password/conf/config.inc.php and update it with below values,

<?php
# LDAP
$ldap_url = "localhost";
$ldap_starttls = true;
$ldap_binddn = "cn=sspadmin,id=service_ids,dc=devopsideas,dc=com";
$ldap_bindpw = "test";
$ldap_base = "ou=people,dc=devopsideas,dc=com";
$ldap_login_attribute = "cn";
$ldap_fullname_attribute = "givenName";
$ldap_filter = "(&(cn={login})(objectClass=inetOrgPerson))";

# Active Directory mode
$ad_mode = false;

$shadow_options['update_shadowLastChange'] = true;


# Local password policy
$pwd_min_length = 10;
$pwd_max_length = 40;
$pwd_min_lower = 1;
$pwd_min_upper = 1;
$pwd_min_digit = 1;
$pwd_min_special = 1;
$pwd_special_chars = "^a-zA-Z0-9";
$pwd_no_reuse = true;
$pwd_diff_login = true;
$pwd_complexity = 2;
$pwd_show_policy = "onerror";
$pwd_show_policy_pos = "above";
$who_change_password = "user";

# Use standard change form?
$use_change = true;

## Questions/answers
$use_questions = false;


$use_tokens = false;
$crypt_tokens = true;
$token_lifetime = "3600";

## Mail
# LDAP mail attribute
$mail_attribute = "mail";
# Who the email should come from
$mail_from = "[email protected]";
$mail_from_name = "devopsideas LDAP Password Reset";
# Notify users anytime their password is changed
$mail_address_use_ldap = true;

$notify_on_change = true;

# PHPMailer configuration 
$mail_sendmailpath = '/usr/sbin/sendmail';
$mail_protocol = 'smtp';
$mail_smtp_debug = 0;
$mail_debug_format = 'html';
$mail_smtp_host = 'smtp.gmail.com';
$mail_smtp_auth = true;
$mail_smtp_user = '[email protected]';
$mail_smtp_pass = '<mail_password>';
$mail_smtp_port = 25;
$mail_smtp_timeout = 30;
$mail_smtp_keepalive = false;
$mail_smtp_secure = 'tls';
$mail_contenttype = 'text/plain';
$mail_charset = 'utf-8';
$mail_priority = 3;
$mail_newline = PHP_EOL;

$use_sms = false;

# Display help messages
$show_help = true;

# Language
$lang ="en";

# Display menu on top
$show_menu = true;

# Logo
$logo = "images/devopsideas-logo.png";

# Background image
$background_image = "images/background.jpg";

# Debug mode
$debug = true;

# Encryption, decryption keyphrase
$keyphrase = "<keyphrase>";

# Where to log password resets - Make sure apache has write permission
# By default, they are logged in Apache log
$reset_request_log = "/var/log/self-service-password";

# Invalid characters in login
# Set at least "*()&|" to prevent LDAP injection
# If empty, only alphanumeric characters are accepted
$login_forbidden_chars = "*()&|";

# Use Google reCAPTCHA
$use_recaptcha = false;

?>

You can refer this site for complete details on the variables used and their explanation.  We have trimmed the configuration to reset the password just using the SSP application. You can enhance it to send sms, tokens etc for additional security.

In this example we will use a separate id (sspadmin) to do the bind and update the password. We need to update ACL to allow sspadmin to reset user password. We are doing this since using root DN (cn=admin,dc=devopsideas,dc=com) and passing passwords in file is not recommended.

First let’s create sspadmin id. Create a file named sspadminid.ldif and copy the below content.

dn: cn=sspadmin,ou=service_ids,dc=devopsideas,dc=com
cn: sspadmin
givenName: SSP Admin
sn: id
uid: sspadmin
objectClass: top
objectClass: inetOrgPerson
objectClass: person
userPassword: {SHA}qUqP5cyxm6YcTAhz05Hph5gvu9M=

Create the id by running the below command,

ldapadd -Z -W -D cn=admin,dc=devopsideas,dc=com  -f sspadmin.ldif

 

Update LDAP ACL to allow password change for sspadmin id.

Create a file named acl.ldif with the below content

dn: olcDatabase={1}mdb,cn=config
changetype: modify
replace: olcAccess
olcAccess: {0}to attrs=userPassword by self write by anonymous auth by * none
olcAccess: {1}to attrs=shadowLastChange by self write by * read
olcAccess: {2}to * by self write by group.exact="cn=sspadmin,ou=service_ids,dc=devopsideas,dc=com" write by users read by * none

Run the below command to make the changes,

ldapmodify -W -D cn=admin,cn=config -f acl.ldif

 

Verify by Changing password

Access the SSP application, https://<domain name>/ssp. You’ll be prompted for authentication.

LDAP utilities - 1

 

Once authenticated, you’ll get the SSP page.

LDAP utilities - 2

 

Try changing the password by entering a password that does not meet the password quality rules.

LDAP Utilities - 3

 

You’ll get a message as below,

LDAP Utilities - 4

 

Provide a valid credential now and you should be able to change your password. You’ll receive a mail regarding your password change if you had configured mail in the SSP config.

LDAP Utilities - 5

Now the end users have a way to manage their own passwords.

 

Password expiry notification

Create a file named passwdExpiration.sh with the below content

#!/bin/sh

# LDAP host URI
MY_LDAP_HOSTURI="localhost"

# LDAP root DN 
MY_LDAP_ROOTDN="cn=sspadmin,ou=service_ids,dc=devopsideas,dc=com"

MY_LDAP_ROOTPW="<sspadmin_password>"

MY_LDAP_DEFAULTPWDPOLICYDN="cn=userPasswordPolicy,ou=pwpolicies,dc=devopsideas,dc=com"

MY_LDAP_SEARCHBASE="ou=people,dc=devopsideas,dc=com"

MY_LDAP_SEARCHFILTER="(&(cn=*)(objectClass=inetOrgPerson))"

MY_LDAP_SEARCHSCOPE="one"

MY_LDAP_SEARCHBIN="/usr/bin/ldapsearch -Z"


MY_LDAP_NAME_ATTR=givenName
MY_LDAP_LOGIN_ATTR=cn
MY_LDAP_MAIL_ATTR=mail

MY_MAIL_BODY="From: LDAP Password expiry Alert \n\n \
	Hi %name,\n\n \
	Your LDAP password is about to expire. Please change your password soon using Self Service Password Portal.\n\n \
	Link --> https://ldap.devopsideas.com/ssp \n\n \
	\n\n- devopsideas LDAP team."

MY_MAIL_SUBJECT="Your LDAP account will expire soon. Please reset your password"
MY_MAIL_BIN="mailx"

MY_LOG_HEADER="`date +\"%b %e %T\"` `hostname` $0[$$]:"

MY_GAWK_BIN="/usr/bin/gawk"


getTimeInSeconds() {
	date=0
	os=`uname -s`

	if [ "$1" ]; then
		date=`${MY_GAWK_BIN} 'BEGIN  { \
			if (ARGC == 2) { \
		        	print mktime(ARGV[1]) \
			} \
			exit 0 }' "$1"`
	else
		if [ "${os}" = "SunOS" ]; then
			date=`/usr/bin/truss /usr/bin/date 2>&1 | nawk -F= \
				'/^time\(\)/ {gsub(/ /,"",$2);print $2}'`
		else
			now=`date +"%Y %m %d %H %M %S" -u`
			date=`getTimeInSeconds "$now"`
		fi
	fi

	echo ${date}
}


tmp_dir="/tmp/$$.checkldap.tmp"
result_file="${tmp_dir}/res.tmp.1"
buffer_file="${tmp_dir}/buf.tmp.1"
ldap_param="-LLL -h ${MY_LDAP_HOSTURI} -x"
nb_users=0
nb_expired_users=0
nb_warning_users=0

if [ -d ${tmp_dir} ]; then
	echo "Error : temporary directory exists (${tmp_dir})"
	exit 1
fi
mkdir ${tmp_dir}

if [ ${MY_LDAP_ROOTDN} ]; then
	ldap_param="${ldap_param} -D ${MY_LDAP_ROOTDN} -w ${MY_LDAP_ROOTPW}"
fi

${MY_LDAP_SEARCHBIN}  ${ldap_param} -s ${MY_LDAP_SEARCHSCOPE} \
	-b "${MY_LDAP_SEARCHBASE}" "${MY_LDAP_SEARCHFILTER}" \
	"dn" > ${result_file}

while read dnStr
do
	if [ ! "${dnStr}" ]; then
		continue
	fi

	dn=`echo ${dnStr} | cut -d : -f 2`

	nb_users=`expr ${nb_users} + 1`
	
	${MY_LDAP_SEARCHBIN} ${ldap_param} -s base -b "${dn}" \
		${MY_LDAP_NAME_ATTR} ${MY_LDAP_LOGIN_ATTR} ${MY_LDAP_MAIL_ATTR} pwdChangedTime pwdPolicySubentry \
		> ${buffer_file}

	login=`grep -w "${MY_LDAP_LOGIN_ATTR}:" ${buffer_file} | cut -d : -f 2 \
		| sed "s/^ *//;s/ *$//"`
	name=`grep -w "${MY_LDAP_NAME_ATTR}:" ${buffer_file} | cut -d : -f 2\
		| sed "s/^ *//;s/ *$//"`
	mail=`grep -w "${MY_LDAP_MAIL_ATTR}:" ${buffer_file} | cut -d : -f 2 \
		| sed "s/^ *//;s/ *$//"`
	pwdChangedTime=`grep -w "pwdChangedTime:" ${buffer_file} \
		| cut -d : -f 2 | cut -c 1-15 | sed "s/^ *//;s/ *$//"`
	pwdPolicySubentry=`grep -w "pwdPolicySubentry:" ${buffer_file} \
		| cut -d : -f 2 | sed "s/^ *//;s/ *$//"`

	if [ ! "${pwdChangedTime}" ]; then
		echo "${MY_LOG_HEADER} No password change date for ${login}" >&2
		continue
	fi

	if [ ! "${pwdPolicySubentry}" -a ! "${MY_LDAP_DEFAULTPWDPOLICYDN}" ]; then
		echo "${MY_LOG_HEADER} No password policy for ${login}" >&2
		continue
	fi

	ldap_search="${MY_LDAP_SEARCHBIN} ${ldap_param} -s base"
	if [ "${pwdPolicySubentry}" ]; then
		ldap_search="${ldap_search} -b ${pwdPolicySubentry}"
	else
		ldap_search="${ldap_search} -b ${MY_LDAP_DEFAULTPWDPOLICYDN}"
	fi

	ldap_search="$ldap_search pwdMaxAge pwdExpireWarning"
	pwdMaxAge=`${ldap_search} | grep -w "pwdMaxAge:" | cut -d : -f 2 \
		| sed "s/^ *//;s/ *$//"`
	pwdExpireWarning=`${ldap_search} | grep -w "pwdExpireWarning:" | cut -d : -f 2 \
		| sed "s/^ *//;s/ *$//"`

	MY_MAIL_DELAY=${MY_MAIL_DELAY:=$pwdExpireWarning}

	if [ "${pwdChangedTime}" ]; then
		s=`echo ${pwdChangedTime} | cut -c 13-14`
		m=`echo ${pwdChangedTime} | cut -c 11-12`
		h=`echo ${pwdChangedTime} | cut -c 9-10`
		d=`echo ${pwdChangedTime} | cut -c 7-8`
		M=`echo ${pwdChangedTime} | cut -c 5-6`
		y=`echo ${pwdChangedTime} | cut -c 1-4`
		currentTime=`getTimeInSeconds`
		pwdChangedTime=`getTimeInSeconds "$y $M $d $h $m $s"`
		diffTime=`expr ${currentTime} - ${pwdChangedTime}`
	fi

	expireTime=`expr ${pwdChangedTime} + ${pwdMaxAge}`
	if [ ${currentTime} -gt ${expireTime} ]; then
		nb_expired_users=`expr ${nb_expired_users} + 1`
		echo "${MY_LOG_HEADER} Password expired for ${login}" >&2
		continue
	fi

	if [ "${mail}" -a "${name}" \
		-a "${login}" -a "${diffTime}" -a "${pwdMaxAge}" ]
	then
		diffTime=`expr ${diffTime} + ${MY_MAIL_DELAY}`
		if [ ${diffTime} -gt ${pwdMaxAge} ]; then
			logmsg="${MY_MAIL_BODY}"
			logmsg=`echo ${logmsg} | sed "s/%name/${name}/; \
				s/%login/${login}/"`

			echo "${logmsg}" | ${MY_MAIL_BIN} -s "${MY_MAIL_SUBJECT}" ${mail} >&2

			echo "${MY_LOG_HEADER} Mail sent to user ${login} (${mail})" >&2

			nb_warning_users=`expr ${nb_warning_users} + 1`
		fi
	fi

done < ${result_file}

echo "${MY_LOG_HEADER} --- Statistics ---"
echo "${MY_LOG_HEADER} Users checked: ${nb_users}"
echo "${MY_LOG_HEADER} Account expired: ${nb_expired_users}"
echo "${MY_LOG_HEADER} Account in warning: ${nb_warning_users}"

rm -rf ${tmp_dir}

exit 0

 

The variables marked in blue are the one’s you need to update. MY_LDAP_DEFAULTPWDPOLICYDN variable defines the password policy that is to be checked for. 

Sample script output,

$ ./passwdExpiry.sh 
Sep 25 13:24:38 ldap ./passwdExpiry.sh[32419]: --- Statistics ---
Sep 25 13:24:38 ldap ./passwdExpiry.sh[32419]: Users checked: 2
Sep 25 13:24:38 ldap ./passwdExpiry.sh[32419]: Account expired: 0
Sep 25 13:24:38 ldap ./passwdExpiry.sh[32419]: Account in warning: 0

 

Run this script as part of cron on daily bases. Based on pwdExpireWarning set as part of ppolicy overlay, user will get notification when the password reaches the threshold period. In this example, we have set the warning period to 7 days before the password expires. Refer ppolicy to get a better context on this.

In the crontab entry, add the below content

00 12 * * * <path to script>/checkLdapPwdExpiration.sh 1>>/var/log/openldap/result.log 2>>/var/log/openldap/audit.log

The cron entry will run the script everyday once at 12 pm

 

Unlock  password

For unlocking password, you just simply need to delete the pwdAccountLockedTime attribute which unlocks the account immediately.

dn: cn=<user id>,ou=people,dc=devopsideas,dc=com
changetype: modify
delete: pwdAccountLockedTime

Running ldap modify command with above content replacing the correct user id will remove the pwdAccountLockedTime attribute if set.

 

LDAP Backup

Backup is pretty straighforward in LDAP. Using slapcat command against a specific DB will create an LDIF file containing all the data.

Create a file named ldapbackup.sh with the below content

#!/bin/bash

BACKUP_PATH=/root/backup
SLAPCAT=/usr/sbin/slapcat
#today=`date +%Y-%m-%d.%H:%M:%S`

nice ${SLAPCAT} -n 0 > ${BACKUP_PATH}/config.ldif
nice ${SLAPCAT} -n 1 > ${BACKUP_PATH}/devopsideas.com.ldif
nice ${SLAPCAT} -n 2 > ${BACKUP_PATH}/access.ldif
chmod 640 ${BACKUP_PATH}/*.ldif

Create a cron entry to run the backup once every day or based on your need. Make sure the path where you are storing the backup files are encrypted. You can make use of encrypted filesystem for this. Also once backed up, you can make use of this backup strategy to store the files in AWS S3.

 

With this we have successfully implemented all the use cases that was described in the scenario. This concludes the openLDAP series.

Feel free to provide your comments/suggestions if any in the comment section below or through mail ( [email protected]).

Series Navigation<< openLDAP – Basic Authentication using LDAP

DevopsIdeas

Established in 2016, a community where system admins and devops practitioners can find useful in-depth articles, latest trends and technologies, interview ideas, best practices and much more on Devops

Any Udemy course for $9.99 REDEEM OFFER
Any Udemy course for $9.99 REDEEM OFFER