Alpine Linux Encrypted Disk Installation

Pursuing ever smaller size, many Docker images have changed their base image from Ubuntu to Debian GNU/Linux to Alpine Linux. I have built a few Alpine Linux based images here and there of my own and, upon finding out that it sports a desktop, figured I'd give it a try on an aging laptop.

I had one complicating requirement though. The install had to be on an encrypted disk. The whole installation process turned out to be fairly straightforward, despite that encryption requirement. It was just a bit of command-line work. So, for those not afraid of a CLI, read on!

Standard Hard-Disk Installation

Forgetting about the encryption requirement for a bit, installing Alpine Linux onto your hard disk can be as simple as

setup-alpine -m sys

after you boot from an ISO CD-image or USB stick. I used an USB stick to boot the alpine-3.4.4-x86_64.iso image. You can login as root without a password at the prompt.

The questions you will be asked by the program aren't too complicated if you've installed a Linux distribution before. What is of interest though, is the step-by-step breakdown of what this little program does. Looking through that information, it looks like running it in quick mode, with the -q option, does most of what needs to be done anyway but skips the setup of the disk. That's just what we need!

Encrypted Hard-Disk Installation

Okay then, let's setup-alpine, quickly!

setup-alpine -q
jp                   # keyboard
xeno                 # hostname
eth0                 # network interface
dhcp                 # dynamically configured IP address
done                 # skip the wlan interface for now
no                   # manual network configuration
                     # hit <Enter> and leave the root password empty
                     # confirm empty root password
none                 # proxy
                     # apk mirror, take your pick

I used an empty password for the root user as I intend to disable that account later. Feel free to use a password of your liking if you want to keep the account enabled.

Since quick mode skips this

setup-timezone -z Asia/Tokyo

Adjust that value to your situation of course. If you are not sure about the timezone you're in, just run the command without the -z option and its argument and select ? to get a list.

Now that we've got the basics out of the way, it's time to configure the disk. Most of what follows below is based on the LVM on LUKS information. There are scripts that claim to automate this a fair bit but I did things by hand.

Randomizing The Disk

Before setting things up for an encrypted hard-disk installation, it is a good idea to randomize the content of the target disk. This makes it harder for anyone without knowledge of the passphrase used for the encryption later on to access your data.

Warning

In case this isn't obvious, the following commands will completely and totally destroy whatever information is currently stored on the target disk, /dev/sda below.

My laptop has a 160GB hard disk so that requires a lot of random data. The /dev/urandom device will happily provide that but it can be a bit slow for lack of "entropy". You can speed up things with the help of haveged.

apk add haveged
haveged -n 0 | dd bs=64k of=/dev/sda

The bs=64k option to dd aims to improve writing performance a bit. Replace /dev/sda with whatever device you are targetting.

The randomization of my 160GB hard disk took about 45 minutes. I don't know how good the randomization is, but it's definetely better than all zero bytes (which you can get by piping the output of a cat /dev/zero invocation into the dd command if so inclined).

Most of us have no need for haveged installed on our hard disk. Run

apk del haveged

to remove it. All packages that you apk add before you do the installation to disk will end up on your disk.

Partitioning The Disk

With the disk content randomized, we are ready to partition. Ideally, you'd set up a single LVM partition and install there, but boot loaders typically work better when the /boot directory is not encrypted. So, we want to break out a smallish, unencrypted, bootable partition for /boot and stuff the rest in a large LVM partition.

Replace /dev/sda with your target device.

fdisk /dev/sda
n
p
1
             # accept the default by hitting <Enter>
+100M
a
1            # makes /dev/sda1 bootable
n
p
2
             # use the default → <Enter>
             # <Enter>
t
2
8e           # makes /dev/sda2 a Linux LVM partition
w

Running fdisk -l /dev/sda after this produces something like this

   Device Boot      Start         End      Blocks  Id System
/dev/sda1   *           1          13      104391  83 Linux
/dev/sda2              14       19457   156183930  8e Linux LVM

The exact Start, End and Blocks values will probably differ in your case but the important parts are in the Device, Boot, Id and System columns.

Linux Unified Key Setup With Cryptsetup

With the disk partitioned, we are ready to set up /dev/sda2 for use with encryption. Picking a set of suitable parameters to use in the cryptsetup command is not too difficult. You should at least run a cryptsetup benchmark to find a cipher that is fast at both encryption and decryption. At the same time, you should consider whether a longer key is worth a slight performance decrease. Longer keys usually mean the encryption is stronger. For more information, consult the cryptsetup manual page and dmcrypt wiki page.

Here's what I used to install all the packages needed (some we will use in later stages) and ran cryptsetup on /dev/sda2 to make it work with LUKS. The first cryptsetup command will ask if you are sure if you want to do this and, if so, to enter and confirm a passphrase. You will need this passphrase every time you boot the system so memorize it. The second cryptsetup command will ask you for the passphrase and make the /dev/sda2 partition available as /dev/mapper/lvmcrypt.

