Unverified Commit 33d8dbfa authored by tomf's avatar tomf Committed by GitHub
Browse files

nixos/sshd: add generateHostKeys setting (#418136)

parents 56b858a7 375fc85a
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -22,4 +22,4 @@

<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->

- Create the first release note entry in this section!
- `services.openssh` now supports generating host SSH keys by setting `services.openssh.generateHostKeys = true` while leaving `services.openssh.enable` disabled.  This is particularly useful for systems that have no need of an SSH daemon but want SSH host keys for other purposes such as using agenix or sops-nix.
+225 −207
Original line number Diff line number Diff line
@@ -381,6 +381,19 @@ in
        '';
      };

      generateHostKeys = lib.mkOption {
        type = lib.types.bool;
        default = config.services.openssh.enable;
        defaultText = lib.literalExpression "services.openssh.enable";
        description = ''
          Whether to generate SSH host keys.

          This can be enabled explicitly if you want to generate host keys but
          don't want to enable the SSH daemon.
        '';
        example = true;
      };

      banner = lib.mkOption {
        type = lib.types.nullOr lib.types.lines;
        default = null;
@@ -669,7 +682,8 @@ in

  ###### implementation

  config = lib.mkIf cfg.enable {
  config = lib.mkMerge [
    (lib.mkIf cfg.enable {

      users.users.sshd = {
        isSystemUser = true;
@@ -731,7 +745,7 @@ in
            "network.target"
            "sshd-keygen.service"
          ];
        wants = [ "sshd-keygen.service" ];
          wants = lib.mkIf cfg.generateHostKeys [ "sshd-keygen.service" ];
          stopIfChanged = false;
          path = [ cfg.package ];
          environment.LD_LIBRARY_PATH = nssModulesPath;
@@ -756,7 +770,7 @@ in
            "network.target"
            "sshd-keygen.service"
          ];
        wants = [ "sshd-keygen.service" ];
          wants = lib.mkIf cfg.generateHostKeys [ "sshd-keygen.service" ];
          stopIfChanged = false;
          path = [ cfg.package ];
          environment.LD_LIBRARY_PATH = nssModulesPath;
@@ -775,33 +789,6 @@ in
            KillMode = "process";
          };
        };

      services.sshd-keygen = {
        description = "SSH Host Keys Generation";
        unitConfig = {
          ConditionFileNotEmpty = map (k: "|!${k.path}") cfg.hostKeys;
        };
        serviceConfig = {
          Type = "oneshot";
        };
        path = [ cfg.package ];
        script = lib.flip lib.concatMapStrings cfg.hostKeys (k: ''
          if ! [ -s "${k.path}" ]; then
              if ! [ -h "${k.path}" ]; then
                  rm -f "${k.path}"
              fi
              mkdir -p "$(dirname '${k.path}')"
              chmod 0755 "$(dirname '${k.path}')"
              ssh-keygen \
                -t "${k.type}" \
                ${lib.optionalString (k ? bits) "-b ${toString k.bits}"} \
                ${lib.optionalString (k ? comment) "-C '${k.comment}'"} \
                ${lib.optionalString (k ? openSSHFormat && k.openSSHFormat) "-o"} \
                -f "${k.path}" \
                -N ""
          fi
        '');
      };
      };

      networking.firewall.allowedTCPPorts = lib.optionals cfg.openFirewall cfg.ports;
@@ -915,6 +902,37 @@ in
          message = "addr must be specified in each listenAddresses entry";
        }
      );
    })

    (lib.mkIf cfg.generateHostKeys {
      systemd.services.sshd-keygen = {
        description = "SSH Host Keys Generation";
        wantedBy = [ "multi-user.target" ];
        unitConfig = {
          ConditionFileNotEmpty = map (k: "|!${k.path}") cfg.hostKeys;
        };
        serviceConfig = {
          Type = "oneshot";
        };
        path = [ cfg.package ];
        script = lib.flip lib.concatMapStrings cfg.hostKeys (k: ''
          if ! [ -s "${k.path}" ]; then
              if ! [ -h "${k.path}" ]; then
                  rm -f "${k.path}"
              fi
              mkdir -p "$(dirname '${k.path}')"
              chmod 0755 "$(dirname '${k.path}')"
              ssh-keygen \
                -t "${k.type}" \
                ${lib.optionalString (k ? bits) "-b ${toString k.bits}"} \
                ${lib.optionalString (k ? comment) "-C '${k.comment}'"} \
                ${lib.optionalString (k ? openSSHFormat && k.openSSHFormat) "-o"} \
                -f "${k.path}" \
                -N ""
          fi
        '');
      };
    })
  ];

}
+26 −0
Original line number Diff line number Diff line
@@ -250,6 +250,15 @@ in
        };
      };

    server-no-sshd-with-key =
      { pkgs, ... }:
      {
        services.openssh.generateHostKeys = true;
        users.users.root.openssh.authorizedKeys.keys = [
          snakeOilPublicKey
        ];
      };

    client =
      { ... }:
      {
@@ -276,6 +285,10 @@ in
    server_localhost_only_lazy.wait_for_unit("sshd.socket", timeout=30)
    server_lazy_socket.wait_for_unit("sshd.socket", timeout=30)

    # sshd-keygen is a oneshot unit, so just wait for multi-user.target, which
    # pulls it in.
    server_no_sshd_with_key.wait_for_unit("multi-user.target", timeout=30)

    with subtest("manual-authkey"):
        client.succeed(
            '${pkgs.openssh}/bin/ssh-keygen -t ed25519 -f /root/.ssh/id_ed25519 -N ""'
@@ -408,6 +421,19 @@ in

        server_sftp.wait_for_file("/srv/sftp/uploads/test-file")

    with subtest("keygen without sshd"):
        client.fail(
            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil root@server-no-sshd-with-key true",
            timeout=30
        )
        server_no_sshd_with_key.succeed("test -e /etc/ssh/ssh_host_ed25519_key")
        server_no_sshd_with_key.succeed("test -e /etc/ssh/ssh_host_ed25519_key.pub")
        server_no_sshd_with_key.fail("pgrep sshd")

        # Validate the above check for sshd using pgrep does pass on a server
        # that should have sshd running, just to prove it's a useful test.
        server.succeed("pgrep sshd")

    # None of the per-connection units should have failed.
    server_lazy.fail("systemctl is-failed 'sshd@*.service'")
  '';