Alpine linux with encryption

According to the project’s website:

Alpine Linux is an independent, non-commercial, general purpose Linux distribution designed for power users who appreciate security, simplicity and resource efficiency.

Some features:

Initial configurations

An iso image can be downloaded from the project’s website and then copied into a usb stick or a dvd.

After booting, a login screen appears. Entering root will log you in and typing setup-alpine launches the installation manager. The installation is fairly straight-forward if no special configurations are needed.

After the keyboard, root password, timezones and network configuration, the installation script will prepare the disks. At that moment, if you want to use encryption, quit the script.

Encryption

Objectives:

The solution is to use dm-crypt. This kernel module can encrypt and decrypt transparently, providing a virtual device which can be used as if it were a physical one. The device appears under /dev/mapper/.

I chose the following disk layout:

+-------------------------------+
|  /sda1  |  /sda2              |
| (/boot) |  encrypted lvm      | 
+-------------------------------+

The /boot partition contains the necessary files to boot, such as the kernel binary, so it needs to be unencrypted. It’s possible to encrypt /boot as well, but it requires support from the bootloader.

The second partition is encrypted and will contain the whole system, except for the files in /boot.

The fdisk tool can be used to create the partition scheme.

The cryptsetup utility talks to the dm-crypt module. It doesn’t come with the installation image, so use apk to add it:

apk add cryptsetup

Once installed, assuming your storage device is in /dev/sda, the command

cryptsetup luksFormat /dev/sda2

will format the /dev/sda2 partition with the LUKS format, which is used by dm-crypt. It’s possible to use a keyfile instead of a password.

Backup the LUKS header in that partition (if it breaks you will lose the information) using

cryptsetup luksHeaderBackup /dev/sda2 --header-backup-file luks_backup_alpine_sda2

Create a mapped device which can be used as if it were a physical one using the command

cryptsetup open /dev/sda2 crypto

The kernel will create the /dev/mapper/crypto device to interact with the encrypted partition transparently.

Logical volume management

Lvm is used to make partition management more flexible. It solves the problem of wanting to shrink or grow a partition, without having enough contiguous space. This setup will look like:

file system <--> lvm <--> dm-crypt <--> drivers <--> storage

Just as dm-crypt provides a device other software can talk to, while keeping it’s operations hidden, lvm does the same, but mapping blocks in the exposed device to blocks in the underlying device. This makes it possible to have a partition spread over non contiguos blocks of storage, which in turn makes the partition scheme more flexible.

The concepts to understand here are: physical group, volume group and logical volume. The physical group is the actual device; it could be a disk, a partition, or any block device interface provided by the kernel, such as a dm-crypt mapped device or a loopback device from a file. The volume group is the set of physical groups used to store the logical volumes; this gives flexibility to lvm: the logical volumes can be located in different devices. The logical volumes are the analog of partitions; they appear as devices that can be mounted, usually under /dev/vg/name where name is it’s name, and vg is the name of the related volume group.

I created the volume group using the dm-crypt backed device as a physical volume. Then, I created logical volumes for /, /home and swap.

Run apk add lvm2 to install the lvm tool. Then, configure a device as a physical group

pvcreate /dev/mapper/crypto

Then define a volume group with the previous device

vgcreate vg0 /dev/mapper/crypto

The name of the volume group will be vg0. Now, you can add the logical volumes

lvcreate -n swap -L 4G vg0
lvcreate -n home -L 100G vg0
lvcreate -n root -L 80G vg0

The -n flag sets the name and the -L flag the size. To activate the volume group vg0 execute

vgchange -a n vg0

Now, the partitions will be available under /dev/vg0/partition_name.

Creating the file systems

Once the devices are ready, the next step is to create the file systems. I used ext4 and ext2 for the root and boot partition respectively.

The tools to create the file systems come with the e2fsprogs package, so install them using pkg add e2fsprogs.

mkfs.ext4 /dev/vg0/root
mkfs.ext2 /dev/sda1

Also, format the swapp partition, if any:

mkswap /dev/vg0/swap

Now that the file systems exist, the installation script can copy the system files. To mount them

mount -t ext4 /dev/vg0/root /mnt
mount -t ext2 /dev/sda1 /mnt/boot

Finally, the script can proceed and install the base system. The setup-alpine script is made up of several scripts, each one solving a particular problem. The setup-disk script, copies the operating system files to disk. Call it as setup-disk -m sys /mnt/, or wherever root is mounted.

