NixOS / nixpkgs

Nix Packages collection & NixOS
MIT License
18.08k stars 14.13k forks source link

LUKS single-password unlock #24386

Closed CMCDragonkai closed 6 years ago

CMCDragonkai commented 7 years ago

One of the ways to achieve a single password unlock for multiple LUKs encrypted drives is to use a randomly generated keyfile, which is set as a key for each encrypted drive, then encrypt the key itself into an image file, that has to unlocked and mounted prior to unlocking all subsequent drives at stage-1 boot.

I achieved this before using boot.initrd.luks.devices with the first submodule set to keyfile image, and subsequent submodules keyFile parameter set to the mounted unlocked keyfile.

Since I have upgraded to release-17.03, it still works, but I'm seeing some weird messages on the stage 1 console.

# this happens twice
Waiting 10 seconds for key file /dev/mapper/luks-key-encrypted to appear .....
...
Enter passphrase for /luks-key.img
...
# this happens 3 times
killall: cryptsetup: no process killed

Here is the relevant configuration:

boot.initrd.luks.devices = [ 
    { device = "/luks-key.img"; name = "luks-key-encrypted"; keyFile = null; } 
    { device = "/dev/disk/by-id/ata..."; name = "main-1"; keyFile = "/dev/mapper/luks-key-encrypted"; } 
];

Has something changed in the stage 1 luks booting script so that the device list gets unlocked out of order?

Previously stage 1 will first try to unlock the first submodule, and only then try the next devices, but if there's the Waiting 10 seconds... messages, then that seems like it's trying the other devices before finding my keyfile device.

I am also perplexed by the killall: cryptsetup: no process killed message.

CMCDragonkai commented 7 years ago

Relevant files: https://github.com/NixOS/nixpkgs/blob/release-17.03/nixos/modules/system/boot/luksroot.nix

NickHu commented 7 years ago

The way I do this, with an encrypted /boot on the same partition as /, is as follows:

  boot.initrd.luks.devices = [{
    name = "cryptlvm"; device = "/dev/disk/by-uuid/<redacted>";
    allowDiscards = true;
    keyFile = "/crypto_keyfile.bin";
  }];
  boot.initrd.prepend = ["${/crypto_keyfile.cpio.gz}"];

Works fine on nixos unstable without the errors you're mentioning. You could set up secondary encrypted filesystems with crypttab if you wanted to.

iirc crypto_keyfile.cpio.gz is created with

cpio -o -H newc <<< "crypto_keyfile.bin" > crypto_keyfile.cpio
gzip crypto_keyfile.cpio
CMCDragonkai commented 7 years ago

I think the difference between your method and mine is that I'm also de rypting the keyfile image. That is a single password to unlock the image and then all subsequent drives pointing to the unlocked device. It used to work without those errors, and now it has those errors but still works in the end. That makes me think something changed in the unlocking order. On 29/03/2017 10:25 AM, "Nick Hu" notifications@github.com wrote:

The way I do this, with an encrypted /boot on the same partition as /, is as follows:

boot.initrd.luks.devices = [{ name = "cryptlvm"; device = "/dev/disk/by-uuid/"; allowDiscards = true; keyFile = "/crypto_keyfile.bin"; }]; boot.initrd.prepend = ["${/crypto_keyfile.cpio.gz}"];

Works fine on nixos unstable without the errors you're mentioning. You could set up secondary encrypted filesystems with crypttab if you wanted to.

iirc crypto_keyfile.cpio.gz is created with

cpio -o -H newc <<< "crypto_keyfile.bin" > crypto_keyfile.cpio gzip crypto_keyfile.cpio

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/NixOS/nixpkgs/issues/24386#issuecomment-289935501, or mute the thread https://github.com/notifications/unsubscribe-auth/AAnHHdSgRQNWF2SGlZWKdzfY-SMjEvaXks5rqZb9gaJpZM4MqSCc .

NickHu commented 7 years ago

Yes I have grub prompt me for a password and then unlock my filesystems afterwards - just thought you might like an insight on a different approach to largely achieve the same thing. I don't think it's documented anywhere that nixos is supposed to respect the order of your entries, and it's not evaluated sequentially like a bash script. The standard approach across most Linux distros is that crypttab is used to unlock dependant/second-stage encrypted drives.

