about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
authorWill Fancher <elvishjerricco@gmail.com>2022-08-03 06:36:11 -0400
committerWill Fancher <elvishjerricco@gmail.com>2023-04-17 16:41:35 -0400
commit0698a1cf04392a24f740b4b5e16c5bd33642b6df (patch)
tree1b02e31dcf78521f55b21e40c85ffb1c99562f20 /nixos
parent748f1329fcee7db22be526677089719a5276ba30 (diff)
systemd-initrd: sshd
Diffstat (limited to 'nixos')
-rw-r--r--nixos/modules/system/boot/initrd-ssh.nix64
-rw-r--r--nixos/tests/all-tests.nix1
-rw-r--r--nixos/tests/systemd-initrd-networkd-ssh.nix82
3 files changed, 137 insertions, 10 deletions
diff --git a/nixos/modules/system/boot/initrd-ssh.nix b/nixos/modules/system/boot/initrd-ssh.nix
index 125f75d667069..60c5ff62ffff0 100644
--- a/nixos/modules/system/boot/initrd-ssh.nix
+++ b/nixos/modules/system/boot/initrd-ssh.nix
@@ -5,6 +5,10 @@ with lib;
 let
 
   cfg = config.boot.initrd.network.ssh;
+  shell = if cfg.shell == null then "/bin/ash" else cfg.shell;
+  inherit (config.programs.ssh) package;
+
+  enabled = let initrd = config.boot.initrd; in (initrd.network.enable || initrd.systemd.network.enable) && cfg.enable;
 
 in
 
