summary refs log tree commit diff
path: root/nixos/modules/system/boot/initrd-ssh.nix
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules/system/boot/initrd-ssh.nix')
-rw-r--r--nixos/modules/system/boot/initrd-ssh.nix178
1 files changed, 124 insertions, 54 deletions
diff --git a/nixos/modules/system/boot/initrd-ssh.nix b/nixos/modules/system/boot/initrd-ssh.nix
index 2d3e3b05c98..5a334e69056 100644
--- a/nixos/modules/system/boot/initrd-ssh.nix
+++ b/nixos/modules/system/boot/initrd-ssh.nix
@@ -10,19 +10,21 @@ in
 
 {
 
-  options = {
-
-    boot.initrd.network.ssh.enable = mkOption {
+  options.boot.initrd.network.ssh = {
+    enable = mkOption {
       type = types.bool;
       default = false;
       description = ''
         Start SSH service during initrd boot. It can be used to debug failing
         boot on a remote server, enter pasphrase for an encrypted partition etc.
         Service is killed when stage-1 boot is finished.
+
+        The sshd configuration is largely inherited from
+        <option>services.openssh</option>.
       '';
     };
 
-    boot.initrd.network.ssh.port = mkOption {
+    port = mkOption {
       type = types.int;
       default = 22;
       description = ''
@@ -30,7 +32,7 @@ in
       '';
     };
 
-    boot.initrd.network.ssh.shell = mkOption {
+    shell = mkOption {
       type = types.str;
       default = "/bin/ash";
       description = ''
@@ -38,95 +40,163 @@ in
       '';
     };
 
-    boot.initrd.network.ssh.hostRSAKey = mkOption {
-      type = types.nullOr types.path;
-      default = null;
-      description = ''
-        RSA SSH private key file in the Dropbear format.
-
-        WARNING: Unless your bootloader supports initrd secrets, this key is
-        contained insecurely in the global Nix store. Do NOT use your regular
-        SSH host private keys for this purpose or you'll expose them to
-        regular users!
-      '';
-    };
-
-    boot.initrd.network.ssh.hostDSSKey = mkOption {
-      type = types.nullOr types.path;
-      default = null;
+    hostKeys = mkOption {
+      type = types.listOf (types.either types.str types.path);
+      default = [];
+      example = [
+        "/etc/secrets/initrd/ssh_host_rsa_key"
+        "/etc/secrets/initrd/ssh_host_ed25519_key"
+      ];
       description = ''
-        DSS SSH private key file in the Dropbear format.
-
-        WARNING: Unless your bootloader supports initrd secrets, this key is
-        contained insecurely in the global Nix store. Do NOT use your regular
-        SSH host private keys for this purpose or you'll expose them to
-        regular users!
+        Specify SSH host keys to import into the initrd.
+
+        To generate keys, use
+        <citerefentry><refentrytitle>ssh-keygen</refentrytitle><manvolnum>1</manvolnum></citerefentry>:
+
+        <screen>
+        <prompt># </prompt>ssh-keygen -t rsa -N "" -f /etc/secrets/initrd/ssh_host_rsa_key
+        <prompt># </prompt>ssh-keygen -t ed25519 -N "" -f /etc/secrets/initrd/ssh_host_ed_25519_key
+        </screen>
+
+        <warning>
+          <para>
+            Unless your bootloader supports initrd secrets, these keys
+            are stored insecurely in the global Nix store. Do NOT use
+            your regular SSH host private keys for this purpose or
+            you'll expose them to regular users!
+          </para>
+          <para>
+            Additionally, even if your initrd supports secrets, if
+            you're using initrd SSH to unlock an encrypted disk then
+            using your regular host keys exposes the private keys on
+            your unencrypted boot partition.
+          </para>
+        </warning>
       '';
     };
 
-    boot.initrd.network.ssh.hostECDSAKey = mkOption {
-      type = types.nullOr types.path;
-      default = null;
-      description = ''
-        ECDSA SSH private key file in the Dropbear format.
-
-        WARNING: Unless your bootloader supports initrd secrets, this key is
-        contained insecurely in the global Nix store. Do NOT use your regular
-        SSH host private keys for this purpose or you'll expose them to
-        regular users!
-      '';
-    };
-
-    boot.initrd.network.ssh.authorizedKeys = mkOption {
+    authorizedKeys = mkOption {
       type = types.listOf types.str;
       default = config.users.users.root.openssh.authorizedKeys.keys;
+      defaultText = "config.users.users.root.openssh.authorizedKeys.keys";
       description = ''
         Authorized keys for the root user on initrd.
-        Note that Dropbear doesn't support OpenSSH's Ed25519 key type.
       '';
     };
-
   };
 
-  config = mkIf (config.boot.initrd.network.enable && cfg.enable) {
+  imports =
+    map (opt: mkRemovedOptionModule ([ "boot" "initrd" "network" "ssh" ] ++ [ opt ]) ''
+      The initrd SSH functionality now uses OpenSSH rather than Dropbear.
+
+      If you want to keep your existing initrd SSH host keys, convert them with
+        $ dropbearconvert dropbear openssh dropbear_host_$type_key ssh_host_$type_key
+      and then set options.boot.initrd.network.ssh.hostKeys.
+    '') [ "hostRSAKey" "hostDSSKey" "hostECDSAKey" ];
+
+  config = let
+    # Nix complains if you include a store hash in initrd path names, so
+    # as an awful hack we drop the first character of the hash.
+    initrdKeyPath = path: if isString path
+      then path
+      else let name = builtins.baseNameOf path; in
+        builtins.unsafeDiscardStringContext ("/etc/ssh/" +
+          substring 1 (stringLength name) name);
+
+    sshdCfg = config.services.openssh;
+
+    sshdConfig = ''
+      Port ${toString cfg.port}
+
+      PasswordAuthentication no
+      ChallengeResponseAuthentication no
+
+      ${flip concatMapStrings cfg.hostKeys (path: ''
+        HostKey ${initrdKeyPath path}
+      '')}
+
+      KexAlgorithms ${concatStringsSep "," sshdCfg.kexAlgorithms}
+      Ciphers ${concatStringsSep "," sshdCfg.ciphers}
+      MACs ${concatStringsSep "," sshdCfg.macs}
+
+      LogLevel ${sshdCfg.logLevel}
+
+      ${if sshdCfg.useDns then ''
+        UseDNS yes
+      '' else ''
+        UseDNS no
+      ''}
+    '';
+  in mkIf (config.boot.initrd.network.enable && cfg.enable) {
     assertions = [
-      { assertion = cfg.authorizedKeys != [];
+      {
+        assertion = cfg.authorizedKeys != [];
         message = "You should specify at least one authorized key for initrd SSH";
       }
+
+      {
+        assertion = cfg.hostKeys != [];
+        message = ''
+          You must now pre-generate the host keys for initrd SSH.
+          See the boot.initrd.network.ssh.hostKeys documentation
+          for instructions.
+        '';
+      }
     ];
 
     boot.initrd.extraUtilsCommands = ''
-      copy_bin_and_libs ${pkgs.dropbear}/bin/dropbear
+      copy_bin_and_libs ${pkgs.openssh}/bin/sshd
       cp -pv ${pkgs.glibc.out}/lib/libnss_files.so.* $out/lib
     '';
 
     boot.initrd.extraUtilsCommandsTest = ''
-      $out/bin/dropbear -V
+      # sshd requires a host key to check config, so we pass in the test's
+      echo -n ${escapeShellArg sshdConfig} |
+        $out/bin/sshd -t -f /dev/stdin \
+        -h ${../../../tests/initrd-network-ssh/ssh_host_ed25519_key}
     '';
 
     boot.initrd.network.postCommands = ''
       echo '${cfg.shell}' > /etc/shells
       echo 'root:x:0:0:root:/root:${cfg.shell}' > /etc/passwd
+      echo 'sshd:x:1:1:sshd:/var/empty:/bin/nologin' >> /etc/passwd
       echo 'passwd: files' > /etc/nsswitch.conf
 
-      mkdir -p /var/log
+      mkdir -p /var/log /var/empty
       touch /var/log/lastlog
 
-      mkdir -p /etc/dropbear
+      mkdir -p /etc/ssh
+      echo -n ${escapeShellArg sshdConfig} > /etc/ssh/sshd_config
+
+      echo "export PATH=$PATH" >> /etc/profile
+      echo "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH" >> /etc/profile
 
       mkdir -p /root/.ssh
       ${concatStrings (map (key: ''
         echo ${escapeShellArg key} >> /root/.ssh/authorized_keys
       '') cfg.authorizedKeys)}
 
-      dropbear -s -j -k -E -p ${toString cfg.port} ${optionalString (cfg.hostRSAKey == null && cfg.hostDSSKey == null && cfg.hostECDSAKey == null) "-R"}
+      ${flip concatMapStrings cfg.hostKeys (path: ''
+        # keys from Nix store are world-readable, which sshd doesn't like
+        chmod 0600 "${initrdKeyPath path}"
+      '')}
+
+      /bin/sshd -e
     '';
 
-    boot.initrd.secrets =
-     (optionalAttrs (cfg.hostRSAKey != null) { "/etc/dropbear/dropbear_rsa_host_key" = cfg.hostRSAKey; }) //
-     (optionalAttrs (cfg.hostDSSKey != null) { "/etc/dropbear/dropbear_dss_host_key" = cfg.hostDSSKey; }) //
-     (optionalAttrs (cfg.hostECDSAKey != null) { "/etc/dropbear/dropbear_ecdsa_host_key" = cfg.hostECDSAKey; });
+    boot.initrd.postMountCommands = ''
+      # Stop sshd cleanly before stage 2.
+      #
+      # If you want to keep it around to debug post-mount SSH issues,
+      # run `touch /.keep_sshd` (either from an SSH session or in
+      # another initrd hook like preDeviceCommands).
+      if ! [ -e /.keep_sshd ]; then
+        pkill -x sshd
+      fi
+    '';
 
+    boot.initrd.secrets = listToAttrs
+      (map (path: nameValuePair (initrdKeyPath path) path) cfg.hostKeys);
   };
 
 }