CMCDragonkai commented 7 years ago

@NickHu Sorry but I don't understand, I'm using boot.initrd.prepend in the same way you are proposing. Your one is called /crypto_keyfile.bin and mine is called /luks-key.img which gets decrypted and mapped to /dev/mapper/luks-key-encrypted. I noticed that you don't have your keyfile image being part of the Luks devices list, maybe that's the thing I'm missing?

CMCDragonkai commented 7 years ago

@NickHu How was your /crypto_keyfile.bin created? My method was through this: https://gist.github.com/CMCDragonkai/6456d974982e09d4fe71f339f9029bd3

NickHu commented 7 years ago

There's no need to make the keyfile a block device, a simple file will suffice:

dd bs=512 count=4 if=/dev/urandom of=/crypto_keyfile.bin
cryptsetup luksAddKey /dev/sda1 /crypto_keyfile.bin
chmod 000 /crypto_keyfile.bin

Also, the kernel parameter defaults to look for /crypto_keyfile.bin in the root if it exists, otherwise it has to be specified with the cryptkey parameter on the kernel command line.

CMCDragonkai commented 7 years ago

I didn't know about that feature. That's very useful. What kernel parameter causes a defaulting to /crypto_keyfile.bin? Just wondering if this is a NixOS thing or a LUKs thing.

NickHu commented 7 years ago

It's a Linux default as far as I'm aware.

CMCDragonkai commented 7 years ago

How is your crypto_keyfile.bin password protected? I didn't see any gpg encryption or luks encryption in your examples.

NickHu commented 7 years ago

It's in the root partition which is decrypted by grub at boot time. I have a unified boot/root partition. When grub hands off to the Linux kernel, the partition gets locked again, but as the keyfile is in the initramfs (also on the encrypted partition), it can be unlocked by the kernel without prompting for a password the second time.

CMCDragonkai commented 7 years ago

My zfs root is composed of several partitions that needs to be decrypted prior to zpool import. I don't know what you mean by "unified boot/root". Are you saying you use grub to unlock something and then it relocks it when in stage 1 boot phase? I'm currently using systemd boot and there's nothing like that.

CMCDragonkai commented 7 years ago

Also my root and boot can't be unified because of uefi firmware which requires a fat32 boot partition. So that's where my keyfile image and initramfs currently reside.

NickHu commented 7 years ago

Yes, as far as I'm aware, this only works with grub. If you don't have an encrypted /boot anyway, there's no need to do this and you can just use crypttab. The crypto keyfile is only useful for unlocking the root partition from the bootloader/initramfs.

CMCDragonkai commented 7 years ago

I have just discovered why my system still works even when in the presence of the 2 errors.

First I found that my l2arc devices were not working:

 ♜ sudo zpool status                                                                                                                                      pts/2 19:53:02
[sudo] password for cmcdragonkai: 
  pool: rpool
 state: ONLINE
status: One or more devices could not be used because the label is missing or
        invalid.  Sufficient replicas exist for the pool to continue
        functioning in a degraded state.
action: Replace the device using 'zpool replace'.
   see: http://zfsonlinux.org/msg/ZFS-8000-4J
  scan: none requested
config:

        NAME                                                                        STATE     READ WRITE CKSUM
        rpool                                                                       ONLINE       0     0     0
          mirror-0                                                                  ONLINE       0     0     0
            dm-name-main-encrypted-ata-ST2000LM003_HN-M201RAD_S321J9BFB03122-part1  ONLINE       0     0     0
            dm-name-main-encrypted-ata-ST2000LM003_HN-M201RAD_S321J9BFB03123-part1  ONLINE       0     0     0
        logs
          mirror-1                                                                  ONLINE       0     0     0
            dm-name-zil-encrypted-ata-PLEXTOR_PX-G128M6e_P02445180196-part2         ONLINE       0     0     0
            dm-name-zil-encrypted-ata-PLEXTOR_PX-G128M6e_P02445180209-part2         ONLINE       0     0     0
        cache
          dm-name-l2arc-encrypted-ata-PLEXTOR_PX-G128M6e_P02445180196-part3         UNAVAIL      0     0     0
          dm-name-l2arc-encrypted-ata-PLEXTOR_PX-G128M6e_P02445180209-part3         UNAVAIL      0     0     0

