Friday, July 12, 2013

Local-disk encryption - LUKS and OPAL - to protect against casual privacy loss

This is a detailed continuation of one of my previous posts.

Scope

In my case, I use local-disk encryption to guard against privacy loss when RMA'ing broken disks or if (not: "when", hopefully) my equipment gets stolen. The scope is not to guard against overly-intrusive governments; If a government wants my data, and a judge forces me to turn over my encryption keys, there is little one can do.

The setup

My home server is an Ivy-Bridge based Core-i7 system that supports VT-d. The actual hardware runs Xen 4.2, and there is a small Dom0 Linux system that handles a single SSD to store its own system and the guest disk images. A nice property of the CPU in question is that it supports AES-NI, so that it can do in-CPU AES encryption at some 500 Mbyte/s/core.


The DomU file server

The DomU file server runs Ubuntu 13.10 server on which I run native ZFS-on-Linux to manage the data.


The disks

The file-server disks all hang off a SAS controller that I pass through to the DomU file server using VT-d (I disable that controller in Dom0, so that Dom0 never sees more than its own single SSD that hang off the on-board SATA3 controller). There are five disks:
  • Four 2-Tbyte old-school hard drives forming a single RAIDZ2 vdev in a zpool.
  • A single SSD that I use as an L2ARC cache device in the same zpool.
The "old-school" hard drives support no native encryption options at all, so we will use LUKS to encrypt those. The SSD (an Intel 520) is a native Self-Encrypting Drive (SED) OPAL compliant device that can be encrypted simple by setting an ATA password (and which can be utterly wiped in one second by resetting that password).


The boot context

The file-server's root "disk" is an image on the Dom0 system, on which I use encrypted LVM (which, internally, uses LUKS as well). This allows me to put a "clear-text" automatic-booting key on the server's root filesystem (since that is encrypted). This allows me to start the file server from Dom0, attach to its console, and enter a single password (to unlock the server's system LVM), after which I use the key stored in "clear text" within that LVM to automatically unlock all other drives. Since LUKS allows one to define more than one key for a volume, I do not put the key in clear-text on the Dom0 (that would defeat the purpose). Rather, I will add a key that exists only in my mind.


Setting up the encryption

I will assume that a DomU has already been installed on encrypted LVM; the Ubuntu installer image makes this very easy, so I am not going to elaborate on that. I will also assume that the kernel-module PPA from ZFS-on-Linux has been installed already.


Defining an encryption key


You can use any method here, but I am using a 32-byte hex key (and no more than 32 bytes, since the ATA password cannot be any longer). I put that key in /root/key.txt.

Setting up the LUKS devices

WARNING: The disks in question will be wiped. Your old data will be gone. Forever. Make backups beforehand.

NOTE: I always refer to disks in /dev/disk/by-id, rather than just /dev/sd?, since at each boot the disks may appear in a different order in /dev, whereas the names in /dev/disk/by-id are always the same.

I start by becoming root and cd-ing to /dev/disk/by-id. I then set up encryption for the four data drives using cryptsetup:

# cryptsetup luksFormat -d /root/key.txt ata-WDC_WD2002FYPS-1

# cryptsetup luksFormat -d /root/key.txt ata-WDC_WD2002FYPS-2

# cryptsetup luksFormat -d /root/key.txt ata-WDC_WD2002FYPS-3

# cryptsetup luksFormat -d /root/key.txt ata-WDC_WD2002FYPS-4

Also, I add my "in-mind-only" key to each of them:

# cryptsetup luksAddKey -d /root/key.txt ata-WDC_WD2002FYPS-1

# cryptsetup luksAddKey -d /root/key.txt ata-WDC_WD2002FYPS-2

# cryptsetup luksAddKey -d /root/key.txt ata-WDC_WD2002FYPS-3

# cryptsetup luksAddKey -d /root/key.txt ata-WDC_WD2002FYPS-4

Cryptsetup will prompt you for that key in each case. Now, we unlock each of the drives. In order to prevent confusion, I encrypted-device name I choose is the same as the actual device name:

# cryptsetup luksOpen -d /root/key.txt ata-WDC_WD2002FYPS-1 ata-WDC_WD2002FYPS-1

# cryptsetup luksOpen -d /root/key.txt ata-WDC_WD2002FYPS-2 ata-WDC_WD2002FYPS-2

# cryptsetup luksOpen -d /root/key.txt ata-WDC_WD2002FYPS-3 ata-WDC_WD2002FYPS-3

# cryptsetup luksOpen -d /root/key.txt ata-WDC_WD2002FYPS-4 ata-WDC_WD2002FYPS-4

Now, we have the same names available in /dev/mapper.

Setting up the OPAL SED device

Although I am not sure whether this step is needed, I perform an initial secure erase anyway. Again, I am going to assume that you are root in /dev/disk/by-id. We start by setting the key:

# hdparm --security-set-pass `cat /root/key.txt` ata-INTEL_SSD1

Then, we issue the enhanced secure erase. Since this only changes the encryption key, this takes under a second:

# hdparm --security-erase-enhanced `cat /root/key.txt` ata-INTEL_SSD1

All data is from the SSD is now irrevocably gone. You did take my advice on making backups, eh? Good. We now set up the encryption again:

# hdparm --security-set-pass `cat /root/key.txt` ata-INTEL_SSD1

And that's it.

Setting up the zpool

With the encrypted devices now in place, we can set up the pool. Data first:

# cd /dev/mapper
# zpool create tank raidz2 ata-WDC_WD2002FYPS-1 ata-WDC_WD2002FYPS-2 ata-WDC_WD2002FYPS-3 ata-WDC_WD2002FYPS-4

And then the L2ARC cache device:

# cd /dev/disk/by-id
# zpool add tank cache ata-INTEL_SSD1

We now have our pool up and running. However, we will see none of these devices after a reboot. Therefore, the next section is on getting things unlocked at the right point during boot-up.

Setting up automatic unlocking

As I mentioned before, entering the file-server's root-LVM key will have to be done by hand (otherwise, anyone booting Dom0 would have access to my data anyway). However, we want all of the other disks to be set up automatically.

A slightly irritating fact in that regard is the fact that the current ZFS-on-Linux implementation suffers from some race conditions in the Ubuntu Upstart phase (Ubuntu's parallel init-script execution). In particular, it is almost impossible to run the drive unlocking during Upstart in such a way that the devices are all available when the ZFS module loads.

Therefore, we do the unlocking in an earlier phase: In the initial ramdisk, when the actual root filesystem has already been mounted, but Upstart has not started yet. As it turns out, the so-called "local-bottom" phase of the initial-ramdisk execution is just the place where both of these conditions are satisfied.

Preparations for LUKS


Even though I will make sure that my devices are unlocked before running the normal Upstart script that reads /etc/crypttab, I will put my LUKS-device definitions there anyway; Upstart does not get confused by this (it will just see that the devices are already unlocked, and leave it at that), and it provides for a clean shutdown of the system. In my case, I append my data drives there:

ata-WDC_WD2002FYPS-1 /dev/disk/by-id/ata-WDC_WD2002FYPS-1 /root/key.txt luks
ata-WDC_WD2002FYPS-2 /dev/disk/by-id/ata-WDC_WD2002FYPS-2 /root/key.txt luks
ata-WDC_WD2002FYPS-3 /dev/disk/by-id/ata-WDC_WD2002FYPS-3 /root/key.txt luks
ata-WDC_WD2002FYPS-4 /dev/disk/by-id/ata-WDC_WD2002FYPS-4 /root/key.txt luks

Preparations for OPAL

For OPAL, I created a file called /etc/crypt_opal_devices.txt that contains the name of my OPAL device:

ata-INTEL_SSD1

This is a manual solution, but it will have to do.


Writing the initial-ramdisk configuration for the LUKS devices


Since I use a LUKS-encrypted root filesystem, the cryptsetup binary is already in the initial ramdisk. The remainder of the configuration consists of two items:

  1. Ensuring that I have a list of to-be-unlocked LUKS devices in a text file in my ramdisk (a "hook").
  2. Ensuring that this text file is used to issue to correct cryptsetup commands (a "script").

For the first item, I create the executable script /etc/initramfs-tools/hooks/early_luks_devices , containing the following code to automatically scrape together the list of devices from /etc/crypttab.

#!/bin/sh

set -e

PREREQ=""

prereqs () {
        echo "${PREREQ}"
}

case "${1}" in
        prereqs)
                prereqs
                exit 0
                ;;
esac

. /usr/share/initramfs-tools/hook-functions

cat /etc/crypttab | grep ^ata- | tr '\t' ' ' | tr -s ' ' |  cut -d ' ' -f 1 > ${DESTDIR}/etc/cryptdevs

exit 0

For the second item, I contain the executable script /etc/initramfs-tools/scripts/local-bottom/unlock_luks_partitions , containing:

#!/bin/sh

set -e

case "${1}" in
        prereqs)
                exit 0
                ;;
