In my previous post, I detailed how I set up an LUKS-encrypted filesystem on a loop device (a.k.a. sparse disk image file). To make automated backups easier and not have to add commands in my backup script to handle the mounting and unmounting of the disk image, I set up automount to:

  1. Mount the SMB share when accessing the /cifs directory
  2. Attach a loop device and set up LUKS filesystem access when accessing the /encrypted directory

Following along with the examples in my previous posts, I have a SMB share available at //lacie-2big/backup with the encrypted disk image linuxbackup.sparseimage sitting on it. My goal was to be able to do this:

$ ls /encrypted/linuxbackup

and for automount to auto-magically mount the SMB share and then mount the encrypted disk image. Unfortunately I could not find a way for automount to handle this "cascading" or "recursive" mount operation. Instead I have to execute two commands:

$ ls /cifs/lacie-2big/backup
linuxbackup.sparseimage
$ ls /encrypted/linuxbackup
hda1  hda2  hda3  hda6  hda7  lost+found

Still, two simple ls commands are better than the 4 hard-to-remember mount, losetup, cryptsetup, and mount commands needed to do this manually. And with the help of a small script along with what automount provides, I don't have to worry about the 4 hard-to-remember umount, cryptsetup, losetup, umount commands needed to unmount everything when I'm finished.

Automount CIFS mount script

I found online an example automount script based from the auto.smb one that comes in many distributions called auto.cifs that will work with SMB mounts requiring authentication. That script looks like:

#!/bin/bash
# $Id$
# This file must be executable to work! chmod 755!
key="$1"
# Note: create a cred file for each windows/Samba-Server in your network
#       which requires password authentification.  The file should contain
#       exactly two lines:
#          username=user
#          password=*****
#       Please don't use blank spaces to separate the equal sign from the
#       user account name or password.
credfile="/etc/auto.smb.$key"
# Note: Use cifs instead of smbfs:
mountopts="-fstype=cifs,file_mode=0644,dir_mode=0755,uid=root,gid=wheel"
smbclientopts=""
for P in /bin /sbin /usr/bin /usr/sbin
do
        if [ -x $P/smbclient ]
        then
                SMBCLIENT=$P/smbclient
                break
        fi
done
#echo $SMBCLIENT >&2
[ -x $SMBCLIENT ] || exit 1
if [ -e "$credfile" ]
then
        mountopts=$mountopts",credentials=$credfile"
        smbclientopts="-A "$credfile
else
        smbclientopts="-N"
fi
#echo $smbclientopts -gL $key >&2
$SMBCLIENT $smbclientopts -gL $key 2>/dev/null \
   | awk -v key="$key" -v opts="$mountopts" -F'|' -- '
        BEGIN   { ORS=""; first=1 }
	/Disk/  { if (first) { print opts; first=0 };
		  gsub(/ /, "\\ ", $2);
		  sub(/\$/, "\\$", $2);
		  print " \\\n\t /" $2, "://" key "/" $2 }
        END     { if (!first) print "\n"; else exit 1 }
        '

I created that as /etc/auto.cifs and made it executable. Then in /etc/auto.master I added this line:

/cifs /etc/auto.cifs --timeout=60

The credentials for mounting the SMB share are then stored in a /etc/auto.smb.lacie-2big file, which is how I set things up originally in my last post. Now, after reloading the autofs service, I am able to:

$ ls /cifs/lacie-2big/backup
linuxbackup.sparseimage

Automount LUKS script

Then I created another automount script /etc/auto.luks.loop.0:

#!/bin/bash
# This file must be executable to work! chmod 755!
# Make links to this file with the last digit replaced
# with other numbers for corresponding loop devices,
# e.g. if this script is executed as auto.luks.loop.1 the 
# /dev/loop1 device will be used.
#
# The LUKS key must exist as a file at /etc/.key

