A Slick Option for Dynamically Mounting LUKS Encrypted External Hard Drives

Posted on 01/18/2016 by Brian Carey

In this post we will cover a method we developed years ago for a specific use case of dynamically mounting external USB hard drives that are encrypted via LUKS.  The need seemed simple enough at first.  A client was looking for an offsite backup solution.  After reviewing some options it was determined rather than the expense and complexity of a new tape system, we would leverage hard drives via an external USB enclosure to store offsite backups.  These drives would then be rotated offsite on a weekly basis just like tapes would be.  Oh, and some of the data will need to be encrypted for legal reasons.  After running through a few scenarios I quickly discovered that one of the biggest challenges was going to be reliably, and dynamically, un-mounting and re-mounting the external drives that were encrypted.  Below I will share the solution I came up with that has been working wonders ever since, and also share our script with you.

The Requirements

The following requirements were gathered when starting the project, some of which were requirements that I set for the solution since it needed to not be a an extremely "techie" solution that required me or another resource babysitting the mounts every week.

  • One or more USB drives will be connected via an external enclosure.  Some data will need to be encrypted so rather than worry what is and isn't required we'll just encrypt it all using LUKS.
  • The mounting and un-mounting of the drives must be automated and able to be scheduled for a specific day and time.
  • The process of plugging in drives should not require that any one drive need to be inserted in a specific slot so that a non technical person could complete it if needed.
  • Status notifications must be sent via email.  Failure notifications need to be sent to an additional email address for ticketing.
  • To ensure a simple recovery path of the backup server itself, the configuration and data required to rebuild the backup server will be stored unencrypted.  This is acceptable since it is not sensitive data. 

The Biggest Challenge

The biggest challenge to this turned out to be: How do you dynamically determine what encrypted partitions existed on what devices since they are...ENCRYPTED?  It turns out that the desire to have the configuration of the backup server itself backed up unencrypted helped solve this problem.  I soon realized that by having a small, unencrypted partition as the first partition with a labeled file system, I could use that device name to determine the physical device and then derive the encrypted device path.

Hard Drive setup

For the purposes of this post, I'm only going to cover configuring a single hard drive for use with our script.  However, the same process applies to however many disks you may have.  Furthermore, this example assumes your physical disk device is /dev/sdx.  

First, we will partition the disk using parted.  We need two partitions, first a tiny one then the remainder of the disk for the encrypted partition.

# parted -a optimal /dev/sdx
(parted) mklabel gpt
Warning: The existing disk label on /dev/sdx will be destroyed and all data on this disk will be lost. Do you want to continue?
Yes/No? Yes
(parted) unit MB
(parted) mkpart primary 0% 1GB
(parted) mkpart primary 1GB 100%
(parted) p
Model: ST2000DM 001-9YN164 (scsi)
Disk /dev/sdi: 2000399MB
Sector size (logical/physical): 512B/512B
Partition Table: gpt

Number  Start   End        Size       File system  Name     Flags
 1      1.05MB  1000MB     999MB                   primary
 2      1000MB  2000399MB  1999399MB               primary

Next, we format /dev/sdx1 as a standard EXT4 partition with a label, in this case test1.

# mkfs -t ext4 -L test1 /dev/sdx1

Now, if we reload our partitions, we can see that this partition shows up in /dev/disk/by-label as a symlink to our physical device/partition.  This is the key to using our script.

# partprobe
# ls -l /dev/disk/by-label/
total 0
lrwxrwxrwx 1 root root 10 Jan 18 20:06 test1 -> ../../sdx1

Next, we setup /dev/sdx2 as our LUKS encrypted partition.  I enter a dummy password when prompted.

# cryptsetup --cipher aes-cbc-essiv:sha256 --verbose --verify-passphrase luksFormat /dev/sdx2

Next, we add our encryption key.

# cryptsetup luksAddKey /dev/sdx2 /path/to/our/key

Next, we open the device created in the previous step in device mapper.

# cryptsetup luksOpen --key-file /path/to/our/key /dev/sdx2 test1

Now you can create a file system on your encrypted device, for example:

# mkfs -t ext4 /dev/mapper/test1

Once thats finished, close the encrypted device

# cryptsetup luksClose /dev/mapper/test1

Configuring and using our script

Our script can be downloaded from Github using the following links:

Script: https://raw.githubusercontent.com/kissit/kiss-ops/master/disk/luks_mounter.pl

Sample Config File: https://raw.githubusercontent.com/kissit/kiss-ops/master/disk/luks_mounter.ini

In terms of setup, simply download them to a common directory, say /usr/local/bin.  Make sure the perl script is executable.  The script requires some extra CPAN modules that are not typically included in a minimal Perl installation.  To install them on a CentOS 6.x system you can do the following:

yum install perl-MIME-Lite perl-Config-Simple

You now must update the configuration file to reflect your setup.  The configuration file is already well commented so I'm not going to cover it in detail here.  This sample is already configured for our example above.

The script has the following options:

# ./luks_mounter.pl --help
./luks_mounter.pl: Mount or unmount the devices as configured in the config file
 Usage: luks_mounter.pl --mount | --unmount [ --clean ] [ --config ]
    --mount         - Mount the devices in the config file
    --unmount       - Unmount the devices in the config file
    --clean         - Run the cleanups as configured (Default: no cleanups)
    --config        - Alternate config file (Default: ./luks_mounter.ini)
    --help          - This help page

To mount the configured disks, run the script with the --mount option.  If you included directories to be cleaned in the configuration, and want to do so, also include the --clean option.  This will delete the contents of the directories you have configured.  This is useful for completely removing the data on the disk when being mounted.

To unmount the configured disks, run the script with the --unmount option.

By default, the script will load the configuration file luks_mounter.ini from the same directory as the script is located.  If you wish to use an alternate location simply move the file as desired and pass the --config=/path/to/config/file.ini option when running the command.

Conclusion

In conclusion, I hope someone finds this post helpful.  I'm sure many will say why all the complexity just use XYZ but nothing I could find at the time provided a solution that met all of our requirements and worked so well.  This solution has been used for years without a hiccup.  Please reach out to us here with any questions or comments.