Creating A Safe Directory With PAM And EncFS
Contents
. Introduction
. Installation of encfs
. Installation of pam_script
. Adjusting PAM configuration
. The result
Introduction
I do work a lot with programs with require credentials
Examples of those programs are:
. mount.cifs
. fusesmb (look for details here)
Now, in my network (and others) the credentials provided at login could (and should) be used by those programs. How can you retrieve these credentials, providing enough security?
With a the PAM modules pam_script it's possible to store the password in a file, which will be used by fusemb and mount.cifs to read the password from.
To achieve security, one could make the user logging in owner and deny read/write for anybody else. Remove this file when the user ends his/her session.
This is enough, for runtime. But I was wondering, but what if the system crashes, and the file with the credentials remains on the harddrive? Anybody who is able to mount this harddrive with for example a lifecd, can read this file!
That's why I was looking for a was to encrypt this file.
With encfs this is very possible! At run time it gives an interface to encrypted files and directories, which does only exist at runtime! When the system is not running, there are only encrypted files, useless when you do not know the key to it. And this key is exactly the (encrypted) password! That's why I've chosen for a combination of PAM and Encfs.
Look for the website of Encfs: http://freshmeat.net/projects/encfs
This construction is intended to give enough security for run- and downtime (after a crash) to store sensitive information, not for creating a permanent safe directory on your harddrive to store documents.
Installation of encfs
Of course FUSE should be installed.
Installation is very straight forward:
(look at the website for rlog which is required by EncFs)
tar -xzf encfs-*.tar.gz
cd encfs-*
configure --prefix=/usr --sysconfdir=/etc --libexecdir=/usr/sbin
make
make install
After installation you can test if it works:
mkdir -p ~/test/encrypted
mkdir -p ~/test/decrypted
encfs ~/test/encrypted ~/test/decrypted
mount (should show the just created mount)
echo "This is very secret." > ~/test/decrypted/testfile
The directory encrypted contains the encrypted files, and remains on the harddisk after unmounting and/or shutting down. The directory decrypted is the interface to it, and disappears when unmounted and/or shutting down.
The file testfile should appear in the decrypted directory, and in encrypted form in the encrypted directory. Name and content of the file are encrypted.
I've chosen for a seperate map in the local machine where for every user logging in a encrypted (and an interface to it) will be stored:
install -m777 -o root -g root /var/lib/encfs
Note the permissions: everybody must be able to create a directory here. Later in this document is explained why.
Installation of pam_script
Installation is very simple. After a make command move the library to the proper directory, /lib/security.
tar -xzf pam-script-*.tar.gz
cd pam-script-*
make
mv pam_script.so /lib/security
chown root:root /lib/security/pam_script.so
chmod 755 /lib/security/pam_script.so
Pam_script.so uses some parameters. All of them are described in the README in the source directory. Important are:
common parameters
- runas=#user# : makes the script called run as user #user#
parameters only in the auth part
- onauth=/path/to/onauth/script : path to script which is run when in auth part
default is /etc/security/onauth
only in the session part
- onsessionopen=/path/to/onsessionopen/script : path to script which is run when a (valid)session is started
default is /etc/security/onsessionopen
- onsessionclose=/path/to/onsessionclose/script : path to script which is run when a session is closed
default is /etc/security/onsessionclose
After installation the more complex thing is to configure the system to make use of this module.
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 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 an 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 just before the first auth module following, pam_unix.
- 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 matter 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 scripts
cat /etc/security/onauth
#!/bin/bash retcode=0; userid=$1 service=$2 authtok=$3 if [ -z "$authtok" ]; then authtok=$PAM_AUTHTOK 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 [ -f password.tmp ]; then rm password.tmp fi; echo $authtok > password.tmp fi; fi;
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.
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.
Very important to notice is that the script is executed as the user logging in, not as root! And because programs like mount.cifs later need to have access to this directory to read the password file, the common fuseoption allow_root is added.
#!/bin/bash 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/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;
Some remarks:
This script is executed when the credentials provided are not valid. The encrypted directory, which has just been setup, wil be removed (unmounted) again and cleared. This is of course only when this user is not logged on any other tty.
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 scripts 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 scripts
On session open
cat /etc/security/onsessionopen
#!/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/password.tmp ]; then if [ -f /var/lib/encfs/$userid/unencrypted/password ]; then # # an old passwordfile found # if [ -z "(diff /var/lib/encfs/$userid/unencrypted/password /var/lib/encfs/$userid/unencrypted/password.tmp)" ]; then # # new password and old one are the same # rm /var/lib/encfs/$userid/unencrypted/password.tmp else mv /var/lib/encfs/$userid/unencrypted/password.tmp /var/lib/encfs/$userid/unencrypted/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/password.tmp /var/lib/encfs/$userid/unencrypted/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;
On session close
cat >> /etc/security/onsessionclose
#!/bin/bash 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 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;
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 :
CLOSE_SESSIONS yes
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.
The result
The encrypted directory contains now the credentials, which are only available to the owner and root. By creating a file mount.cifs.conf with these values:
touch /var/lib/encfs/$userid/unencrypted/mount.cifs.conf
chmod 600 /var/lib/encfs/$userid/unencrypted/mount.cifs.conf
echo "username=$userid" > /var/lib/encfs/$userid/unencrypted/mount.cifs.conf
echo -n "password=" >> /var/lib/encfs/$userid/unencrypted/mount.cifs.conf
cat /var/lib/encfs/$userid/unencrypted/password >> /var/lib/encfs/$userid/unencrypted/mount.cifs.conf
Now mounting a cifs share is possible with:
/sbin/mount.cifs //fileserver/public /home/sbon/netshares/fileserver/public -o credentails=/var/lib/encfs/sbon/unencrypted/mount.cifs.conf,ip=192.168.0.2
where the directory /home/sbon/netshares/fileserver/public does exist, and therse is a share "public" available on "fileserver", a smb/cifs server with ipnumber 192.168.0.2.
This command must be run as root. Root needs to have access to the encrypted directory.
An other example is fusesmb, which can use credentials to browse the smb networkneighbourhood. This is not done by creating a seperate file for the credentials only, but in the global-per-user configurationfile of fusesmb in ~/.smb/fusesmb.conf:
(here I create a simple configuationfile with credentials only)
touch /var/lib/encfs/sbon/unencrypted/fusesmb.conf
chmod 600 /var/lib/encfs/sbon/unencrypted/fusesmb.conf
echo "[global]" > /var/lib/encfs/sbon/unencrypted/fusesmb.conf
echo "username = sbon" >> /var/lib/encfs/sbon/unencrypted/fusesmb.conf
echo -n "password = " >> /var/lib/encfs/sbon/unencrypted/fusesmb.conf
cat /var/lib/encfs/sbon/unencrypted/password >> /var/lib/encfs/sbon/unencrypted/fusesmb.conf
ln -sf /var/lib/encfs/sbon/unencrypted/fusesmb.conf /home/sbon/.smb/fusesmb.conf
The last link is because fusesmb (started by sbon) expects the configuration file there.
Now start it with:
fusesmb /home/sbon/network
when I'm logged in as myself (sbon). The directory /home/sbon/network has to exist.