key="$1"
los=""
cry=""
name=`basename $0`
l=${name##*.}
img="/cifs/lacie-2big/backup/$key.sparseimage"
mountopts="-fstype=ext3,defaults,noatime,nodiratime"

if [ ! -e "/etc/$key.key" ]; then
	exit 1
fi

# search for losetup and cryptsetup
for P in /bin /sbin /usr/bin /usr/sbin
do
	if [ -z "$los" -a -x $P/losetup ]; then
		los=$P/losetup
	fi
	if [ -z "$cry" -a -x $P/cryptsetup ]; then
		cry=$P/cryptsetup
	fi
	if [ -n "$los" -a -n "$cry" ]; then
		break
	fi
done

# check if loop device already attached, if not then attach it
chk=`$los -a |grep /dev/loop$l`
if [ -z "$chk" ]; then
	if [ ! -e $img ]; then
		echo "Image file $img not found." >&2
		exit 1
	fi
	$los /dev/loop$l $img
	$cry --key-file /etc/$key.key luksOpen /dev/loop$l \
		luks-`$cry luksUUID /dev/loop$l` >/dev/null 2>&1
fi

echo $mountopts / :/dev/mapper/luks-`$cry luksUUID /dev/loop$l`

This script is hard-coded to look for disk image files in /cifs/lacie-2big/backup named imagefilename.sparseimage where filename will be the automount directory name. The script uses the last number of the script name to determine which loop device to use, although it could be easily adapted to use any freely-available device (by way of losetup -f).

Then it uses cryptsetup to open the LUKS device, using a key file located at /etc/imagefilename.key. Then it configures automount to mount the LUKS filesystem. In short, the script basically does:

$ losetup /dev/loop0 /cifs/lacie-2big/backup/linuxbackup.sparseimage
$ cryptsetup --key-file /etc/linuxbackup.key luksOpen /dev/loop0 \
	luks-`cryptsetup luksUUID /dev/loop0`
$ echo -fstype=ext3,defaults,noatime,nodiratime \
	/ :/dev/mapper/luks-`cryptsetup luksUUID /dev/loop0`

That last line is what is returned to automount and causes it to mount the image file as a directory named filename.

Finally in /etc/auto.master I added this line:

/encrypted /etc/auto.luks.loop.0 --timeout=600

Afer having the autofs reload this configuration, I am then able to do this:

ls /encrypted/linuxbackup
hda1  hda2  hda3  hda6  hda7  lost+found

With the caveat that the /cifs/lacie-2big/linuxbackup directory is already mounted. This is where I couldn't find a way for automount to mount both file systems in one call.

Auto-umount

One final piece remains to be automated, however: completely unmounting both the encrypted and SMB file systems. automount will take care unmounting the LUKS encrypted filesystem after the configured period of inactivity. However, it won't be able to unmount the SMB filesystem because the auto.luks.loop.0 script has attached the /dev/loop0 device to the disk image file on that share. In effect, the SMB share is still in use.

It would be very nice if automount provided a way to execute scripts when it unmounted a filesystem. But it does not. My solution was to write a small script that runs every so often (via cron) that looks to see if any loop devices are attached to filesystems that are "not in use" and if found, detach them. Here's the script, which I have stored at /etc/auto.luks.loop.umount:

#!/bin/bash

los=""
cry=""

# search for losetup and cryptsetup
for P in /bin /sbin /usr/bin /usr/sbin
do
	if [ -z "$los" -a -x $P/losetup ]; then
		los=$P/losetup
	fi
	if [ -z "$cry" -a -x $P/cryptsetup ]; then
		cry=$P/cryptsetup
	fi
	if [ -n "$los" -a -n "$cry" ]; then
		break
	fi
done

for dev in `$los -a|cut -d: -f1`; do
	file=`$los -a|grep /dev/loop |sed 's/.*(\(.*\)).*/\1/'`
	dir=${file%/*}
	# check if the only open file on loopback file's filesystem 
	# is from automount, if so close loopback device so automount
	# can un-mount filesystem for us later
	match=`lsof |grep $dir|cut -d" " -f1 |grep -v automount`
	if [ -n "$match" ]; then
		echo "Loop device $dev attched to in-use filesystem $dir" >&2
	else
		echo "Loop device $dev on unused filesystem $dir"
		$cry isLuks $dev 2>/dev/null
		if [ "$?" -eq "0" ]; then
			echo "Closing LUKS on $dev"
			$cry luksClose luks-`$cry luksUUID $dev`
		fi
		echo "Unattaching loop device $dev"
		$los -d $dev
	fi
done

It's not particularly clever. I'm sure it would not work on systems using loop devices for other things that what I'm using them for on my system and it's making assumptions on the output of losetup. But it does the job nicely for what I need. I have this run every so often via cron. After it runs, and if it found any loop devices it could detach, then automount will eventually unmount the SMB share for us and we're done.

These scripts can be obtained via anonymous CVS:

cvs -d :pserver:anonymous@msqr.us:/data/cvs co twobig