Disk Encryption on NixOS servers: How & when to unlock?

While setting up a new NixOS server VM, I’d like to take that as an opportunity to finally encrypt my data with LUKS/ dm-crypt.
As I don’t have physical access to its host machine, probably asking for the password at each boot is the way to go.

Is anyone already using such a setup? To avoid downtime in case of an unattended reboot, I’d like to be notified about the need of entering a password.
For both security and simplicity reasons, it’d be nice to put the disk unlock process as early into the boot process as possible. One popular approach seems to be including dropbear as a light-weight SSH server into the initramfs and then unlock via that. Does anyone have a configuration for that?

Curious about your approaches!

1 Like

Search for: tinyssh or encryptssh

I run such a setup with dropbear. It’s quite easy to configure:

  # additional hardware configuration not discovered by hardware scan
  boot.initrd.availableKernelModules = [ "virtio-pci" ];

  boot.initrd.network = {
    enable = true;
    ssh = {
      enable = true;
      port = 2222;
      hostECDSAKey = /var/src/secrets/dropbear/ecdsa-hostkey;
      # this includes the ssh keys of all users in the wheel group, but you can just specify some keys manually
      # authorizedKeys = [ "ssh-rsa ..." ];
      authorizedKeys = with lib; concatLists (mapAttrsToList (name: user: if elem "wheel" user.extraGroups then user.openssh.authorizedKeys.keys else []) config.users.users);
    };
    postCommands = ''
      echo 'cryptsetup-askpass' >> /root/.profile
    '';
  };

Since the ssh server in initrd will use a different hostkey (it’s unencrypted), I always let it listen on a different port, so that the ssh client will not complain about the different host key.

Also note that the initrd networking will automatically include dhcp for the interfaces where you have enabled it (with networking.interfaces.xyz.useDHCP).
Just be aware that the networking from initrd is kept, and predictable interface names and IPv6 configuration might not work as expected.

7 Likes

Thx @petabyteboy. Especially the remarks about the postCommands and the host keys are very helpful.
Do you rely on external monitoring for learning about unexpected reboots? Otherwise one would need to put a notification toll into the initramfs as well.

This server is hosting my mail and all the other stuff, so I will notice it when I take a look at my phone and Conversations complains about not being able to reach the XMPP server.
But to be honest I have not had any unexpected reboots since I set this up a few months ago, what would cause the server to reboot?

what would cause the server to reboot?
In case of my VMs on a shared host server, someone rebooting for a kernel update without announcing it to me.
In case of servers at home, power cuts do happen sometimes.

But I guess I’ll take care of that later.

Is it possible to re-configure the interface after initrd?
While I do not care about the interface name, I only supplied a static IPv4 config via the kernel cmdline ip= parameter, resulting in the lack of an IPv6 address after bootup although it is configured in my configuration.nix.

Why doesn’t the interface reconfigure during the init bootup? How does it recognise its existing configuration, maybe it could be reset after entering the password?

1 Like

The lack of predictable interface names in stage 1 is an unfixed bug.
Use this to enable predictable names (needs NixOS 19.09 or later):

boot.initrd = {
  extraUdevRulesCommands = ''
    cp ${pkgs.udev}/lib/udev/rules.d/75-net-description.rules $out
    cp ${pkgs.udev}/lib/udev/rules.d/80-net-setup-link.rules $out
  '';
  preDeviceCommands = let
    linkUnits = pkgs.runCommand "link-units" { preferLocalBuild = true; } ''
      install ${pkgs.systemd}/lib/systemd/network/99-default.link -Dt $out
    '';
  in ''
    mkdir -p /etc/systemd
    ln -sfn ${linkUnits} /etc/systemd/network
  '';
}
3 Likes

I use boot.kernelParams = ["ip=:::::eno1:dhcp"]; also, i hapend net driver to boot.initrd.availableKernelModules