errors: No known data errors

Then I found that the within /dev/mapper, the l2arc block devices no longer existed:

 ♖ ls /dev/mapper                                                                                                                                         pts/2 19:59:35
control                                                          zil-encrypted-ata-PLEXTOR_PX-G128M6e_P02445180196-part2@
main-encrypted-ata-ST2000LM003_HN-M201RAD_S321J9BFB03122-part1@  zil-encrypted-ata-PLEXTOR_PX-G128M6e_P02445180209-part2@
main-encrypted-ata-ST2000LM003_HN-M201RAD_S321J9BFB03123-part1@

My investigation into dmesg showed no errors, and upon restart, I remembered the 2 messages appearing:

# this happens twice
Waiting 10 seconds for key file /dev/mapper/luks-key-encrypted to appear .....

I tested the actual partitions using:

sudo cryptsetup status l2arc-encrypted-ata-PLEXTOR_PX-G128M6e_P02445180196-part3
sudo cryptsetup --test-passphrase luksOpen /dev/disk/by-id/ata-PLEXTOR_PX-G128M6e_P02445180196-part3
sudo cryptsetup status l2arc-encrypted-ata-PLEXTOR_PX-G128M6e_P02445180209-part3
sudo cryptsetup --test-passphrase luksOpen /dev/disk/by-id/ata-PLEXTOR_PX-G128M6e_P02445180209-part3

And those partitions are working.

Then it occurred to me, that I have 7 luks devices specified under nixos configuration.nix.

luks-key-encrypted
main-encrypted-ata-ST2000LM003_HN-M201RAD_S321J9BFB03123-part1
main-encrypted-ata-ST2000LM003_HN-M201RAD_S321J9BFB03122-part1
zil-encrypted-ata-PLEXTOR_PX-G128M6e_P02445180196-part2
l2arc-encrypted-ata-PLEXTOR_PX-G128M6e_P02445180196-part3
zil-encrypted-ata-PLEXTOR_PX-G128M6e_P02445180209-part2
l2arc-encrypted-ata-PLEXTOR_PX-G128M6e_P02445180209-part3

And it turns out that in alphabetical order, l2arc-... is ahead of luks....

This meant NixOS at stage 1 tried to unlock the 2 l2arc-... devices, but couldn't because both relied on the existence of the unlocked /dev/mapper/luks-key-encrypted device. So it showed those 2 failure messages, then it tried to unlock luks-... device, which unlocked /dev/mapper/luks-key-encrypted, and then all subsequent drives were automatically unlocked and the system proceeded as normal.

Which all the more points to the comments above mentioning the change in the luks order of operations: https://github.com/NixOS/nixpkgs/issues/24386#issuecomment-289939950

@NickHu it does appear that since UEFI doesn't allow a unified boot and root, then crypttab should be used. I'm still not sure how NixOS will ask for unlocking a password protected /crypto_keyfile.bin and then feed that result into the subsequent luks devices.

CMCDragonkai commented 7 years ago

So I looked at crypttab, and it appears that using crypttab is no different from just using NixOS's boot.initrd.luks.devices specification. Both achieve the same thing. What neither achieves is the usage of a password-protected /crypto_keyfile.bin. It is trivial to use one that is not password protected, as that is just the a property of keyFile.

I think that we need a new NixOS luks option, that allows a designation of a primary keyfile device, whether that is /crypto_keyfile.bin or my own /luks-key.img, which then gets mapped to a name, and where that name can be used as the keyFile setting for all other subsequent luks devices. This would achieve a password protected keyfile, that doesn't involve any extra tooling like gnupg, but only using existing luks tools.

Previously I was relying an insertion ordered luks opening operations, but with the new alphabetical ordering, a designated keyfile option should be used instead.

This option could automate a declarative single password unlock for LUKS systems, as upon nixos-rebuild, it could generate the cpio archive, attach it to initrd, and then make it the first LUKS device to be opened. Then it is up to the user to specify the /dev/mapper/... as the keyfile for luks devices they want opened automatically.

CMCDragonkai commented 7 years ago

