Unlock encrypted ZFS via ssh on boot

I am trying to get NixOS to prompt me for the ZFS password to decrypt and mount a non-boot vdev. I have followed the following two tutorials but am not getting anywhere:
https://nixos.wiki/wiki/ZFS
https://nixos.wiki/wiki/Remote_disk_unlocking

When I reboot after successfully rebuilding my configuration, the system seems to ignore the boot.initrd section. Rather than seeing any pause for password, it goes straight to the regular login prompt. I have tried multiple permutations and combinations, but am getting nowhere. Would appreciate any pointers.

{
  imports =
    [ # Include the results of the hardware scan.
      ./hardware-configuration.nix
    ];

  # Use the systemd-boot EFI boot loader.
  boot.loader.systemd-boot.enable = true;
  boot.loader.efi.canTouchEfiVariables = true;

  boot.supportedFilesystems = [ "zfs" ];
  boot.zfs.forceImportRoot = false;

  boot.kernelParams = [ "ip=dhcp" ];
  boot.initrd.availableKernelModules = [ "r8169" ];
  boot.initrd.network = {
    enable = true;
    ssh = {
      enable = true; # Use a different port than your usual SSH port!
      port = 2222;
      hostKeys = [ /var/ssh/ssh_host_rsa_key ];
      authorizedKeys = with lib; concatLists (mapAttrsToList (name: user: if elem "wheel" user.extraGroups then user.openssh.authorizedKeys.keys else []) config.users.users);
    };
    postCommands = ''
      echo "zfs load-key -a; killall zfs >> /root/.profile"
    '';
  };


  networking = {
    hostId = "8425e349";
    hostName = "NixOS";
    interfaces.enp1s0.ipv4.addresses = [{
      address = "192.168.2.11";
      prefixLength = 24;
    }];
    defaultGateway = "192.168.2.1";
    nameservers = [ "192.168.100.100" "192.168.100.101" ];
    firewall.enable = false;
  };

  time.timeZone = "America/Toronto";

  users.users.MYUSER = {
    isNormalUser = true;
    extraGroups = [ "wheel" ];
    openssh.authorizedKeys.keys = [ "AUTHORIZED KEYS GO HERE" ];
  };

  services.openssh = {
    enable = true;
    settings.PermitRootLogin = "yes";
  };
  
  environment.systemPackages = with pkgs; [
    vim
  ];

  system.stateVersion = "23.11"; # Did you read the comment?

}

For datasets that aren’t required for boot, initrd will not be involved whatsoever. Instead, they use systemd-ask-password during stage 2 bootup. Note the pool does need to be configured to be imported, either via boot.zfs.extraPools or by having any of the pool’s file systems in fileSystems e.g. in hardware-configuration.nix. NixOS by default prompts for the password for any dataset in any imported pool. However, SSH doesn’t start early enough in stage 2, so you won’t be able to unlock these datasets remotely.

That matches what I found. Thank you for confirming. The challenge then is that for a server, I need physical access (or KVM) to enter the ZFS password to decrypt a data pool. Is there no way to use the initrd ssh session to request this?

If you’ve got datasets in your fileSystems nix config, you can add neededForBoot = true; to them and their passwords will be prompted during initrd. Not ideal, but the alternative is figuring out how to get sshd to start earlier in stage 2 which… is interesting, and now I wanna do that :stuck_out_tongue:

If you’re relying on extraPools though, then we don’t really have a way to indicate extra pools to import and unlock in initrd…

I have a similar setup, where I define a custom target to boot to which starts some things that don’t need the encrypted pool imported and which I need to be able to SSH in:

boot.kernelParams = ["systemd.unit=pre-unlock.target"];
systemd.targets.pre-unlock = {
  unitConfig.AllowIsolate = true;
  wants = [
    "sshd.service" "basic.target" "tinc.lugnet.service" "network-online.target"
    "dbus.socket" "systemd-logind.service" "getty.target"
    "systemd-resolved.service"
  ];
};

Then I can SSH into the non-initrd system and run systemctl start multi-user.target to unlock the encrypted devices and start everything else up.

2 Likes