Remote disk unlocking: Difference between revisions

imported>Ikovnatsky
m Wording: theses -> these
Arnecc (talk | contribs)
 
(48 intermediate revisions by 18 users not shown)
Line 1: Line 1:
= Unlocking your LUKS via SSH and Tor =
This page describes the method for <strong>remotely</strong> unlocking LUKS / ZFS encrypted root partition during boot process. SSH or even Tor may be used to access the system.


If you want to unlock your computer remotely, and you are facing the problem, that you can’t reach your computer before your computer is unlocked, Tor will help you to reach your computer, even during the boot process.
== Setup ==


== SSH in initrd ==
=== Add kernel modules for the network card ===


=== Prepare SSH host keys ===
The network card may not work in initrd without its kernel being manually loaded by {{nixos:option|boot.initrd.availableKernelModules}}. Find out the kernel module required by  checking "Kernel modules" section in the output of <code>lspci -v | grep -iA8 'network\|ethernet'</code> (<code>lspci</code> is available in {{nixos:package|pciutils}}), and add it to {{nixos:option|boot.initrd.availableKernelModules}} (not to confuse with {{nixos:option|boot.availableKernelModules}}, which is for stage 2).


It is very important that you create your SSH host keys upfront, otherwise you end up connecting to a server on the internet and typing in your disk encryption password without authenticating the machine on the remote end!
{{file|/etc/nixos/configuration.nix|nix|<nowiki>
boot.initrd.availableKernelModules = [ ... ];
</nowiki>}}


To create a hostkey run
=== Generate host key ===


<pre>ssh-keygen -t rsa -N "" -f /etc/secrets/initrd/ssh_host_rsa_key</pre>
Generate host key for the SSH daemon in <code>/etc/secrets/initrd/ssh_host_ed25519_key</code> which is required.


or
<syntaxhighlight lang="console">
# mkdir -p /etc/secrets/initrd
# ssh-keygen -t ed25519 -N "" -f /etc/secrets/initrd/ssh_host_ed25519_key
</syntaxhighlight>


<pre>ssh-keygen -t ed25519 -N "" -f /etc/secrets/initrd/ssh_host_ed25519_key</pre>
=== Configure SSH ===


==== Known hosts ====
Configure {{nixos:option|boot.initrd.network.ssh}}. Add the generated host key to {{nixos:option|boot.initrd.network.ssh.hostKeys}} and your public key to {{nixos:option|boot.initrd.network.ssh.authorizedKeys}}.


It’s a good idea to add the host key (which got printed during creation) to your known_hosts file e.g. <code>~/.ssh/known_hosts</code> or <code>services.openssh.knownHosts</code>.
{{file|/etc/nixos/configuration.nix|nix|<nowiki>boot.initrd.network = {
  enable = true;
  ssh = {
    enable = true;
    hostKeys = [
      "/etc/secrets/initrd/ssh_host_ed25519_key"
    ];
    authorizedKeys = [
      "ssh-rsa AAAAyourpublic-key-here...";
    ];
  };
};
</nowiki>}}
 