I can try tackling a PR for this, but I would like some feed back on this idea.

NickHu commented 7 years ago

My configuration works perfectly fine on my UEFI laptops. What you can do is put just the EFI booting stubs in the FAT32 EFI system partition mounted to /boot/efi or something, and leave the kernel, initramfs, crypto_keyfile etc. on the /boot partition unified to / and use GRUB to boot it. As far as I am aware, GRUB is the only bootloader which supports loading a kernel/initramfs from an encrypted partition.

CMCDragonkai commented 7 years ago

If I leave crypto_keyfile etc on /boot unified to /, this means the key (keys?) required to unlock the devices (listed above in my previous comment) needed for zpool import will be inside the root zpool. Importing the zpool requires all LUKS devices to be unlocked prior. I don't see how your solution solves the problem?

It's not a good idea to partially import a zpool (which will be degraded because other mirrors will be missing, and you'll need the -f flag) just to get access to the crypto_key, to unlock all subsequent devices and finally reimporting the entire zpool. My zpool consists of 2 drives and 2 ssds, of which the ssds are each split into /boot, zil and l2arc.

NickHu commented 7 years ago

Yeah, the way it works for me is that GRUB is able to mount encrypted filesystems - although I don't know if this extends to ZFS. In principle this has nothing to do with /boot, so what you could do is put a crypto_keyfile.bin which will unlock your ZFS drives on a partition that is in a LUKS device (could be separate) and have GRUB unlock that and then pass it to the kernel to decrypt your ZFS drives. That LUKS device could contain a tiny ext4 filesystem, and GRUB will prompt for the password when it tries to read from it. In essence, this is a way to emulate a password protected crypto_keyfile.bin.

Alternatively, there may already be facilities in GRUB to handle password protected crypto_keyfile.bin files, so switching bootloader might solve your problems.

CMCDragonkai commented 7 years ago

So I see 3 ways around this:

  1. Reprogram GRUB somehow so it supports password protected files. This would most likely use gnupg somehow.
  2. Use the LUKS file device trick I suggested above, and relied on before the change to alphabetical luks unlocking.
  3. Use the your suggestion which isn't really that different from my suggestion.

The main issue, is that there must always be some part of the disk that is unencrypted so the UEFI can load the bootloader. I prefer symmetrical partition layouts, so I had previously researched into soft raid capabilities of UEFI, but it appears it's not recommended, as they only officially support using a FAT32 partition. In my system, I have 2 (refer to my preference for symmetricity) unencrypted boot partitions that are mirrored (but not in soft raid style) manually.

The next issue is that the bootloader can be programmed to do anything, so that's where you need to put the keyfile. If that keyfile is not password protected, it's super easy. Password protecting it requires some crypto system available at stage-1 boot. So that's why I'm suggesting solution 2, since it makes use of existing luks infrastructure, it works and doesn't change the partitioning layouts, it's just a file that exists in the unencrypted boot partition that can be first unlocked and fed into the rest of the partitions that constitute a zpool or lvm setup.

mickours commented 7 years ago

Hi @CMCDragonkai,

I run into the same problem as you but with another use case: LVM on LUKS on multiple disks. It means that all my LUKS partitions have to be unlock before mounting LVM volume group.

Your solution seems very nice and simpler then the GRUB with crypted /boot option option. So, I am really interested in a PR around the solution 2.

mickours commented 7 years ago

Also, I'm trying to find a workaround by prefixing the key name with "aa" but it do not work. Even if the keyfile device is loaded first I have the same error as you: waiting 10 second...

