 |
HOWTO create a safe directory to store files in a secure way at run- and downtime. |
|
|
Chapter Adjusting the PAM configuration |
|
Adjusting PAM configuration
I've used pam_script in the auth and in the session part of the pam (login,kde) service file.
First I describe howto adjust the auth part, where pam_script is used more than once.
The auth part
Pam_script has the ability (from version 0.1.5) to get the password provided at login, and make
this via an evironmentvariable PAM_AUTHTOK available to scripts.
The purpose here is to create a safe directory where confidential information (like credentials) are stored.
The module is stacked in the authpart:
cat /etc/pam.d/login
-- snip --
auth required pam_shells.so
auth required pam_script.so expose=1
auth sufficient pam_unix.so use_first_pass
auth required pam_script.so onauth=/etc/security/onauth.ldap
auth sufficient pam_ldap.so use_first_pass
auth required pam_script.so onauth=/etc/security/onauth.failed
auth required pam_deny.so
|
As you can see I use pam_scripts.so multiple times:
- The first time to run a script which creates a encrypted directory (with encfs) and to
write the secret password to a file in this directory for use by credential
sensitive programs like fusesmb and mount.cifs. This is done for the first
auth module following, pam_unix.
- The second time to run a script which adjusts this encrypted directory to store the password in a
ldap-subdirectory.
- The last time to run a script when authentication is not succesfull. When the preceding authmodules
fail (pam_unix and pam_ldap) (and only then) this module is reached. It's neccasary to unmount the encrypted directory
and to remove temporary files.
- Note that the last module of all is pam_deny, is really neccasary. Without it
everybody is able to login. This is because pam_script always returns "PAM_SUCCESS",
no mather what the return value is of the scripts.
Do not forget to add the flag "use_first_pass" to the existing module pam_unix.so.
-
The onauth script
cat >> /etc/security/onauth << "EOF"
#!/bin/bash
retcode=0;
userid=$1
service=$2
authtok=$3
if [ -z "$authtok" ]; then
authtok=$PAM_AUTHTOK
fi;
call4db=$(basename $0 | cut -s -d "." -f 2)
if [ -z "$call4db" ]; then
# default
call4db="local"
fi;
userproperties=$(getent passwd | grep -m 1 -E "^$userid")
if [ -z "$userproperties" ]; then
#
# userproperties not found: something wrong
#
echo "User not found."
exit
fi;
homedir=$(echo $userproperties | cut -d ":" -f 6);
gidnr=$(echo $userproperties | cut -d ":" -f 4);
uidnr=$(echo $userproperties | cut -d ":" -f 3);
if [ -d /var/lib/encfs ]; then
# create a safe
if [ ! -d /var/lib/encfs/$userid ]; then
install -m700 -o $uidnr -g $gidnr -d /var/lib/encfs/$userid
fi;
if [ ! -d /var/lib/encfs/$userid/encrypted ]; then
install -m700 -o $uidnr -g $gidnr -d /var/lib/encfs/$userid/encrypted
fi;
if [ ! -d /var/lib/encfs/$userid/unencrypted ]; then
install -m700 -o $uidnr -g $gidnr -d /var/lib/encfs/$userid/unencrypted
fi;
if [ ! -d /var/lib/encfs/$userid/run ]; then
install -m700 -o $uidnr -g $gidnr -d /var/lib/encfs/$userid/run
fi;
#
# test the encrypted directory is not already mounted
#
if [ -z "$(mount | grep -w /var/lib/encfs/$userid/unencrypted )" ]; then
#
# create a password-provide-program
#
cd /var/lib/encfs/$userid
md5authsum=$(echo $authtok | md5sum | cut -d " " -f 1)
echo "$md5authsum" > run/tmp
echo "$md5authsum" >> run/tmp
chown $uidnr:$gidnr run/tmp
rm -rf encrypted/*
rm -f encrypted/.encfs*
cat run/tmp | encfs -S /var/lib/encfs/$userid/encrypted /var/lib/encfs/$userid/unencrypted -- -o allow_root 1>>/dev/null
fi;
#
# this is what's all about: storing the credentials in a file
# in this case the password
#
if [ -n "$(mount | grep -w /var/lib/encfs/$userid/unencrypted )" ]; then
cd /var/lib/encfs/$userid/unencrypted/
if [ ! -d $service ]; then
install -m 700 -o $uidnr -g $gidnr -d $service
fi;
cd $service
if [ ! -d $call4db ]; then
install -m 700 -o $uidnr -g $gidnr -d $call4db
fi;
if [ -d $latest ]; then
if [ -f latest/password.tmp ]; then
rm latest/password.tmp
fi;
rm -rf latest
fi;
ln -sf $call4db latest
echo $authtok > latest/password.tmp
fi;
fi;
exit $retcodeEOF
|
Some remarks:
This scripts creates a encrypted directory (if it does not already exists) with encfs. Note the -S option at encfs:
the password for this encrypted directory is read from stdin and not propmted for.
This password is the same as used at login.
The encrypted directory is at /var/lib/encfs/$userid/$service.
The password is written to a file, password.tmp. This is a temporary file: the password does not have to be the correct one. Later
in the process, depending on the success of the validation this file is renamed (to password) or removed.
Note also the common fuse option allow_root: this allows the scripts run by kdm (see at this sollution, mount cifs shares for example) to enter
this directory and to read the stored password and to create other files.
-
The onauth.ldap script
The /etc/security/onauth.ldap script is a symbolic link to the onauth script:
cd /etc/security
ln -sf onauth onauth.ldap
|
Some remarks:
The script onauth knows how it is called, and changes it actions accordingly.
-
The onauth.failed script
cat >> /etc/security/onauth.failed << "EOF"
#!/bin/bash
retcode=1;
userid=$1
service=$2
userproperties=$(getent passwd | grep -m 1 -E "^$userid")
if [ -z "$userproperties" ]; then
#
# userproperties not found: something wrong
#
echo "User not found."
exit
fi;
homedir=$(echo $userproperties | cut -d ":" -f 6);
gidnr=$(echo $userproperties | cut -d ":" -f 4);
uidnr=$(echo $userproperties | cut -d ":" -f 3);
#
# this script is run when the authentication failed
# a safe encrypted is still created
# so it's important to remove this safe again
#
if [ -d /var/lib/encfs ]; then
if [ -d /var/lib/encfs/$userid/unencrypted ]; then
#
# test the encrypted directory is not already mounted
#
if [ -n "$(mount | grep -w /var/lib/encfs/$userid/unencrypted )" ]; then
if [ $(w -h $userid | wc -l) -eq 0 ]; then
#
# this user is not logged in on more tty's
# just remove everything and umount the encrypted directory
#
rm -rf /var/lib/encfs/$userid/unencrypted/*
fusermount -u /var/lib/encfs/$userid/unencrypted
rm -rf /var/lib/encfs/$userid/encrypted/*
rm -f /var/lib/encfs/$userid/encrypted/.encfs*
else
#
# this user is still logged in
#
rm -f /var/lib/encfs/$userid/unencrypted/$service/latest/password.tmp
fi;
fi;
if [ -z "$(mount | grep -w /var/lib/encfs/$userid/unencrypted )" ]; then
rm -rf /var/lib/encfs/$userid/encrypted/*
rm -f /var/lib/encfs/$userid/encrypted/.encfs*
rm -rf /var/lib/encfs/$userid/unencrypted/*
fi;
fi;
fi;
exit $retcodeEOF
|
Some remarks:
The session part
When the session part is reached, it's sure that the credentials provided (password) are correct. This
means that the password - in the the auth fase stored in a temporary file - is correct. So one thing to do in the
session fase is to move the contents of the password.tmp to the permanent one, password.
A second thing is running script which need these credentials for own use, like mounting CIFS shares or fusesmb.
It's logical that any confidential information stays inside the safe directory. That's what it was all about in the
first place!!
Note that this modules runs two scripts on default:
. /etc/security/onsessionopen : when a session start/opens;
. /etc/security/onsessionclose : when a session ends/closes.
I follow the default, there is no reason to do otherwise.
My /etc/pam.d/login file (the sessionpart) looks like:
cat /etc/pam.d/login
-- snip --
session required pam_mkhomedir.so
session required pam_motd.so
session optional pam_mail.so empty dir=/var/mail
session optional pam_lastlog.so
session required pam_env.so
session required pam_script.so
session required pam_unix.so
session required pam_ldap.so
|
-
The onsessionopen script
cat >> /etc/security/onsessionopen << "EOF"
#!/bin/bash
retcode=0;
userid=$1
service=$2
userproperties=$(getent passwd | grep -E "^$userid")
if [ -z "$userproperties" ]; then
#
# userproperties not found: something wrong
#
echo "User not found."
exit
fi;
homedir=$(echo $userproperties | cut -d ":" -f 6);
gidnr=$(echo $userproperties | cut -d ":" -f 4);
uidnr=$(echo $userproperties | cut -d ":" -f 3);
if [ -d /var/lib/encfs/$userid/encrypted ]; then
#
# test the encrypted directory is mounted
#
if [ -n "$(mount | grep -w /var/lib/encfs/$userid/unencrypted )" ]; then
if [ -f /var/lib/encfs/$userid/unencrypted/$service/latest/password.tmp ]; then
if [ -f /var/lib/encfs/$userid/unencrypted/$service/latest/password ]; then
#
# an old passwordfile found
#
if [ -z "(diff /var/lib/encfs/$userid/unencrypted/$service/latest/password /var/lib/encfs/$userid/unencrypted/$service/latest/password.tmp)" ]; then
#
# new password and old one are the same
#
rm /var/lib/encfs/$userid/unencrypted/$service/latest/password.tmp
else
mv /var/lib/encfs/$userid/unencrypted/$service/latest/password.tmp /var/lib/encfs/$userid/unencrypted/$service/latest/password
fi;
else
#
# password not found : it's the first login.
# just move the temporary passwordfile to the remaining
#
mv /var/lib/encfs/$userid/unencrypted/$service/latest/password.tmp /var/lib/encfs/$userid/unencrypted/$service/latest/password
fi;
fi;
if [ -d /etc/session.d/pam/onsessionopen ]; then
for script in /etc/session.d/pam/onsessionopen/*.sh; do
if [ -x $script ]; then
eval $script $userid $service
fi
done;
fi;
fi;
fi;
exit $retcode
EOF
|
Some remarks:
-
The onsessionclose script
cat >> /etc/security/onsessionclose << "EOF"
#!/bin/bash
retcode=0;
userid=$1
service=$2
userproperties=$(getent passwd | grep -E "^$userid")
if [ -z "$userproperties" ]; then
#
# userproperties not found: something wrong
#
echo "User not found."
exit
fi;
homedir=$(echo $userproperties | cut -d ":" -f 6);
gidnr=$(echo $userproperties | cut -d ":" -f 4);
uidnr=$(echo $userproperties | cut -d ":" -f 3);
#
# this script is run when the authentication failed
# a safe encrypted is still created
# so it's important to remove this safe again
#
if [ -d /var/lib/encfs ]; then
if [ -d /var/lib/encfs/$userid/unencrypted ]; then
#
# test the encrypted directory is not already mounted
#
if [ -n "$(mount | grep -w /var/lib/encfs/$userid/unencrypted )" ]; then
# if [ $(w -h $userid | wc -l) -eq 0 ]; then
rm -rf /var/lib/encfs/$service.$userid/unencrypted/*
fusermount -u /var/lib/encfs/$userid/unencrypted
rm -rf /var/lib/encfs/$userid/encrypted/*
rm -f /var/lib/encfs/$userid/encrypted/.encfs*
# fi;
else
rm -rf /var/lib/encfs/$userid/encrypted/*
fi;
if [ -d /etc/session.d/pam/onsessionclose ]; then
for script in /etc/session.d/pam/onsessionclose/*.sh; do
if [ -x $script ]; then
eval $script $userid $service
fi
done;
fi;
fi;
fi;
exit $retcode
EOF
|
Some remarks:
. important: early versions of shadow (where the login program is a part of) did not close sessions
on default (versions prior to 4.0.12). You'll have to add :
to the /etc/login.defs file.
This option is not documented, and not present in the login.defs file installed by the Shadow package. You'll have to add
it yourself.
In newer versions this option is removed: the session is always closed.