esac

. /scripts/functions

for i in `cat /etc/cryptdevs`; do
  log_success_msg Unlocking ${i} ...
  /sbin/cryptsetup luksOpen -d ${rootmnt}/root/key.txt /dev/disk/by-id/${i} ${i}
done;

exit 0

This is all that is needed for the LUKS devices.

Writing the initial-ramdisk configuration for the OPAL device

Here, we need the following three items:
  1. We need to include /sbin/hdparm in the ramdisk.
  2. We need a list of to-be-unlocked OPAL devices in the ramdisk (a "hook").
  3. We need to run the correct hdparm invocation to unlock the device (a "script").
For the first item, I create the executable script /etc/initramfs-tools/hooks/include_hdparm , containing:

#!/bin/sh

set -e

PREREQ=""

prereqs () {
        echo "${PREREQ}"
}

case "${1}" in
        prereqs)
                prereqs
                exit 0
                ;;
esac

. /usr/share/initramfs-tools/hook-functions

copy_exec /sbin/hdparm /sbin

exit 0

For the second item, I create the executable script /etc/initramfs-tools/hooks/early_opal_devices , containing:

#!/bin/sh

set -e

PREREQ=""

prereqs () {
        echo "${PREREQ}"
}

case "${1}" in
        prereqs)
                prereqs
                exit 0
                ;;
esac

. /usr/share/initramfs-tools/hook-functions

cp /etc/crypt_opal_devices.txt ${DESTDIR}/etc/crypt_opal_devices.txt 

exit 0

For the third item, I create the executable script /etc/initramfs-tools/scripts/local-bottom/unlock_opal_devices , containing:

#!/bin/sh

set -e

case "${1}" in
        prereqs)
                exit 0
                ;;
esac

. /scripts/functions

for i in `cat /etc/crypt_opal_devices.txt`; do
  log_success_msg Unlocking OPAL device ${i} ...
  /sbin/hdparm --security-unlock `cat ${rootmnt}/root/opal.txt` /dev/disk/by-id/${i}
done;

exit 0

And that's it.

Refreshing the actual initial ramdisk


NOTE: This is crucial!

You run the following simple command:

# update-initramfs -u

And you're all set. Enjoy!

No comments: