Re-lock LUKS volume after use in the initramfs

I’m experimenting right now with different ways of creating secure Linux installations. Currently, the setup that I am trying to create is one where the root file system is stored on a LUKS-encrypted internal SSD, and the EFI System Partition (ESP) is stored on an external USB drive along with another LUKS volume, containing the root volume’s key file and detached LUKS header. I’m able to accomplish this with the following /etc/crypttab.initramfs and /etc/fstab.

# /etc/crypttab.initramfs
#
# <target>  <source device>  <key file>                 <options>
keys        LABEL=cryptkeys  none                       luks,tries=5,readonly
root        LABEL=cryptroot  /cryptroot.key:LABEL=keys  luks,discard,header=/cryptroot.hdr:LABEL=keys
# /etc/fstab
#
# <file system>  <mount point>  <type>  <options>                                <dump>  <pass>
LABEL=root       /              ext4    defaults                                 0       1
LABEL=UEFI       /boot/efi      vfat    defaults,nodev,noexec,nosuid,fmask=0177  0       2
LABEL=keys       /keys          ext4    defaults,nodev,noexec,nosuid,noauto      0       2

In the initramfs, the cryptkeys volume is unlocked and mounted to /keys, and then the cryptroot volume is successfully unlocked using the key file and detached LUKS header stored within the decrypted cryptkeys volume.

The problem that I’m running into is that the cryptkeys volume is NOT closed after the root volume is unlocked. The noauto option in /etc/fstab prevents it from being auto-mounted under the new system root, but the volume is still unlocked and mapped at /dev/mapper/keys, allowing an attacker with root privileges to simply mount the volume and read its contents, without having to know the password.

Ideally, I would like the cryptkeys volume to be unlocked for as short a period of time as possible, but, at the very least, it should NOT be unlocked outside of the initramfs. I’m using a systemd-based initramfs, and I read that, for such initramfs, you should use systemd services instead of initramfs hooks. I don’t know if that’s accurate or not, but I would like to follow best practices. That said, I’m not married to any one particular way of achieving my overarching goal, which is to ensure that the root volume’s key file and detached LUKS header remain encrypted outside the initramfs, but I would like to avoid solutions that are fragile or overly complex. For example, if it would be simpler to encrypt the key file and detached LUKS header with GPG or OpenSSL, instead of storing them in a separate LUKS volume, then that would be fine too.

Going with the systemd service approach, this is what I’ve come up with so far, but I’m currently running into a race condition that’s causing the service to fail sporadically, resulting in either the cryptkeys volume remaining unlocked after boot or the boot to fail entirely.

# /etc/systemd/system/initrd-cleanup-cryptkeys.service
[Unit]
Description=Unmount and Lock the cryptkeys LUKS volume
DefaultDependencies=no
AssertPathExists=/etc/initrd-release
ConditionPathExists=/dev/mapper/keys
OnFailure=emergency.target
OnFailureJobMode=replace-irreversibly
After=initrd-root-device.target
Before=initrd-root-fs.target

[Service]
Type=oneshot
ExecStart=/usr/bin/umount /dev/mapper/keys
ExecStart=/usr/lib/systemd/systemd-cryptsetup detach keys

I’ve tried various incantations of the above systemd service, but nothing has been consistently successful.

Specifically, I have …

  • Tried with and without the ExecStart=/usr/bin/umount /dev/mapper/keys line
  • Tried the following values for the After= directive
    • systemd-cryptsetup@root.target
    • blockdev@dev-mapper-root.target
    • initrd-root-device.target
    • initrd-root-fs.target
    • initrd.target
  • Tried the following values for the Before= directive
    • initrd-root-fs.target
    • initrd.target
    • initrd-switch-root.target

For each attempt, I created a symlink at /etc/systemd/system/<before>.target.wants/initrd-cleanup-cryptkeys.service to /etc/systemd/system/initrd-cleanup-cryptkeys.service, replacing <before> with the value, specified in the Before= directive.

Can someone please tell me what I’m doing wrong here, or suggest a better way to accomplish what it is that I’m trying to do?