I used the gist you provide (https://gist.github.com/CMCDragonkai/6456d974982e09d4fe71f339f9029bd3) , but I think I am missing the extra steps describe at the end of it:

 # prepend the CPIO archive to the initial ramdisk
 # it will be available at stage-1 boot as /luks-key.img

How do you prepend the CPIO archive to the initial ramdisk?

Here is my config just in case:

  boot.initrd.luks.devices =
  let
    luks_key_name = "aa-luks-key-encrypted";
    luks_key = "/dev/mapper/${luks_key_name}";
  in
  [
    {
      name = luks_key_name;
      device = "/luks-key.img";
    }
    {
      name = "root";
      device = "/dev/disk/by-uuid/c3a6ae03-368b-4877-b8a3-9d02c0a64d47";
      keyFile = luks_key;
      preLVM = true;
      allowDiscards = true;
    }
    {
      name = "home";
      device = "/dev/disk/by-uuid/edea857a-8e93-4eaf-b7cd-e94b753cd573";
      keyFile = luks_key;
      preLVM = true;
      allowDiscards = true;
    }
  ];
eqyiel commented 7 years ago

@CMCDragonkai @mickours you might be able to do something like this:

boot.initrd.luks.devices = [
  {
    name = "root";
    device = "/dev/disk/by-uuid/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
  }
  {
    name = "crypto_zfs_00";
    device = "/dev/disk/by-uuid/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
    keyFile = "/mnt-root/root/tank.keyfile";
  }
  {
    name = "crypto_zfs_01";
    device = "/dev/disk/by-uuid/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
    keyFile = "/mnt-root/root/tank.keyfile";
  }
];

systemd.generator-packages = [
  pkgs.systemd-cryptsetup-generator
];

environment.etc = {
  "crypttab" = {
    enable = true;
    text = ''
      crypto_zfs_00 UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx /mnt-root/root/tank.keyfile luks
      crypto_zfs_01 UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx /mnt-root/root/tank.keyfile luks
    '';
  };
};

I only realised that /mnt-root was a thing after reading this: https://github.com/NixOS/nixpkgs/pull/29343

CMCDragonkai commented 7 years ago

What does that systemd generator package do?

eqyiel commented 7 years ago

@CMCDragonkai it automatically creates unit files from the entries it sees in /etc/crypttab: https://www.freedesktop.org/software/systemd/man/systemd-cryptsetup-generator.html

CMCDragonkai commented 6 years ago

@eqyiel Since you're using tank.keyFile as your keyFile, and I'm assuming the combination of crypttab and systemd-cryptsetup-generator will unlock multiple drives. Where do you store the tank.keyFile in the extra initrd?

  boot.initrd.prepend = ["${/crypto_keyfile.cpio.gz}"];

What is inside your .cpio.gz in order to get a /mnt-root/root/tank.keyfile working?

BTW I want to move to native zfs encryption anyway.

eqyiel commented 6 years ago

@CMCDragonkai the root partition is on another drive which gets unlocked with a passphrase. I didn't do anything special to .cpio.gz.

I don't think this will help if you want your root to be on ZFS though. I don't really remember because it was so long ago now, but you can look at my config here: https://github.com/eqyiel/dotfiles/blob/bbb0b195399e084ff0f70200bd7c47d041ba5bb3/nix/.config/nixpkgs/nixos/config/hoshijiro/configuration.nix#L108-L137 (there's a comment about the pitfalls of cryptsetup-generator)

schmittlauch commented 6 years ago

May I ask why you are using a key file anyways? In my current Gentoo setup I just use the same passphrase for all LUKS devices (the key is derived from header information anyways) and try to unlock all luks devices with the phrase entered once.

In NixOS that might need changes to the initramfs though, but apart from that I see no real downside to your approach.

fpletz commented 6 years ago

Since #29441 we have the password reuse feature. I think this should be enough to solve this issue. Using an extra keyfile, depending on its location, doesn't really change any security properties because both the passphrase and key eventually unlock a master key that's stored encrypted in the LUKS header.

Feel free to reopen if you disagree though.

aduong commented 3 years ago

For those looking for a solution in 2021 (this is one of the first results searching for similar terms), a key file can be included in the initrd using boot.initrd.secrets. This might look something like

{
  boot.initrd.luks.devices.root = {
    device = "...";
    preLVM = true;
    allowDiscards = true;
    keyFile = "/crypto_keyfile.bin";
  };

  boot.initrd.secrets = {
    "/crypto_keyfile.bin" = "/boot/initrd/crypto_keyfile.bin";
  };
}

And, while writing this comment, I found this gist which basically does the same thing.

nixos-discourse commented 1 year ago

This issue has been mentioned on NixOS Discourse. There might be relevant details there:

https://discourse.nixos.org/t/how-to-unlock-some-luks-devices-with-a-keyfile-on-a-first-luks-device/18949/11