After the script is done add the partition to the /etc/crypttab file of the installed system, located in /mnt/etc/crypttab, assuming you mounted root into /mnt/. The line to add is

crypto  UUID=<uuid> none    luks

If the file doesn’t exist, create it. This file tells the operating system that a luks volume exists with node /dev/mapper/crypto. Replace <uuid> with he UUID of the partition; to get it run

blkid -s UUID -o value /dev/sda2 > ~/uuid

which saves the id into a file named uuid in the root home of the live linux (not the new installation), since it’ll be needed again later.

New initial ram disk

At startup, the computer finds a bootable device: a hard-drive, usb-stick, even an ethernet interface for network boot. Then, assuming it’s a storage device, it extracts the Master Boot Record, or mbr for short, wich contains the partition scheme of the device as well as the boot loader.

The boot loader is a small piece of code in charge of loading operating system kernels into memory, along with any requiered parameters. Modern boot loaders can be complex and divide its operations into stages, providing more features, but the main goal is to load the operating system.

In linux, a separate partition, usually called boot, contains the files needed to initialize the system, such as the kernel image. The boot loader reads this partition and loads the kernel.

If everything went well, the first process, init, is launched. This process is in charged of mounting the required filesystems, activating the swapp, launching other important processes, etc. It’s located in the root file system, which creates a problem if the partition it’s in is ecrypted: the kernel will not be able to load it.

A solution is to use the initial ram disk. Along with the kernel, the boot partition contains a file used to set up a temporal filesystem in memory. Inside it, there is an init script used to mount the actual root file system and then give the control to the actual init script. So, if you use encryption, you will need to create a new initial ram disk with the modules to decrypt partitions.

To add cryptsetup to the initial ram disk, append cryptsetup to the features list inside the file /mnt/etc/mkinitfs/mkinitfs.conf. The following command generates the initial ram disk:

mkinitfs -c /mnt/etc/mkinitfs/mkinitfs.conf -b /mnt/ $(ls /mnt/lib/modules)

Bootloader

There are many bootloaders, I went with Syslinux, but Grub is also a possibility. Make sure the one you choose can read the file system of the /boot partition, if it’s ext2 it probably can.

First, let’s install the syslinux package:

apk add syslinux

Since we’re using encryption, the kernel will need to know which disk needs to be decrypted and mounted as well as it’s name. I chose the name crypto for /dev/sda2, so append the string

cryptroot=UUID=<uuid> cryptdm=crypto

to the default_kernel_opts parameter list, in the file /mnt/etc/update-extlinux.conf. The first tuple defines the device with UUID uuid as the encripted root file system, so put the UUID of the encrypted root there. The second defines the name crypto as the mapping name and should be the same name used in /mnt/etc/crypttab.

The update-extlinux utility is used to generate an image with the bootloader, which can then be copied into the mbr. Since the paths it uses are hardcoded, a chroot is needed:

chroot /mnt
update-extlinux
exit

The update-extlinux program may return an error, I ignored it and nothing happend. This alpine guide advices the same.

Now, you can write the bootloader into the mbr of the device, /dev/sda in my case

dd bs=440 count=1 conv=notrunc if=/mnt/usr/share/syslinux/mbr.bin of=/dev/sda

Finishing the installation

At this point the installation is complete. Close everything and reboot

sync
umount /mnt/boot
umount /mnt
vgchange -a n vg0
cryptsetup close /dev/mapper/crypto
reboot

If everything went well the system is ready to use. In my case, I added a swap area and have a separated home partition which needs to be added to /etc/fstab:

...
/dev/vg0/swap   swap    swap    defaults    0 0
/dev/vg0/home   /home   ext4    defaults    0 0
...

And to activate swap at boot

rc-update add swap boot

To set up wifi automatically

apk add wpa_supplicant wireless-tools
wpa_passphrase "name" "password" > /etc/wpa_supplicant/wpa_supplicant.conf

The wpa_supplicant program manages the wpa and wpa2 authentication and wireless-tools has important programs such as udhcpc to dynamically get an ip address from an access point.

Look for your wireless interface name by issuing the ifconfig -a or ip link command. Assuming it’s wlan0, set the interface behaviour in /etc/network/interfaces by adding

auto wlan0
iface wlan0 inet dhcp

This tells the kernel to automatically activate the interface and dynamically get an address from an access point.

To connect with a network after boot without manual intervention, the wpa_supplicant package installs an init script which can be run by openRC. To activate it, use

rc-update add wpa_supplicant boot