@@ -33,8 +37,9 @@ in
     };
 
     shell = mkOption {
-      type = types.str;
-      default = "/bin/ash";
+      type = types.nullOr types.str;
+      default = null;
+      defaultText = ''"/bin/ash"'';
       description = lib.mdDoc ''
         Login shell of the remote user. Can be used to limit actions user can do.
       '';
@@ -119,9 +124,11 @@ in
     sshdCfg = config.services.openssh;
 
     sshdConfig = ''
+      UsePAM no
       Port ${toString cfg.port}
 
       PasswordAuthentication no
+      AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys2 /etc/ssh/authorized_keys.d/%u
       ChallengeResponseAuthentication no
 
       ${flip concatMapStrings cfg.hostKeys (path: ''
@@ -142,7 +149,7 @@ in
 
       ${cfg.extraConfig}
     '';
-  in mkIf (config.boot.initrd.network.enable && cfg.enable) {
+  in mkIf enabled {
     assertions = [
       {
         assertion = cfg.authorizedKeys != [];
@@ -157,14 +164,19 @@ in
           for instructions.
         '';
       }
+
+      {
+        assertion = config.boot.initrd.systemd.enable -> cfg.shell == null;
+        message = "systemd stage 1 does not support boot.initrd.network.ssh.shell";
+      }
     ];
 
-    boot.initrd.extraUtilsCommands = ''
-      copy_bin_and_libs ${pkgs.openssh}/bin/sshd
+    boot.initrd.extraUtilsCommands = mkIf (!config.boot.initrd.systemd.enable) ''
+      copy_bin_and_libs ${package}/bin/sshd
       cp -pv ${pkgs.glibc.out}/lib/libnss_files.so.* $out/lib
     '';
 
-    boot.initrd.extraUtilsCommandsTest = ''
+    boot.initrd.extraUtilsCommandsTest = mkIf (!config.boot.initrd.systemd.enable) ''
       # sshd requires a host key to check config, so we pass in the test's
       tmpkey="$(mktemp initrd-ssh-testkey.XXXXXXXXXX)"
       cp "${../../../tests/initrd-network-ssh/ssh_host_ed25519_key}" "$tmpkey"
@@ -176,9 +188,9 @@ in
       rm "$tmpkey"
     '';
 
-    boot.initrd.network.postCommands = ''
-      echo '${cfg.shell}' > /etc/shells
-      echo 'root:x:0:0:root:/root:${cfg.shell}' > /etc/passwd
+    boot.initrd.network.postCommands = mkIf (!config.boot.initrd.systemd.enable) ''
+      echo '${shell}' > /etc/shells
+      echo 'root:x:0:0:root:/root:${shell}' > /etc/passwd
       echo 'sshd:x:1:1:sshd:/var/empty:/bin/nologin' >> /etc/passwd
       echo 'passwd: files' > /etc/nsswitch.conf
 
@@ -204,7 +216,7 @@ in
       /bin/sshd -e
     '';
 
-    boot.initrd.postMountCommands = ''
+    boot.initrd.postMountCommands = mkIf (!config.boot.initrd.systemd.enable) ''
       # Stop sshd cleanly before stage 2.
       #
       # If you want to keep it around to debug post-mount SSH issues,
@@ -217,6 +229,38 @@ in
 
     boot.initrd.secrets = listToAttrs
       (map (path: nameValuePair (initrdKeyPath path) path) cfg.hostKeys);
+
+    # Systemd initrd stuff
+    boot.initrd.systemd = mkIf config.boot.initrd.systemd.enable {
+      users.sshd = { uid = 1; group = "sshd"; };
+      groups.sshd = { gid = 1; };
+
+      contents."/etc/ssh/authorized_keys.d/root".text =
+        concatStringsSep "\n" config.boot.initrd.network.ssh.authorizedKeys;
+      contents."/etc/ssh/sshd_config".text = sshdConfig;
+      storePaths = ["${package}/bin/sshd"];
+
+      services.sshd = {
+        description = "SSH Daemon";
+        wantedBy = ["initrd.target"];
+        after = ["network.target" "initrd-nixos-copy-secrets.service"];
+
+        # Keys from Nix store are world-readable, which sshd doesn't
+        # like. If this were a real nix store and not the initrd, we
+        # neither would nor could do this
+        preStart = flip concatMapStrings cfg.hostKeys (path: ''
+          /bin/chmod 0600 "${initrdKeyPath path}"
+        '');
+        unitConfig.DefaultDependencies = false;
+        serviceConfig = {
+          ExecStart = "${package}/bin/sshd -D -f /etc/ssh/sshd_config";
+          Type = "simple";
+          KillMode = "process";
+          Restart = "on-failure";
+        };
+      };
+    };
+
   };
 
 }
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 00a637b460f54..5771d1c3bc96d 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -678,6 +678,7 @@ in {
   systemd-initrd-swraid = handleTest ./systemd-initrd-swraid.nix {};
   systemd-initrd-vconsole = handleTest ./systemd-initrd-vconsole.nix {};
   systemd-initrd-networkd = handleTest ./systemd-initrd-networkd.nix {};
+  systemd-initrd-networkd-ssh = handleTest ./systemd-initrd-networkd-ssh.nix {};
   systemd-journal = handleTest ./systemd-journal.nix {};
   systemd-machinectl = handleTest ./systemd-machinectl.nix {};
   systemd-networkd = handleTest ./systemd-networkd.nix {};
diff --git a/nixos/tests/systemd-initrd-networkd-ssh.nix b/nixos/tests/systemd-initrd-networkd-ssh.nix
new file mode 100644
index 0000000000000..943552613be99
--- /dev/null
+++ b/nixos/tests/systemd-initrd-networkd-ssh.nix
@@ -0,0 +1,82 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "systemd-initrd-network-ssh";
+  meta.maintainers = [ lib.maintainers.elvishjerricco ];
+
+  nodes = with lib; {
+    server = { config, pkgs, ... }: {
+      environment.systemPackages = [pkgs.cryptsetup];
+      boot.loader.systemd-boot.enable = true;
+      boot.loader.timeout = 0;
+      virtualisation = {
+        emptyDiskImages = [ 4096 ];
+        useBootLoader = true;
+        useEFIBoot = true;
+      };
+
+      specialisation.encrypted-root.configuration = {
+        virtualisation.bootDevice = "/dev/mapper/root";
+        boot.initrd.luks.devices = lib.mkVMOverride {
+          root.device = "/dev/vdc";
+        };
+        boot.initrd.systemd.enable = true;
+        boot.initrd.network = {
+          enable = true;
+          ssh = {
+            enable = true;
+            authorizedKeys = [ (readFile ./initrd-network-ssh/id_ed25519.pub) ];
+            port = 22;
+            # Terrible hack so it works with useBootLoader
+            hostKeys = [ { outPath = "${./initrd-network-ssh/ssh_host_ed25519_key}"; } ];
+          };
+        };
+      };
+    };
+
+    client = { config, ... }: {
+      environment.etc = {
+        knownHosts = {
+          text = concatStrings [
+            "server,"
+            "${
+              toString (head (splitString " " (toString
+                (elemAt (splitString "\n" config.networking.extraHosts) 2))))
+            } "
+            "${readFile ./initrd-network-ssh/ssh_host_ed25519_key.pub}"
+          ];
+        };
+        sshKey = {
+          source = ./initrd-network-ssh/id_ed25519;
+          mode = "0600";
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    def ssh_is_up(_) -> bool:
+        status, _ = client.execute("nc -z server 22")
+        return status == 0
+
+    server.wait_for_unit("multi-user.target")
+    server.succeed(
+        "echo somepass | cryptsetup luksFormat --type=luks2 /dev/vdc",
+        "bootctl set-default nixos-generation-1-specialisation-encrypted-root.conf",
+        "sync",
+    )
+    server.shutdown()
+    server.start()
+
+    client.wait_for_unit("network.target")
+    with client.nested("waiting for SSH server to come up"):
+        retry(ssh_is_up)
+
+    client.succeed(
+        "echo somepass | ssh -i /etc/sshKey -o UserKnownHostsFile=/etc/knownHosts server 'systemd-tty-ask-password-agent' & exit"
+    )
+
+    server.wait_for_unit("multi-user.target")
+    server.succeed("mount | grep '/dev/mapper/root on /'")
+  '';
+})