Unlocking Luks Devices at boot using clevis+tang

Hi,

I’m currently building a new system, thats booting from zfs on top of two luks encrypted drives.
I got to the point, that scripted stage 1 and systemd stage 1 work by asking for the encryption keys at boot and the system boots up fine.
But I want to use clevis to unlock the drives using secrets from tang. I got this working some time ago using debian, but can’t figure it out on nixos.

I tried unlocking the discs with postDevice commands in scripted stage 1 or custom systemd units in systemd stage 1, but thats fails with

device-mapper: table: 254:0 crypt: unknown target type
device-mapper: ioctl: error adding target to table

So is there a propper way to achieve this?

Regards and thanks

There’s a section in the manual about this: NixOS Manual

thanks for the hint. This uses a special jwe file. I want to use the secret stored in the devices luks header via

clevis luks bind

is there any option to get this working?

I don’t think that’s an option with the current module, but that certainly sounds like a welcome improvement. PRs welcome!

Got some kind of progress on this…
Using scripted stage 1 and:

boot.initrd.luks.devices = {
    nvme0crypt = {
      device = "/dev/nvme0n1p2";
      preOpenCommands = ''
      export PATH=$PATH:${pkgs.curl}/bin/:${pkgs.gnused}/bin/
      ${pkgs.clevis}/bin/clevis luks unlock -d /dev/nvme0n1p2 -n nvme0crypt
      '';
   };
};

I get clevis to unlock the drive, but scripted stage 1 does ask for the password and fails because device is already in use.
Any suggestions on this, or should i try to implement/port clevis-systemd as used by debian?

What is “clevis-systemd”? And btw the scripted stage 1 is going to eventually be phased out, so it’s preferable to prioritize systemd stage 1.

Finally got it to work. Bit hacky but works:

boot.initrd.luks.devices = {
  dummy = {device = "/dev/nonExistent"; crypttabExtraOpts = ["nofail"];};
};
boot.initrd.systemd.storePaths = [ "${pkgs.clevis}" "${pkgs.curl}" "${pkgs.cryptsetup}" "${pkgs.luksmeta}" "${pkgs.gnused}" "${pkgs.tpm2-tools}" "${pkgs.jose}" "${pkgs.libpwquality}" "${pkgs.coreutils}" "${pkgs.gnugrep}" "${pkgs.bash}"];
boot.initrd.systemd.services.unlocknvme0 = {
    path = [ "${pkgs.clevis}" "${pkgs.curl}" "${pkgs.cryptsetup}" "${pkgs.luksmeta}" "${pkgs.gnused}" "${pkgs.tpm2-tools}" "${pkgs.jose}" "${pkgs.libpwquality}" "${pkgs.coreutils}" "${pkgs.gnugrep}" "${pkgs.bash}"];
    enable = true;
    unitConfig = {Description = "Unlocknvme0";};
    serviceConfig = {Type="oneshot"; User = "root";
      ExecStart="${pkgs.clevis}/bin/clevis luks unlock -d /dev/nvme0n1p2 -n nvme0crypt";
    };
    wants = [ "network-online.target" ];
    after = [ "network-online.target" ];
    wantedBy = [ "zfs.target" ];
  };

boot.initrd.luks.devices needs to be set, so that device mapper knows the target type crypt (otherwise the error from post 1 is displayed), any recommendations to come around this?

Rather than having a dummy device you can just do boot.initrd.luks.forceLuksSupportInInitrd = true; (This option isn’t documented because it’s marked “internal”, but that should probably be changed).

Also I would probably use these dependencies:

wants = ["network-online.target"];
after = ["network-online.target" "cryptsetup-pre.target" "dev-nvme0n1p2.device"];
requires = ["dev-nvme0n1p2.device"];
requiredBy = ["cryptsetup.target"];
# no wantedBy = zfs.target
unitConfig.DefaultDependencies = false;

(sorry for editing that like 5 times but I think I’m done now :P)

Thanks for that hint, that fixes the need for the dummyDevice

Your listed dependencies do work, but it tries to import the zpool ontop even before setting up network, thats what i tried to come around. No big deal but im trying to get rid of seeing the A start job is running for Import ZFS pool ...-Message until the devices are opened

Well, I can’t see how the dependencies you had before would prevent that either. But you can fix that with boot.initrd.services."zfs-import-${poolName}".after = ["cryptsetup.target"]; (which is actually something we’ll probably be adding by default here very soon for better compatibility with scripted initrd)

That doesn’t help getting rid of the log message, but i guess i’ll live with it. Thanks for helping :slight_smile:

Oh, I forgot to add before = ["cryptsetup.target"]; when I suggested dependencies for your unlock service. That should do it. (Normally you don’t need explicit dependencies for target units that depend on your unit, but DefaultDependencies=false removes this default ordering)

1 Like