Support kexec reboot with automatic FDE decryption

Do you think it’s possible to have automatic reboot somehow using kexec for decrypting FDE? It would be a really useful NixOS feature in server environments.

1 Like

It’s certainly possible, though I’m not sure how you would do it. You would have to somehow acquire the necessary key, add it to a temporary initrd that’s otherwise the same as your normal one, and use kexec with that initrd instead of your normal one. LUKS makes it possible to extract the real master key from the kernel, but e.g. ZFS doesn’t make this possible for its encryption implementation. You could either ask the user for the necessary keys when they invoke kexec, or store them somewhere safe so they’re always accessible as long as the disk is already decrypted.

So this could be a script which does something like this:

  1. asks for the key (stdin or file path)
  2. appends the key to /nix/var/nix/profiles/system/initrd and stores resulting initrd in temporary directory
  3. loads initrd from temporary directory via kexec
  4. removes initrd file
  5. does kexec

The most non-trivial parts seems to be:

  1. In step №2 initrd would need to be decompressed, modified to include the key (by appending a new cpio archive to the end? or is initrd needs to be extracted first?) and then recompressed again (is it even necessary for kexec?).
  2. There’s also should be some script in boot.initrd.postDeviceCommands which would read integrated key and do something with it.

Actually initrd can be a concatenated sequence of compressed cpio images, so no need to decompress and modify the original image; just make a totally separate image with what you need and append it. But yes, you will have to have boot.initrd.luks.devices.<name>.keyFile set to the name of your key file. I don’t remember if NixOS falls back to a password input if that keyFile doesn’t exist (for the case where you’re booting without this mechanism), but if it doesn’t you’ll have to figure something out for that.

1 Like

I’ve quickly “vibecoded” a functional version for myself today, after having in mind publishing something like this for many years.

“Injects” into the target cpio a second image, which is then later used during boot-up, so at least avoids extensive waiting times and is more secure than just /proc/cmdline.

I’m exposing everything through a nix-module, so should be quite reusable by others.

Requires systemd initrd.

Afterwards all you have to do is (which asks for the first kexec reboot for the LUKS key and subsequent ones no more, unfortunately there is no way to tell systemd-crypttab to use --disable-keyring and I want to avoid modifying the NixOS native bootup procedure as much as possible):

sudo emberboot reboot

https://gitlab.com/norpol/emberboot/-/blob/main/README.md#usage-from-another-flake

{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
    emberboot = {
      url = "gitlab:norpol/emberboot";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs =
    { self, nixpkgs, emberboot, ... }:
    {
      nixosConfigurations.my-host = nixpkgs.lib.nixosSystem {
        system = "x86_64-linux";
        modules = [
          emberboot.nixosModules.emberboot
          ./configuration.nix
          {
            boot.initrd.systemd.enable = true;
            security.protectKernelImage = false;

            services.emberboot = {
              enable = true;
              # `lsblk -f` your `crypto` UUID
              luksDevice = "/dev/disk/by-uuid/00000000-0000-0000-0000-000000000000";
              rootName = "cryptroot";
              reservedKeySlot = 7;
              replaceReservedKeySlot = true;
            };
          }
        ];
      };
    };
}

Uh, wow. 1400 lines of Rust vs 50 lines of Nix/Bash for the MVP over here.

1 Like

Cool, thanks for sharing and checking it out. Yeah I wrote something similar 10 years ago, actually ended up being around 1000 lines of Bash back then, mostly because you need xxd to decode the LUKS main key through dmsetup.

I think adding a temporary key through cryptsetup, keyctl + joining and not repacking the CPIO + logging + status commands just makes it a bit more complicated. But I wanted to avoid a persistent keyfile inside the encrypted disk.