Commit 0698a1cf authored by Will Fancher's avatar Will Fancher
Browse files

systemd-initrd: sshd

parent 748f1329
Loading
Loading
Loading
Loading
+54 −10
Original line number Diff line number Diff line
@@ -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";
        };
      };
    };

  };

}
+1 −0
Original line number Diff line number Diff line
@@ -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 {};
+82 −0
Original line number Diff line number Diff line
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 /'")
  '';
})