Now proceed to one of [[#Setup with Systemd]] or [[#Setup without Systemd]].
 
== Setup with Systemd ==
 
=== Configure systemd-networkd ===
 
{{nixos:option|boot.initrd.systemd.network}} has a syntax similar to {{nixos:option|systemd.network}}. For details, see [[Systemd/networkd]].
 
First find the interface name(s) (<code>eth0</code> in this example):
 
<syntaxhighlight lang="console">
# ip addr
lo ...
eth0 ...
</syntaxhighlight>
 
To configure DHCP:
 
{{file|/etc/nixos/configuration.nix|nix|3=<nowiki>
boot.initrd.systemd.network = {
  enable = true;
  networks."10-eth0" = {
    matchConfig.Name = "eth0";
    networkConfig.DHCP = "ipv4";
    # Wait until network interfaces have a routable address
    # https://wiki.archlinux.org/title/Systemd-networkd
    linkConfig.RequiredForOnline = "routable";
  };
};</nowiki>|name=/etc/nixos/configuration.nix|lang=nix}}
 
To configure static IP:
 
{{file|/etc/nixos/configuration.nix|nix|<nowiki>boot.initrd.systemd.network = {
  enable = true;
  networks."10-eth0" = {
    matchConfig.Name = "eth0";
    address = [
      "192.168.1.123/24"
      "2001:db8:1234:5678::1/64"
    ];
    routes = [
      { Gateway = "192.168.1.1"; }
      { Gateway = "fe80::1"; }
    ];
    # Wait until network interfaces have a routable address
    # https://wiki.archlinux.org/title/Systemd-networkd
    linkConfig.RequiredForOnline = "routable";
  };
};
</nowiki>}}
 
=== Debug shell ===
 
Enable debug shell. Although it is not required, enabling the debug shell allows you to enter the debug shell by press <code>Ctrl+Alt+F9</code> during Stage 1.


=== Set up SSH in initrd ===
{{file|/etc/nixos/configuration.nix|nix|<nowiki>
boot.kernelParams = [ "rd.systemd.debug_shell=1" ];
</nowiki>}}


Setting up ssh is very easy.
The network and SSH status can be checked from within the debug shell:


<pre># ssh setup
<syntaxhighlight lang="console">
boot.initrd.network.enable = true;
# networkctl status: show systemd-networkd status
boot.initrd.network.ssh = {
# journalctl -u sshd
# cat /etc/ssh/sshd_config
</syntaxhighlight>
 
=== Automatic password prompt ===
 
To automatically be prompted for a password when logging in via SSH, add <code>command="systemctl default"</code> to {{nixos:option|boot.initrd.network.ssh.authorizedKeys}}.
 
{{file|/etc/nixos/configuration.nix|diff|<nowiki>
boot.initrd.network = {
   enable = true;
   enable = true;
   port = 22;
   ssh = {
  authorizedKeys = [ &quot;ssh-rsa AAAAyourpublic-key-here....&quot; ];
    enable = true;
   hostKeys = [ "/etc/secrets/initrd/ssh_host_rsa_key" "/etc/secrets/initrd/ssh_host_ed25519_key" ];
    hostKeys = [
      "/etc/secrets/initrd/ssh_host_ed25519_key"
    ];
    authorizedKeys = [
-      "ssh-rsa AAAAyourpublic-key-here...";
+      ''command="systemctl default" ssh-rsa AAAAyourpublic-key-here...''
    ];
  };
};
</nowiki>}}
 
== Setup without Systemd ==
 
Enable SSH daemon in initrd
 
{{file|/etc/nixos/configuration.nix|nix|<nowiki>
boot.initrd = {
  systemd.enable = false;
  availableKernelModules = [ "r8169" ];
   network = {
    enable = true;
    udhcpc.enable = true;
    flushBeforeStage2 = true;
    ssh = {
      enable = true;
      port = 22;
      authorizedKeys = [ "ssh-rsa AAAAyourpublic-key-here..." ]; # The public key of the client (Not the public key created in the previous step) (required)
      hostKeys = [ "/etc/secrets/initrd/ssh_host_ed25519_key" ]; # The path of the private key created in the previous step (required)
    };
    postCommands = ''
      # unlock LUKS encrypted partitions
      echo 'cryptsetup-askpass'</nowiki> >> <nowiki>/root/.profile
      # unlock ZFS encrypted partitions (NOTE: boot.initrd.supportedFilesystems.zfs must be true for zfs, zpool to be available here)
      # zpool import -a;
      # echo 'zfs load-key -a'</nowiki> >> <nowiki>/root/.profile
      # exit SSH
      echo 'exit'</nowiki> >> <nowiki>/root/.profile
    '';
  };
};
};
</pre>
</nowiki>}}
 
{{Info|When using the systemd initrd (<code>boot.initrd.systemd.enable</code>, which is enabled by default starting with NixOS 26.05), <code>cryptsetup-askpass</code> is not available; use <code>systemctl default</code> instead. See the [https://nixos.org/manual/nixos/unstable/release-notes#sec-release-26.05 release notes] for more information.}}
 
Adapt following parts according to your setup
 
* '''authorizedKeys''': Add the SSH public keys for the users which should be able to authenticate to the SSH daemon to the <code>authorizedKeys</code> option.
* '''availableKernelModules''': Most likely your network card is not working without its kernel module being part of the initrd, so you have to find out which module is used for your network. Use <code>lspci -v | grep -iA8 'network\|ethernet'</code> for that.
* '''kernelParams''':
** When using a dynamic IP address with DHCP you might want to publish your hostname already in the initrd so it can be resolved in the local network: <code>boot.kernelParams = [ "ip=::::${config.networking.hostName}::dhcp" ];</code><ref>https://github.com/NixOS/nixpkgs/issues/63941#issuecomment-2628615604</ref> Note that when using DHCP, make sure your computer is always attached to the network and is able to get an IP adress, or the boot process will hang.
** You could also configure a static IP <code>boot.kernelParams = [ "ip=10.25.0.2::10.25.0.1:255.255.255.0:myhost::none" ];</code>, where <code>10.25.0.2</code> is the client IP, <code>10.25.0.1</code> is the gateway IP. See [https://www.kernel.org/doc/Documentation/filesystems/nfs/nfsroot.txt the kernel documentation] for more information on the <code>ip=</code> parameter.
 
 
The <code>postCommands</code> option is necessary to get a password prompt instead of a shell.
If you omit it, you will get dropped into <code>/bin/ash</code>, and you will have to manually run <code>cryptsetup-askpass</code> to enter the password. Alternatively, the <code>boot.initrd.systemd.users.root.shell</code> option can be set to <code>/bin/conspy</code> for passwords which expect stdin. This binary included by default, and provided by busybox.
 
 
== Usage ==
 
After reboot, connect to the initrd SSH daemon using
 
<syntaxhighlight lang="bash">
# ssh root@10.25.0.2
</syntaxhighlight>
 
Where <code>10.25.0.2</code> is the IP which is acquired via DHCP or configured via the kernel parameter.
 
== Tips and tricks ==
 
=== Remote bcachefs unlocking ===
 
Starting with NixOS 26.05, the bcachefs module (boot.supportedFilesystems = [ "bcachefs" ]) automatically creates an unlock-bcachefs-<mountpoint>.service in the systemd initrd for boot-critical bcachefs filesystems. This service calls systemd-ask-password and pipes the response to bcachefs unlock, running before the generated sysroot.mount unit.
 
For remote unlocking via SSH, set the initrd root shell to systemd-tty-ask-password-agent --watch, which picks up the pending password request and displays the prompt over the SSH connection. The agent must be added to the initrd's /bin via extraBin, as it is not included by default.
 
{{file|/etc/nixos/configuration.nix|nix|<nowiki>
boot.initrd.systemd = let
  unlockShell = pkgs.writeShellScriptBin "bcachefs-unlock-shell" ''
    keyctl link @u @s 2>/dev/null || true
    exec systemd-tty-ask-password-agent --watch
  '';
in {
  enable = true;
  initrdBin = with pkgs; [ keyutils ];
  extraBin = {
    "systemd-tty-ask-password-agent" = "${lib.getExe' pkgs.systemd "systemd-tty-ask-password-agent"}";
    "bcachefs-unlock-shell" = "${lib.getExe unlockShell}";
  };
  users.root.shell = "/bin/bcachefs-unlock-shell";
};
</nowiki>}}
 
=== Wireguard in initrd ===
Considering you've already enabled the ssh daemon, configured networking (for example with DHCP or static IP) and configured an unlocking command, following additional snippet will enable [[WireGuard]] connectivity to a remote peer while in initrd.<syntaxhighlight lang="nix">
boot.initrd.availableKernelModules = [ "r8169" "wireguard" ];
boot.initrd.systemd = {
  enable = true;
  network = {
    netdevs."30-wg-initrd" = {
      netdevConfig = {
        Kind = "wireguard";
        Name = "wg-initrd";
      };
      wireguardConfig = { PrivateKeyFile = "/etc/secrets/30-wg-initrd.key"; };
      wireguardPeers = [{
        AllowedIPs = [ "10.250.0.1/32" ];
        PublicKey = "wUE//Lwi8DZVIvAjIAtMoy+ku+hJ0w28H7ofySwAJRk=";
        Endpoint = "198.51.100.1:51821";
        PersistentKeepalive = 25;
      }];
    };
    networks."30-wg-initrd" = {
      name = "wg-initrd";
      addresses = [{ Address = "10.250.0.2/24"; }];
    };
  };
};
boot.initrd.secrets."/etc/secrets/30-wg-initrd.key" = "/etc/wireguard/private-key";
</syntaxhighlight>First generate a private und public key pair as mentioned in the WireGuard article. Reference the private key in <code>boot.initrd.secrets</code>, in this exmaple <code>/etc/wireguard/private-key</code>. Put the <code>PublicKey</code> of the remote peer into the <code>wireguardPeers</code> array.


Most likely your network card is not working without its kernel module being part of the initrd, so you have to find out which module is used for your network. Use <code>lspci -v</code> for that.
Configure the IP addresses used by your initrd peer (<code>10.250.0.2</code>) and the remote peer (<code>10.250.0.1</code>). Also specify the IP and port of the remote peer in <code>Endpoint</code>, in our example <code>198.51.100.1:51821</code>. The remote peer also needs to know address configuration and the public key of the initrd peer.


<pre>boot.initrd.availableKernelModules = [ &quot;r8169&quot; ];</pre>
Last but not least add the <code>wireguard</code> kernel module to <code>boot.initrd.availableKernelModules</code> beside the module required by your network device.


== Tor in initrd ==
=== Tor in initrd ===


=== Prepare the Onion ID ===
==== Prepare the Onion ID ====


You need 3 files to create an onion id (a.k.a. tor hidden service).
You need 3 files to create an onion id (a.k.a. tor hidden service).
Line 49: Line 248:
* <code>hs_ed25519_secret_key</code>
* <code>hs_ed25519_secret_key</code>


To create these files, you have to run tor once, with a dummy configuration.
To create these files:
$ nix-shell -p mkp224o --command "mkp224o-donna a -n 1 -d ."
set workdir: ./
nixuum6flqthv6ar52j5e2ldulylfsfgezykeg37iy74kqowcp5gxfyd.onion
The files you need are in the <code>*.onion</code> directory:
$ ls *.onion
hostname  hs_ed25519_public_key  hs_ed25519_secret_key


<pre>DataDirectory /tmp/my-dummy.tor/
==== Setup Tor (systemd stage1, since NixOS 26.05) ====
SOCKSPort 127.0.0.1:10050 IsolateDestAddr
Since version 26.05, NixOS uses systemd stage1 in initrd.
SOCKSPort 127.0.0.1:10063
HiddenServiceDir /home/tony/tor/onion
HiddenServicePort 1234 127.0.0.1:1234</pre>
Let’s asume you created this file in <code>/home/tony/tor/tor.rc</code>.


Verify that everything is <code>tor.rc</code> awesome, by running <code>tor -f /home/tony/tor/tor.rc --verify-config</code>. If you don’t see any errors, just run <code>tor -f /home/tony/tor/tor.rc</code>.
The following module starts a tor daemon in the initrd and uses it to expose the ssh port on an onion address.<syntaxhighlight lang="nixos">
{ config, pkgs, ... }:
let
  onionDir = "/etc/tor/onion/bootup";
  initrdTorRc = (pkgs.writeText "tor.rc" ''
    DataDirectory /etc/tor
    ShutdownWaitLength 0
    HiddenServiceDir ${onionDir}
    HiddenServicePort ${builtins.toString config.boot.initrd.network.ssh.port}
  '');
in
{
  boot.initrd = {
    secrets = {
      "${onionDir}" = /etc/secrets/initrd/onion; # Adapt to the location of your onion keys
    };
    systemd = {
      initrdBin = [ pkgs.tor ];
      storePaths = [ initrdTorRc ];
      services."tor" = {
        description = "Tor daemon";
        preStart = ''
          echo "tor: preparing onion keys"
          chmod -R 700 /etc/tor
        '';
        script = ''
          echo "tor: starting tor"
          tor -f ${initrdTorRc} --verify-config
          tor -f ${initrdTorRc}
        '';
        unitConfig.DefaultDependencies = false;
        wantedBy = [ "initrd.target" ];
        after = [
          "network.target"
          "initrd-nixos-copy-secrets.service"
        ];
        before = [ "shutdown.target" ];
        conflicts = [ "shutdown.target" ];
      };
    };
  };
}


You will get some output like this.
</syntaxhighlight>


<pre>May 21 18:38:39.000 [notice] Bootstrapped 80% (ap_conn): Connecting to a relay to build circuits
==== Setup Tor (pre NixOS 26.05) ====
May 21 18:38:39.000 [notice] Bootstrapped 85% (ap_conn_done): Connected to a relay to build circuits
May 21 18:38:39.000 [notice] Bootstrapped 89% (ap_handshake): Finishing handshake with a relay to build circuits
May 21 18:38:39.000 [notice] Bootstrapped 90% (ap_handshake_done): Handshake finished with a relay to build circuits
May 21 18:38:39.000 [notice] Bootstrapped 95% (circuit_create): Establishing a Tor circuit
May 21 18:38:40.000 [notice] Bootstrapped 100% (done): Done</pre>
Hit <code>Ctrl-C</code> and the files you need, should be in <code>/home/tony/tor/onion</code>.


=== Setup Tor ===
Now that you have your 3 files, you have to script a bit, but it’s not too complicated. The snippet is adapted from [https://cgit.euer.krebsco.de/makefu/stockholm/src/commit/9b1008814e981dc01afe9ee7446322ad512c1d72/krebs/2configs/tor/initrd.nix krebs/2configs/tor/initrd.nix in stockholm].


Now that you have your 3 files, you have to script a bit, but it’s not too complicated.
<syntaxhighlight lang="nix"># copy your onion folder
 
<pre># copy your onion folder
boot.initrd.secrets = {
boot.initrd.secrets = {
   &quot;/etc/tor/onion/bootup&quot; = /home/tony/tor/onion; # maybe find a better spot to store this.
   "/etc/tor/onion/bootup" = /home/tony/tor/onion; # maybe find a better spot to store this.
};
};


Line 86: Line 320:
# start tor during boot process
# start tor during boot process
boot.initrd.network.postCommands = let
boot.initrd.network.postCommands = let
   torRc = (pkgs.writeText &quot;tor.rc&quot; ''
   torRc = (pkgs.writeText "tor.rc" ''
     DataDirectory /etc/tor
     DataDirectory /etc/tor
     SOCKSPort 127.0.0.1:9050 IsolateDestAddr
     SOCKSPort 127.0.0.1:9050 IsolateDestAddr
Line 94: Line 328:
   '');
   '');
in ''
in ''
   echo &quot;tor: preparing onion folder&quot;
   echo "tor: preparing onion folder"
   # have to do this otherwise tor does not want to start
   # have to do this otherwise tor does not want to start
   chmod -R 700 /etc/tor
   chmod -R 700 /etc/tor


   echo &quot;make sure localhost is up&quot;
   echo "make sure localhost is up"
   ip a a 127.0.0.1/8 dev lo
   ip a a 127.0.0.1/8 dev lo
   ip link set lo up
   ip link set lo up


   echo &quot;tor: starting tor&quot;
   echo "tor: starting tor"
   tor -f ${torRc} --verify-config
   tor -f ${torRc} --verify-config
   tor -f ${torRc} &amp;
   tor -f ${torRc} &
'';</pre>
'';</syntaxhighlight>
That was it. Tor should be running during your boot process.
That was it. Tor should be running during your boot process.


=== Setup haveged ===
==== Setup haveged ====


If your system doesn't gather enough entropy the startup time of tor is rather long (2:42 vs 0:06 on a RPi 4b). Counter it by starting <code>haveged</code>.
If your system doesn't gather enough entropy the startup time of tor is rather long (2:42 vs 0:06 on a RPi 4b). Counter it by starting <code>haveged</code>.
Line 117: Line 351:
</pre>
</pre>


Then use this snippet before <code>echo &quot;tor: starting tor&quot;</code> in your <code>boot.initrd.network.postCommands</code>.
Then use this snippet before <code>echo "tor: starting tor"</code> in your <code>boot.initrd.network.postCommands</code>.
<pre>
<pre>
       echo "haveged: starting haveged"
       echo "haveged: starting haveged"
Line 123: Line 357:
</pre>
</pre>


=== Setup ntpdate ===
==== Setup ntpdate ====


If your system doesn't utilize a RTC you've to ensure time is correctly set before startup of tor.
If your system doesn't utilize a RTC you've to ensure time is correctly set before startup of tor.
Line 132: Line 366:
</pre>
</pre>


Then use this snippet before <code>echo &quot;tor: starting tor&quot;</code> in your <code>boot.initrd.network.postCommands</code>.
Then use this snippet before <code>echo "tor: starting tor"</code> in your <code>boot.initrd.network.postCommands</code>.
<pre>
<pre>
       echo "ntp: starting ntpdate"
       echo "ntp: starting ntpdate"
Line 140: Line 374:
</pre>
</pre>


== Unlock your LUKS via SSH and Tor ==
==== Usage ====


When your computer boots, and asks for the LUKS password. Now you can unlock your encrypted Hard drive using:
When your computer boots, and asks for the LUKS password. Now you can unlock your encrypted Hard drive using:


<pre>torify ssh root@&lt;onion.id&gt;.onion -p 22 'echo &quot;my-secret-password&quot; &gt; /crypt-ramfs/passphrase'</pre>
<pre>torify ssh root@<onion.id>.onion -p 22 'my-secret-password'</pre>
 
=== Enable Wifi in initrd ===
Following example configuration by [https://discourse.nixos.org/t/wireless-connection-within-initrd/38317/13 @loutr] enables wifi connections inside initrd. Replace interface name <code>wlp0s20f0u4</code> with the name of your wifi adapter. Depending on your wifi device, you might need to add different kernel modules.<syntaxhighlight lang="nix">
{
  boot.initrd = {
    # crypto coprocessor and wifi modules
    availableKernelModules = [
      "ccm"
      "ctr"
      "iwlmvm"
      "iwlwifi"
    ];
 
    systemd = {
      enable = true;
 
      packages = [ pkgs.wpa_supplicant ];
      initrdBin = [ pkgs.wpa_supplicant ];
      targets.initrd.wants = [ "wpa_supplicant@wlp0s20f0u4.service" ];
 
      # prevent WPA supplicant from requiring `sysinit.target`.
      services."wpa_supplicant@".unitConfig.DefaultDependencies = false;
 
      users.root.shell = "/bin/systemd-tty-ask-password-agent";
 
      network = {
        enable = true;
        networks."10-wlan" = {
          matchConfig.Name = "wlp0s20f0u4";
          DHCP = "yes";
        };
      };
    };
 
    network.ssh = {
      enable = true;
      port = 22;
      hostKeys = [ "/etc/ssh/ssh_host_ed25519_key" ];
      authorizedKeys = default.user.openssh.authorizedKeys.keys;
    };


== Example ==
    secrets."/etc/wpa_supplicant/wpa_supplicant-wlp0s20f0u4.conf" = /root/secrets/wpa_supplicant.conf;
An example with an ssh server listening at a tor hidden service address can be found at [https://cgit.euer.krebsco.de/stockholm/tree/krebs/2configs/tor/initrd.nix?id=9919cb25912dfcc50881239f95494dd2f8e7b858 krebs/2configs/tor/initrd.nix in stockholm]
  };
}
</syntaxhighlight>The file <code>wpa_supplicant-wlp0s20f0u4.conf</code> is the wireless profile used by [[wpa_supplicant]] which will get copied into the initramfs.
[[Category:Server]]
[[Category:Cookbook]]