Mount a LUKS partition with a password-protected GPG-encrypted key using systemd

I recently took a good resolution for my laptop: increase the security of some sensitive data using LUKS. Because I'm using a password-protected gpg-encrypted key, I can't use any automatic mount tool like dracut or automount so I use a bunch of systemd service files.

Note: I'm using the user part of systemd, but this article is not about its configuration.

GPG-Agent

The first tool I need is gpg-agent which is not system-wide, so I create an unit file named gpg-agent.service:

[Unit]
Description=GPG Agent Daemon

[Service]
ExecStart=/usr/bin/gpg-agent --daemon --no-detach
ExecReload=/bin/kill -HUP $MAINPID
Type=forking

[Install]
WantedBy=default.target

If you need the SSH Agent support, append --enable-ssh-support to ExecStart.
ExecReload is used to clean the passphrases cache when you want (reload gpg-agent with: systemctl --user reload gpg-agent.service).

Note: Don't forget to append use-agent in ~/.gnupg/gpg.conf and customize the agent configuration with ~/.gnupg/gpg-agent.conf

LUKS

I've found a script for mounting a LUKS partition with automount. I've customized it for my own use:

#!/bin/sh

key="{keyname}"

# The cryptsetup tool from the package of the same name
CRYPTSETUP=/sbin/cryptsetup

# This is the raw device that we will mount
mount_device=/dev/{device}

# This is the encryption key file, encrypted using gpg
key_file={keypath}

# Mount options for the encrypted fileystem
mount_opts="-t ext4 -o defaults,noatime,nodiratime,users,owner,exec"

# The mapped block device
crypt_device=/dev/mapper/$key

# Give up if there is no key or setup tool
# [ -r $key_file ] || exit 0
[ -x $CRYPTSETUP ] || exit 1

# If there is an encrypted device mapped in already, it must be from a
# previous mount. It may be out-of-date so remove it now.
[ -b $crypt_device ] && sudo $CRYPTSETUP remove $key

# Give up if the raw device doesn't have a LUKS header
sudo $CRYPTSETUP isLuks $mount_device || exit 2

# Open the encrypted block device
gpg --batch --decrypt $key_file 2>/dev/null | sudo $CRYPTSETUP -d - luksOpen $mount_device $key >& /dev/null || exit 3

# If we ended up with a block device, mount it
if [ -b $crypt_device ]; then
  sudo mount $mount_opts /dev/mapper/$key {mountpath}
fi
exit 0

{keyname}: Name of the LUKS device
{device}: LUKS disk device
{keypath}: absolute path of the gpg-encrypted key to unlock the LUKS device
{mountpath}: absolute path of the mount point

Note: I'm using sudo to be able to call cryptsetup and mount with root privileges (without password).

Now with this script, we can make a new unit file for LUKS named luks.service:

[Unit]
Description=Mount LUKS
Requires=gpg-agent.service
After=gpg-agent.service

[Service]
Type=oneshot
EnvironmentFile={gpgagent}
ExecStart=/path/to/luks.sh
RemainAfterExit=yes

[Install]
WantedBy=default.target

Mount LUKS as a requirement for X

This section is optional. Some of important files for the boot of my X session are on the LUKS partition so I need to mount it before X starts.

I don't use any login manager (like gdm or lightdm) and I start X after the tty login. To make sure everything is set up, I do it via another unit file named xsession.service:

[Unit]
Description=XSession Service
After=luks.service
Requires=gpg-agent.service luks.service

[Service]
ExecStart=/usr/bin/startx

[Install]
WantedBy=default.target

Note: I specify xsession to only start after luks.service and it explicitely requires gpg-agent.service for future use

Now, I execute systemctl --user start xsession.service and it will start GPG Agent and the LUKS mount script for me (and X, of course).

Enjoy it!