Exemple:

  boot.initrd.network = {
    enable = true;
    ssh = {
      enable = true;
      port = 2222; 
      hostECDSAKey = /boot/initrd-ssh-key;
      authorizedKeys = [''
ssh-rsa AAAAB3Nza[...]keJQ==
      ''];
     };
     postCommands = ''
       echo "zfs load-key -a; killall zfs >> /root/.profile"
     '';
   };
  boot.kernelParams = ["ip=:::::eth0:dhcp"];

Unfortunately this snippet doesn’t work for me on current NixOS unstable; it’s still eth0 :frowning:

Have you supplied the predictable network interface name of your interface as the 6th argument where @apeyroux wrote eno1? Your device probably has a different name.

I was replying to @earvstedt’s post, not @apeyroux’s.

A quick and dirty solution that works for me is to just shut down the interface(s) after unlocking.

boot.initrd.postMountCommands = ''
    ip link set eth0 down
'';

Edit: Generalized:

boot.initrd.postMountCommands = ''
  for int in /sys/class/net/*/
    do ip link set `basename $int` down
  done
''; 
1 Like

Intel NUC NUC8i7BEH2 - Bean Canyon, NixOS 19.09:

networking.useDHCP = false;
networking.interfaces.eno1.useDHCP = true;

boot.kernelParams = ["ip=:::::eth0:dhcp"];

boot.initrd.availableKernelModules = [ "e1000e" ];
boot.initrd.network = {
  enable = true;
  ssh = {
    enable = true;
    port = 2222;
    hostRSAKey = /boot/dropbear_rsa_host_key;
    hostECDSAKey = /boot/dropbear_ecdsa_host_key;
    hostDSSKey = /boot/dropbear_dss_host_key;
    authorizedKeys = [''
      ssh-rsa AAA...
    ''];
  };
  postCommands = ''
    echo 'cryptsetup-askpass' >> /root/.profile
  '';
};
boot.initrd.postMountCommands = ''
    ip link set eth0 down
'';
nix-shell -p dropbear --command "dropbearkey -t dss -f dropbear_dss_host_key"
nix-shell -p dropbear --command "dropbearkey -t rsa -f dropbear_rsa_host_key"
nix-shell -p dropbear --command "dropbearkey -t ecdsa -f dropbear_ecdsa_host_key"

The PR above should fix most of the issues people are encountering in this thread. Would be awesome if anybody of you could test.

2 Likes

Is it possible to just test this new initrd module or do I need to rebuild my whole system from the nixpkgs branch?
I’m a bit reluctant to rebuild my whole stable system from an unstable branch as I don’t know how that affects my stateful storage data (DBs, application state).

I’ve cherry-picked the commits on top of the current release-19.09 branch and checked that the relevant tests still pass. Pushed to https://github.com/NixOS/nixpkgs/tree/fix-predictable-ifnames-in-initrd-19.09 so you could test with nixpkgs=https://github.com/NixOS/nixpkgs/archive/fix-predictable-ifnames-in-initrd-19.09.tar.gz in your NIX_PATH.

1 Like

Hi,

After some research, and the use of unstable (dropbear to openssh), i found a way to correctly config hostkey to connect with ssh and decrypt luks remotly :

  • ssh-keygen -t ecdsa -N "" -f host_ecdsa_key
  • cp host_ecdsa_key /mnt/boot
        boot.initrd.network = {
          enable = true;
          ssh = {
            enable = true;
            port = 2222;
            authorizedKeys = ["ssh-rsa xxx"];
            hostKeys = [ /boot/host_ecdsa_key ];
            };
        };

Something i don’t understand is why the passphrase is asked only one time. Normally if boot is encrypted, doc indicate that passphrase is needed two times.

I have a setup for zfs encryption as well: https://github.com/Mic92/dotfiles/blob/abb50db1fb0f396a1db05fd85326b08aca044ea7/nixos/eve/modules/network.nix#L99

I have some code to do irc notifications in bash. However I have not integrated this into initrd:

The passphrase shouldn’t prompt twice anymore: