summary refs log tree commit diff
path: root/nixos/modules/services/networking/ssh/sshd.nix
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules/services/networking/ssh/sshd.nix')
-rw-r--r--nixos/modules/services/networking/ssh/sshd.nix338
1 files changed, 338 insertions, 0 deletions
diff --git a/nixos/modules/services/networking/ssh/sshd.nix b/nixos/modules/services/networking/ssh/sshd.nix
new file mode 100644
index 00000000000..d57eef860d2
--- /dev/null
+++ b/nixos/modules/services/networking/ssh/sshd.nix
@@ -0,0 +1,338 @@
+{ config, pkgs, ... }:
+
+with pkgs.lib;
+
+let
+
+  cfg  = config.services.openssh;
+  cfgc = config.programs.ssh;
+
+  nssModulesPath = config.system.nssModules.path;
+
+  permitRootLoginCheck = v:
+    v == "yes" ||
+    v == "without-password" ||
+    v == "forced-commands-only" ||
+    v == "no";
+
+  knownHosts = map (h: getAttr h cfg.knownHosts) (attrNames cfg.knownHosts);
+
+  knownHostsFile = pkgs.writeText "ssh_known_hosts" (
+    flip concatMapStrings knownHosts (h:
+      "${concatStringsSep "," h.hostNames} ${builtins.readFile h.publicKeyFile}"
+    )
+  );
+
+  userOptions = {
+
+    openssh.authorizedKeys = {
+      keys = mkOption {
+        type = types.listOf types.string;
+        default = [];
+        description = ''
+          A list of verbatim OpenSSH public keys that should be added to the
+          user's authorized keys. The keys are added to a file that the SSH
+          daemon reads in addition to the the user's authorized_keys file.
+          You can combine the <literal>keys</literal> and
+          <literal>keyFiles</literal> options.
+        '';
+      };
+
+      keyFiles = mkOption {
+        default = [];
+        description = ''
+          A list of files each containing one OpenSSH public key that should be
+          added to the user's authorized keys. The contents of the files are
+          read at build time and added to a file that the SSH daemon reads in
+          addition to the the user's authorized_keys file. You can combine the
+          <literal>keyFiles</literal> and <literal>keys</literal> options.
+        '';
+      };
+    };
+
+  };
+
+  authKeysFiles = let
+    mkAuthKeyFile = u: {
+      target = "ssh/authorized_keys.d/${u.name}";
+      mode = "0444";
+      source = pkgs.writeText "${u.name}-authorized_keys" ''
+        ${concatStringsSep "\n" u.openssh.authorizedKeys.keys}
+        ${concatMapStrings (f: builtins.readFile f + "\n") u.openssh.authorizedKeys.keyFiles}
+      '';
+    };
+    usersWithKeys = attrValues (flip filterAttrs config.users.extraUsers (n: u:
+      length u.openssh.authorizedKeys.keys != 0 || length u.openssh.authorizedKeys.keyFiles != 0
+    ));
+  in map mkAuthKeyFile usersWithKeys;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.openssh = {
+
+      enable = mkOption {
+        default = false;
+        description = ''
+          Whether to enable the OpenSSH secure shell daemon, which
+          allows secure remote logins.
+        '';
+      };
+
+      forwardX11 = mkOption {
+        default = cfgc.setXAuthLocation;
+        description = ''
+          Whether to allow X11 connections to be forwarded.
+        '';
+      };
+
+      allowSFTP = mkOption {
+        default = true;
+        description = ''
+          Whether to enable the SFTP subsystem in the SSH daemon.  This
+          enables the use of commands such as <command>sftp</command> and
+          <command>sshfs</command>.
+        '';
+      };
+
+      permitRootLogin = mkOption {
+        default = "without-password";
+        check = permitRootLoginCheck;
+        description = ''
+          Whether the root user can login using ssh. Valid values are
+          <literal>yes</literal>, <literal>without-password</literal>,
+          <literal>forced-commands-only</literal> or
+          <literal>no</literal>.
+        '';
+      };
+
+      gatewayPorts = mkOption {
+        default = "no";
+        description = ''
+          Specifies whether remote hosts are allowed to connect to
+          ports forwarded for the client.  See
+          <citerefentry><refentrytitle>sshd_config</refentrytitle>
+          <manvolnum>5</manvolnum></citerefentry>.
+        '';
+      };
+
+      ports = mkOption {
+        default = [22];
+        description = ''
+          Specifies on which ports the SSH daemon listens.
+        '';
+      };
+
+      usePAM = mkOption {
+        default = true;
+        description = ''
+          Specifies whether the OpenSSH daemon uses PAM to authenticate
+          login attempts.
+        '';
+      };
+
+      passwordAuthentication = mkOption {
+        default = true;
+        description = ''
+          Specifies whether password authentication is allowed. Note
+          that setting this value to <literal>false</literal> is most
+          probably not going to have the desired effect unless
+          <literal>usePAM</literal> is disabled as well.
+        '';
+      };
+
+      challengeResponseAuthentication = mkOption {
+        default = true;
+        description = ''
+          Specifies whether challenge/response authentication is allowed.
+        '';
+      };
+
+      hostKeys = mkOption {
+        default =
+          [ { path = "/etc/ssh/ssh_host_dsa_key";
+              type = "dsa";
+              bits = 1024;
+            }
+            { path = "/etc/ssh/ssh_host_ecdsa_key";
+              type = "ecdsa";
+              bits = 521;
+            }
+          ];
+        description = ''
+          NixOS can automatically generate SSH host keys.  This option
+          specifies the path, type and size of each key.  See
+          <citerefentry><refentrytitle>ssh-keygen</refentrytitle>
+          <manvolnum>1</manvolnum></citerefentry> for supported types
+          and sizes.
+        '';
+      };
+
+      authorizedKeysFiles = mkOption {
+        default = [];
+        description = "Files from with authorized keys are read.";
+      };
+
+      extraConfig = mkOption {
+        default = "";
+        description = "Verbatim contents of <filename>sshd_config</filename>.";
+      };
+
+      knownHosts = mkOption {
+        default = {};
+        type = types.loaOf types.optionSet;
+        description = ''
+          The set of system-wide known SSH hosts.
+        '';
+        example = [
+          {
+            hostNames = [ "myhost" "myhost.mydomain.com" "10.10.1.4" ];
+            publicKeyFile = ./pubkeys/myhost_ssh_host_dsa_key.pub;
+          }
+          {
+            hostNames = [ "myhost2" ];
+            publicKeyFile = ./pubkeys/myhost2_ssh_host_dsa_key.pub;
+          }
+        ];
+        options = {
+          hostNames = mkOption {
+            type = types.listOf types.string;
+            default = [];
+            description = ''
+              A list of host names and/or IP numbers used for accessing
+              the host's ssh service.
+            '';
+          };
+          publicKeyFile = mkOption {
+            description = ''
+              The path to the public key file for the host. The public
+              key file is read at build time and saved in the Nix store.
+              You can fetch a public key file from a running SSH server
+              with the <literal>ssh-keyscan</literal> command.
+            '';
+          };
+        };
+      };
+
+    };
+
+    users.extraUsers = mkOption {
+      options = [ userOptions ];
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    users.extraUsers = singleton
+      { name = "sshd";
+        uid = config.ids.uids.sshd;
+        description = "SSH privilege separation user";
+        home = "/var/empty";
+      };
+
+    environment.etc = authKeysFiles ++ [
+      { source = "${pkgs.openssh}/etc/ssh/moduli";
+        target = "ssh/moduli";
+      }
+      { source = knownHostsFile;
+        target = "ssh/ssh_known_hosts";
+      }
+    ];
+
+    systemd.services.sshd =
+      { description = "SSH Daemon";
+
+        wantedBy = [ "multi-user.target" ];
+
+        stopIfChanged = false;
+
+        path = [ pkgs.openssh pkgs.gawk ];
+
+        environment.LD_LIBRARY_PATH = nssModulesPath;
+        environment.LOCALE_ARCHIVE = "/run/current-system/sw/lib/locale/locale-archive";
+
+        preStart =
+          ''
+            mkdir -m 0755 -p /etc/ssh
+
+            ${flip concatMapStrings cfg.hostKeys (k: ''
+              if ! [ -f "${k.path}" ]; then
+                  ssh-keygen -t "${k.type}" -b "${toString k.bits}" -f "${k.path}" -N ""
+              fi
+            '')}
+          '';
+
+        serviceConfig =
+          { ExecStart =
+              "${pkgs.openssh}/sbin/sshd " +
+              "-f ${pkgs.writeText "sshd_config" cfg.extraConfig}";
+            Restart = "always";
+            Type = "forking";
+            KillMode = "process";
+            PIDFile = "/run/sshd.pid";
+          };
+      };
+
+    networking.firewall.allowedTCPPorts = cfg.ports;
+
+    security.pam.services = optional cfg.usePAM { name = "sshd"; startSession = true; showMotd = true; };
+
+    services.openssh.authorizedKeysFiles =
+      [ ".ssh/authorized_keys" ".ssh/authorized_keys2" "/etc/ssh/authorized_keys.d/%u" ];
+
+    services.openssh.extraConfig =
+      ''
+        PidFile /run/sshd.pid
+
+        Protocol 2
+
+        UsePAM ${if cfg.usePAM then "yes" else "no"}
+
+        AddressFamily ${if config.networking.enableIPv6 then "any" else "inet"}
+        ${concatMapStrings (port: ''
+          Port ${toString port}
+        '') cfg.ports}
+
+        ${optionalString cfgc.setXAuthLocation ''
+            XAuthLocation ${pkgs.xorg.xauth}/bin/xauth
+        ''}
+
+        ${if cfg.forwardX11 then ''
+          X11Forwarding yes
+        '' else ''
+          X11Forwarding no
+        ''}
+
+        ${optionalString cfg.allowSFTP ''
+          Subsystem sftp ${pkgs.openssh}/libexec/sftp-server
+        ''}
+
+        PermitRootLogin ${cfg.permitRootLogin}
+        GatewayPorts ${cfg.gatewayPorts}
+        PasswordAuthentication ${if cfg.passwordAuthentication then "yes" else "no"}
+        ChallengeResponseAuthentication ${if cfg.challengeResponseAuthentication then "yes" else "no"}
+
+        PrintMotd no # handled by pam_motd
+
+        AuthorizedKeysFile ${toString cfg.authorizedKeysFiles}
+
+        ${flip concatMapStrings cfg.hostKeys (k: ''
+          HostKey ${k.path}
+        '')}
+      '';
+
+    assertions = [{ assertion = if cfg.forwardX11 then cfgc.setXAuthLocation else true;
+                    message = "cannot enable X11 forwarding without setting xauth location";}];
+
+  };
+
+}