python3Packages.orjson: Disable failing tests on 32 bit
[NixPkgs.git] / nixos / modules / services / networking / ssh / sshd.nix
blobaf8200c7e2951f3f885d27a95a86a5e6006afcef
1 { config, lib, pkgs, ... }:
3 with lib;
5 let
7   # The splicing information needed for nativeBuildInputs isn't available
8   # on the derivations likely to be used as `cfgc.package`.
9   # This middle-ground solution ensures *an* sshd can do their basic validation
10   # on the configuration.
11   validationPackage = if pkgs.stdenv.buildPlatform == pkgs.stdenv.hostPlatform
12     then cfgc.package
13     else pkgs.buildPackages.openssh;
15   sshconf = pkgs.runCommand "sshd.conf-validated" { nativeBuildInputs = [ validationPackage ]; } ''
16     cat >$out <<EOL
17     ${cfg.extraConfig}
18     EOL
20     ssh-keygen -q -f mock-hostkey -N ""
21     sshd -t -f $out -h mock-hostkey
22   '';
24   cfg  = config.services.openssh;
25   cfgc = config.programs.ssh;
27   nssModulesPath = config.system.nssModules.path;
29   userOptions = {
31     options.openssh.authorizedKeys = {
32       keys = mkOption {
33         type = types.listOf types.singleLineStr;
34         default = [];
35         description = lib.mdDoc ''
36           A list of verbatim OpenSSH public keys that should be added to the
37           user's authorized keys. The keys are added to a file that the SSH
38           daemon reads in addition to the the user's authorized_keys file.
39           You can combine the `keys` and
40           `keyFiles` options.
41           Warning: If you are using `NixOps` then don't use this
42           option since it will replace the key required for deployment via ssh.
43         '';
44         example = [
45           "ssh-rsa AAAAB3NzaC1yc2etc/etc/etcjwrsh8e596z6J0l7 example@host"
46           "ssh-ed25519 AAAAC3NzaCetcetera/etceteraJZMfk3QPfQ foo@bar"
47         ];
48       };
50       keyFiles = mkOption {
51         type = types.listOf types.path;
52         default = [];
53         description = lib.mdDoc ''
54           A list of files each containing one OpenSSH public key that should be
55           added to the user's authorized keys. The contents of the files are
56           read at build time and added to a file that the SSH daemon reads in
57           addition to the the user's authorized_keys file. You can combine the
58           `keyFiles` and `keys` options.
59         '';
60       };
61     };
63   };
65   authKeysFiles = let
66     mkAuthKeyFile = u: nameValuePair "ssh/authorized_keys.d/${u.name}" {
67       mode = "0444";
68       source = pkgs.writeText "${u.name}-authorized_keys" ''
69         ${concatStringsSep "\n" u.openssh.authorizedKeys.keys}
70         ${concatMapStrings (f: readFile f + "\n") u.openssh.authorizedKeys.keyFiles}
71       '';
72     };
73     usersWithKeys = attrValues (flip filterAttrs config.users.users (n: u:
74       length u.openssh.authorizedKeys.keys != 0 || length u.openssh.authorizedKeys.keyFiles != 0
75     ));
76   in listToAttrs (map mkAuthKeyFile usersWithKeys);
81   imports = [
82     (mkAliasOptionModule [ "services" "sshd" "enable" ] [ "services" "openssh" "enable" ])
83     (mkAliasOptionModule [ "services" "openssh" "knownHosts" ] [ "programs" "ssh" "knownHosts" ])
84     (mkRenamedOptionModule [ "services" "openssh" "challengeResponseAuthentication" ] [ "services" "openssh" "kbdInteractiveAuthentication" ])
85   ];
87   ###### interface
89   options = {
91     services.openssh = {
93       enable = mkOption {
94         type = types.bool;
95         default = false;
96         description = lib.mdDoc ''
97           Whether to enable the OpenSSH secure shell daemon, which
98           allows secure remote logins.
99         '';
100       };
102       startWhenNeeded = mkOption {
103         type = types.bool;
104         default = false;
105         description = lib.mdDoc ''
106           If set, {command}`sshd` is socket-activated; that
107           is, instead of having it permanently running as a daemon,
108           systemd will start an instance for each incoming connection.
109         '';
110       };
112       forwardX11 = mkOption {
113         type = types.bool;
114         default = false;
115         description = lib.mdDoc ''
116           Whether to allow X11 connections to be forwarded.
117         '';
118       };
120       allowSFTP = mkOption {
121         type = types.bool;
122         default = true;
123         description = lib.mdDoc ''
124           Whether to enable the SFTP subsystem in the SSH daemon.  This
125           enables the use of commands such as {command}`sftp` and
126           {command}`sshfs`.
127         '';
128       };
130       sftpServerExecutable = mkOption {
131         type = types.str;
132         example = "internal-sftp";
133         description = lib.mdDoc ''
134           The sftp server executable.  Can be a path or "internal-sftp" to use
135           the sftp server built into the sshd binary.
136         '';
137       };
139       sftpFlags = mkOption {
140         type = with types; listOf str;
141         default = [];
142         example = [ "-f AUTHPRIV" "-l INFO" ];
143         description = lib.mdDoc ''
144           Commandline flags to add to sftp-server.
145         '';
146       };
148       permitRootLogin = mkOption {
149         default = "prohibit-password";
150         type = types.enum ["yes" "without-password" "prohibit-password" "forced-commands-only" "no"];
151         description = lib.mdDoc ''
152           Whether the root user can login using ssh.
153         '';
154       };
156       gatewayPorts = mkOption {
157         type = types.str;
158         default = "no";
159         description = lib.mdDoc ''
160           Specifies whether remote hosts are allowed to connect to
161           ports forwarded for the client.  See
162           {manpage}`sshd_config(5)`.
163         '';
164       };
166       ports = mkOption {
167         type = types.listOf types.port;
168         default = [22];
169         description = lib.mdDoc ''
170           Specifies on which ports the SSH daemon listens.
171         '';
172       };
174       openFirewall = mkOption {
175         type = types.bool;
176         default = true;
177         description = lib.mdDoc ''
178           Whether to automatically open the specified ports in the firewall.
179         '';
180       };
182       listenAddresses = mkOption {
183         type = with types; listOf (submodule {
184           options = {
185             addr = mkOption {
186               type = types.nullOr types.str;
187               default = null;
188               description = lib.mdDoc ''
189                 Host, IPv4 or IPv6 address to listen to.
190               '';
191             };
192             port = mkOption {
193               type = types.nullOr types.int;
194               default = null;
195               description = lib.mdDoc ''
196                 Port to listen to.
197               '';
198             };
199           };
200         });
201         default = [];
202         example = [ { addr = "192.168.3.1"; port = 22; } { addr = "0.0.0.0"; port = 64022; } ];
203         description = lib.mdDoc ''
204           List of addresses and ports to listen on (ListenAddress directive
205           in config). If port is not specified for address sshd will listen
206           on all ports specified by `ports` option.
207           NOTE: this will override default listening on all local addresses and port 22.
208           NOTE: setting this option won't automatically enable given ports
209           in firewall configuration.
210         '';
211       };
213       passwordAuthentication = mkOption {
214         type = types.bool;
215         default = true;
216         description = lib.mdDoc ''
217           Specifies whether password authentication is allowed.
218         '';
219       };
221       kbdInteractiveAuthentication = mkOption {
222         type = types.bool;
223         default = true;
224         description = lib.mdDoc ''
225           Specifies whether keyboard-interactive authentication is allowed.
226         '';
227       };
229       hostKeys = mkOption {
230         type = types.listOf types.attrs;
231         default =
232           [ { type = "rsa"; bits = 4096; path = "/etc/ssh/ssh_host_rsa_key"; }
233             { type = "ed25519"; path = "/etc/ssh/ssh_host_ed25519_key"; }
234           ];
235         example =
236           [ { type = "rsa"; bits = 4096; path = "/etc/ssh/ssh_host_rsa_key"; rounds = 100; openSSHFormat = true; }
237             { type = "ed25519"; path = "/etc/ssh/ssh_host_ed25519_key"; rounds = 100; comment = "key comment"; }
238           ];
239         description = lib.mdDoc ''
240           NixOS can automatically generate SSH host keys.  This option
241           specifies the path, type and size of each key.  See
242           {manpage}`ssh-keygen(1)` for supported types
243           and sizes.
244         '';
245       };
247       banner = mkOption {
248         type = types.nullOr types.lines;
249         default = null;
250         description = lib.mdDoc ''
251           Message to display to the remote user before authentication is allowed.
252         '';
253       };
255       authorizedKeysFiles = mkOption {
256         type = types.listOf types.str;
257         default = [];
258         description = lib.mdDoc ''
259           Specify the rules for which files to read on the host.
261           This is an advanced option. If you're looking to configure user
262           keys, you can generally use [](#opt-users.users._name_.openssh.authorizedKeys.keys)
263           or [](#opt-users.users._name_.openssh.authorizedKeys.keyFiles).
265           These are paths relative to the host root file system or home
266           directories and they are subject to certain token expansion rules.
267           See AuthorizedKeysFile in man sshd_config for details.
268         '';
269       };
271       authorizedKeysCommand = mkOption {
272         type = types.str;
273         default = "none";
274         description = lib.mdDoc ''
275           Specifies a program to be used to look up the user's public
276           keys. The program must be owned by root, not writable by group
277           or others and specified by an absolute path.
278         '';
279       };
281       authorizedKeysCommandUser = mkOption {
282         type = types.str;
283         default = "nobody";
284         description = lib.mdDoc ''
285           Specifies the user under whose account the AuthorizedKeysCommand
286           is run. It is recommended to use a dedicated user that has no
287           other role on the host than running authorized keys commands.
288         '';
289       };
291       kexAlgorithms = mkOption {
292         type = types.listOf types.str;
293         default = [
294           "sntrup761x25519-sha512@openssh.com"
295           "curve25519-sha256"
296           "curve25519-sha256@libssh.org"
297           "diffie-hellman-group-exchange-sha256"
298         ];
299         description = lib.mdDoc ''
300           Allowed key exchange algorithms
302           Uses the lower bound recommended in both
303           <https://stribika.github.io/2015/01/04/secure-secure-shell.html>
304           and
305           <https://infosec.mozilla.org/guidelines/openssh#modern-openssh-67>
306         '';
307       };
309       ciphers = mkOption {
310         type = types.listOf types.str;
311         default = [
312           "chacha20-poly1305@openssh.com"
313           "aes256-gcm@openssh.com"
314           "aes128-gcm@openssh.com"
315           "aes256-ctr"
316           "aes192-ctr"
317           "aes128-ctr"
318         ];
319         description = lib.mdDoc ''
320           Allowed ciphers
322           Defaults to recommended settings from both
323           <https://stribika.github.io/2015/01/04/secure-secure-shell.html>
324           and
325           <https://infosec.mozilla.org/guidelines/openssh#modern-openssh-67>
326         '';
327       };
329       macs = mkOption {
330         type = types.listOf types.str;
331         default = [
332           "hmac-sha2-512-etm@openssh.com"
333           "hmac-sha2-256-etm@openssh.com"
334           "umac-128-etm@openssh.com"
335           "hmac-sha2-512"
336           "hmac-sha2-256"
337           "umac-128@openssh.com"
338         ];
339         description = lib.mdDoc ''
340           Allowed MACs
342           Defaults to recommended settings from both
343           <https://stribika.github.io/2015/01/04/secure-secure-shell.html>
344           and
345           <https://infosec.mozilla.org/guidelines/openssh#modern-openssh-67>
346         '';
347       };
349       logLevel = mkOption {
350         type = types.enum [ "QUIET" "FATAL" "ERROR" "INFO" "VERBOSE" "DEBUG" "DEBUG1" "DEBUG2" "DEBUG3" ];
351         default = "INFO"; # upstream default
352         description = lib.mdDoc ''
353           Gives the verbosity level that is used when logging messages from sshd(8). The possible values are:
354           QUIET, FATAL, ERROR, INFO, VERBOSE, DEBUG, DEBUG1, DEBUG2, and DEBUG3. The default is INFO. DEBUG and DEBUG1
355           are equivalent. DEBUG2 and DEBUG3 each specify higher levels of debugging output. Logging with a DEBUG level
356           violates the privacy of users and is not recommended.
357         '';
358       };
360       useDns = mkOption {
361         type = types.bool;
362         default = false;
363         description = lib.mdDoc ''
364           Specifies whether sshd(8) should look up the remote host name, and to check that the resolved host name for
365           the remote IP address maps back to the very same IP address.
366           If this option is set to no (the default) then only addresses and not host names may be used in
367           ~/.ssh/authorized_keys from and sshd_config Match Host directives.
368         '';
369       };
371       extraConfig = mkOption {
372         type = types.lines;
373         default = "";
374         description = lib.mdDoc "Verbatim contents of {file}`sshd_config`.";
375       };
377       moduliFile = mkOption {
378         example = "/etc/my-local-ssh-moduli;";
379         type = types.path;
380         description = lib.mdDoc ''
381           Path to `moduli` file to install in
382           `/etc/ssh/moduli`. If this option is unset, then
383           the `moduli` file shipped with OpenSSH will be used.
384         '';
385       };
387     };
389     users.users = mkOption {
390       type = with types; attrsOf (submodule userOptions);
391     };
393   };
396   ###### implementation
398   config = mkIf cfg.enable {
400     users.users.sshd =
401       {
402         isSystemUser = true;
403         group = "sshd";
404         description = "SSH privilege separation user";
405       };
406     users.groups.sshd = {};
408     services.openssh.moduliFile = mkDefault "${cfgc.package}/etc/ssh/moduli";
409     services.openssh.sftpServerExecutable = mkDefault "${cfgc.package}/libexec/sftp-server";
411     environment.etc = authKeysFiles //
412       { "ssh/moduli".source = cfg.moduliFile;
413         "ssh/sshd_config".source = sshconf;
414       };
416     systemd =
417       let
418         service =
419           { description = "SSH Daemon";
420             wantedBy = optional (!cfg.startWhenNeeded) "multi-user.target";
421             after = [ "network.target" ];
422             stopIfChanged = false;
423             path = [ cfgc.package pkgs.gawk ];
424             environment.LD_LIBRARY_PATH = nssModulesPath;
426             restartTriggers = optionals (!cfg.startWhenNeeded) [
427               config.environment.etc."ssh/sshd_config".source
428             ];
430             preStart =
431               ''
432                 # Make sure we don't write to stdout, since in case of
433                 # socket activation, it goes to the remote side (#19589).
434                 exec >&2
436                 ${flip concatMapStrings cfg.hostKeys (k: ''
437                   if ! [ -s "${k.path}" ]; then
438                       if ! [ -h "${k.path}" ]; then
439                           rm -f "${k.path}"
440                       fi
441                       mkdir -m 0755 -p "$(dirname '${k.path}')"
442                       ssh-keygen \
443                         -t "${k.type}" \
444                         ${if k ? bits then "-b ${toString k.bits}" else ""} \
445                         ${if k ? rounds then "-a ${toString k.rounds}" else ""} \
446                         ${if k ? comment then "-C '${k.comment}'" else ""} \
447                         ${if k ? openSSHFormat && k.openSSHFormat then "-o" else ""} \
448                         -f "${k.path}" \
449                         -N ""
450                   fi
451                 '')}
452               '';
454             serviceConfig =
455               { ExecStart =
456                   (optionalString cfg.startWhenNeeded "-") +
457                   "${cfgc.package}/bin/sshd " + (optionalString cfg.startWhenNeeded "-i ") +
458                   "-D " +  # don't detach into a daemon process
459                   "-f /etc/ssh/sshd_config";
460                 KillMode = "process";
461               } // (if cfg.startWhenNeeded then {
462                 StandardInput = "socket";
463                 StandardError = "journal";
464               } else {
465                 Restart = "always";
466                 Type = "simple";
467               });
469           };
470       in
472       if cfg.startWhenNeeded then {
474         sockets.sshd =
475           { description = "SSH Socket";
476             wantedBy = [ "sockets.target" ];
477             socketConfig.ListenStream = if cfg.listenAddresses != [] then
478               map (l: "${l.addr}:${toString (if l.port != null then l.port else 22)}") cfg.listenAddresses
479             else
480               cfg.ports;
481             socketConfig.Accept = true;
482             # Prevent brute-force attacks from shutting down socket
483             socketConfig.TriggerLimitIntervalSec = 0;
484           };
486         services."sshd@" = service;
488       } else {
490         services.sshd = service;
492       };
494     networking.firewall.allowedTCPPorts = if cfg.openFirewall then cfg.ports else [];
496     security.pam.services.sshd =
497       { startSession = true;
498         showMotd = true;
499         unixAuth = cfg.passwordAuthentication;
500       };
502     # These values are merged with the ones defined externally, see:
503     # https://github.com/NixOS/nixpkgs/pull/10155
504     # https://github.com/NixOS/nixpkgs/pull/41745
505     services.openssh.authorizedKeysFiles =
506       [ "%h/.ssh/authorized_keys" "%h/.ssh/authorized_keys2" "/etc/ssh/authorized_keys.d/%u" ];
508     services.openssh.extraConfig = mkOrder 0
509       ''
510         UsePAM yes
512         Banner ${if cfg.banner == null then "none" else pkgs.writeText "ssh_banner" cfg.banner}
514         AddressFamily ${if config.networking.enableIPv6 then "any" else "inet"}
515         ${concatMapStrings (port: ''
516           Port ${toString port}
517         '') cfg.ports}
519         ${concatMapStrings ({ port, addr, ... }: ''
520           ListenAddress ${addr}${if port != null then ":" + toString port else ""}
521         '') cfg.listenAddresses}
523         ${optionalString cfgc.setXAuthLocation ''
524             XAuthLocation ${pkgs.xorg.xauth}/bin/xauth
525         ''}
527         X11Forwarding ${if cfg.forwardX11 then "yes" else "no"}
529         ${optionalString cfg.allowSFTP ''
530           Subsystem sftp ${cfg.sftpServerExecutable} ${concatStringsSep " " cfg.sftpFlags}
531         ''}
533         PermitRootLogin ${cfg.permitRootLogin}
534         GatewayPorts ${cfg.gatewayPorts}
535         PasswordAuthentication ${if cfg.passwordAuthentication then "yes" else "no"}
536         KbdInteractiveAuthentication ${if cfg.kbdInteractiveAuthentication then "yes" else "no"}
538         PrintMotd no # handled by pam_motd
540         AuthorizedKeysFile ${toString cfg.authorizedKeysFiles}
541         ${optionalString (cfg.authorizedKeysCommand != "none") ''
542           AuthorizedKeysCommand ${cfg.authorizedKeysCommand}
543           AuthorizedKeysCommandUser ${cfg.authorizedKeysCommandUser}
544         ''}
546         ${flip concatMapStrings cfg.hostKeys (k: ''
547           HostKey ${k.path}
548         '')}
550         KexAlgorithms ${concatStringsSep "," cfg.kexAlgorithms}
551         Ciphers ${concatStringsSep "," cfg.ciphers}
552         MACs ${concatStringsSep "," cfg.macs}
554         LogLevel ${cfg.logLevel}
556         UseDNS ${if cfg.useDns then "yes" else "no"}
558       '';
560     assertions = [{ assertion = if cfg.forwardX11 then cfgc.setXAuthLocation else true;
561                     message = "cannot enable X11 forwarding without setting xauth location";}]
562       ++ forEach cfg.listenAddresses ({ addr, ... }: {
563         assertion = addr != null;
564         message = "addr must be specified in each listenAddresses entry";
565       });
567   };