apk add cryptsetup lvm2 e2fsprogs syslinux
cryptsetup -v -c serpent-xts-plain64 -s 512 --hash whirlpool \
    --iter-time 5000 --use-random luksFormat /dev/sda2
cryptsetup open --type luks /dev/sda2 lvmcrypt

Setting Up Logical Volumes And File Systems

At this point we have unencrypted access to a /dev/mapper/lvmcrypt device. Everything written to this device, is encrypted before it ends up on disk in the /dev/sda2 partition. Everything read from that device is fetched from the partition and decrypted before you get to see it.

First we set up LVM. We create a phyiscal volume, a vg0 volume group and 5G logical volume by the name of root. In the last step we activate the volume group.

pvcreate /dev/mapper/lvmcrypt
vgcreate vg0 /dev/mapper/lvmcrypt
lvcreate -L 5G vg0 -n root
vgchange -a y

Next, we make and mount file systems. The /dev/sda1 partition doesn't need journalling, so ext2 will do. The root logical volume gets an ext4 file system. For the purpose of installation, we mount it on /mnt and create a boot directory there to mount the file system we made on /dev/sda1.

mkfs.ext2 /dev/sda1
mkfs.ext4 /dev/mapper/vg0-root
mount -t ext4 /dev/mapper/vg0-root /mnt
mkdir /mnt/boot
mount -t ext2 /dev/sda1 /mnt/boot

If desired, you can make additional logical volumes and mount them in this step. You can also add swap and activate it here. I have opted to do these things after installation.

Installing Alpine Linux

Ready for an anti-climax? It's as simple as

setup-disk -m sys /mnt

Yup. That's all that there is to installing. But don't reboot just yet. There are a few things that need taking care of first.

Before You Reboot

First of all, let's take care of that You might need fix the MBR to be able to boot message at the end of the installation. Update the master boot record (MBR) with

dd bs=$(stat -c %c /mnt/usr/share/syslinux/mbr.bin) \
   count=1 conv=notrunc \
   if=/mnt/usr/share/syslinux/mbr.bin \
   of=/dev/sda

Next, you have to make sure your encrypted partition is found and set up during the boot. That is done via a /etc/crypttab file (see the crypttab manual page for details). This file doesn't exist yet in our freshly installed system below /mnt, so

echo "lvmcrypt /dev/sda2 none luks" > /mnt/etc/crypttab

will create one that causes the boot to prompt you for the encrypted partition's passphrase.

You also have to ensure that the cryptsetup module is included in the initramfs image. Add the module and regenerate the image with

sed -i '/^features=/s/base /base cryptsetup /' \
    /mnt/etc/mkinitfs/mkinitfs.conf
mkinitfs -c /mnt/etc/mkinitfs/mkinitfs.conf -b /mnt

In order for the kernel to use /dev/sda2 as your encrypted root, instruct the extlinux bootloader to pass a few extra arguments to the kernel at boot time.

sed -i '/^default_kernel_opts=/{
    s/"$/ cryptroot=\/dev\/sda2 cryptdm=lvmcrypt"/
    }' /mnt/etc/update-extlinux.conf
extlinux --install /mnt/boot --update
sed -i '/APPEND/s|$| cryptroot=/dev/sda2 cryptdm=lvmcrypt|' \
    /mnt/boot/extlinux.conf

I needed that last sed command because for some reason the extra kernel options weren't added by extlinux and caused my reboot to fail.

Finally, unmount file systems, deactivate the volume group and close the lvmcrypt before you reboot. This only needs to be done by hand here. During later boots all this will be done automatically.

umount /mnt/boot
umount /mnt
vgchange -a n
cryptsetup close --type luks lvmcrypt
reboot

Troubleshooting

I had a bit of trouble rebooting until I found out that the extra kernel options had been added in /boot/extlinux.conf. When the boot fails, you will be dropped in an initramfs emergency recovery shell. That looks like

mount: mounting UUID=53f6da24-[...] on /sysroot failed: No such file or directory
Mounting root failed.
initramfs emergency recovery shell launched. Type 'exit' to continue boot
sh: can't access tty; job control turned off
/ #

where I shortened the UUID. Yours is different anyway.

At this point you can continue the boot with

cryptsetup open --type luks /dev/sda2 lvmcrypt
lvm vgchange -a y
mount /dev/mapper/vg0-root /sysroot
exit

You will be prompted for the passphrase by cryptsetup.

Login as root at the prompt and check the following files for any errors:

  • /etc/crypttab
  • /etc/mkinitfs/mkinitfs.conf
  • /etc/update-extlinux.conf
  • /boot/extlinux.conf

Fix them and rerun mkinitfs and extlinux if necessary, like so:

mkinitfs
extlinux --install /boot --update