diff options
Diffstat (limited to 'nixpkgs/nixos/modules/services/networking/ssh/sshd.nix')
-rw-r--r-- | nixpkgs/nixos/modules/services/networking/ssh/sshd.nix | 341 |
1 files changed, 180 insertions, 161 deletions
diff --git a/nixpkgs/nixos/modules/services/networking/ssh/sshd.nix b/nixpkgs/nixos/modules/services/networking/ssh/sshd.nix index 27e037d66a0f..1367381b7a96 100644 --- a/nixpkgs/nixos/modules/services/networking/ssh/sshd.nix +++ b/nixpkgs/nixos/modules/services/networking/ssh/sshd.nix @@ -12,8 +12,24 @@ let then cfgc.package else pkgs.buildPackages.openssh; + # reports boolean as yes / no + mkValueStringSshd = with lib; v: + if isInt v then toString v + else if isString v then v + else if true == v then "yes" + else if false == v then "no" + else if isList v then concatStringsSep "," v + else throw "unsupported type ${builtins.typeOf v}: ${(lib.generators.toPretty {}) v}"; + + # dont use the "=" operator + settingsFormat = (pkgs.formats.keyValue { + mkKeyValue = lib.generators.mkKeyValueDefault { + mkValueString = mkValueStringSshd; + } " ";}); + + configFile = settingsFormat.generate "config" cfg.settings; sshconf = pkgs.runCommand "sshd.conf-validated" { nativeBuildInputs = [ validationPackage ]; } '' - cat >$out <<EOL + cat ${configFile} - >$out <<EOL ${cfg.extraConfig} EOL @@ -24,6 +40,7 @@ let cfg = config.services.openssh; cfgc = config.programs.ssh; + nssModulesPath = config.system.nssModules.path; userOptions = { @@ -79,9 +96,20 @@ in { imports = [ - (mkAliasOptionModule [ "services" "sshd" "enable" ] [ "services" "openssh" "enable" ]) - (mkAliasOptionModule [ "services" "openssh" "knownHosts" ] [ "programs" "ssh" "knownHosts" ]) + (mkAliasOptionModuleMD [ "services" "sshd" "enable" ] [ "services" "openssh" "enable" ]) + (mkAliasOptionModuleMD [ "services" "openssh" "knownHosts" ] [ "programs" "ssh" "knownHosts" ]) (mkRenamedOptionModule [ "services" "openssh" "challengeResponseAuthentication" ] [ "services" "openssh" "kbdInteractiveAuthentication" ]) + + (mkRenamedOptionModule [ "services" "openssh" "kbdInteractiveAuthentication" ] [ "services" "openssh" "settings" "KbdInteractiveAuthentication" ]) + (mkRenamedOptionModule [ "services" "openssh" "passwordAuthentication" ] [ "services" "openssh" "settings" "PasswordAuthentication" ]) + (mkRenamedOptionModule [ "services" "openssh" "useDns" ] [ "services" "openssh" "settings" "UseDns" ]) + (mkRenamedOptionModule [ "services" "openssh" "permitRootLogin" ] [ "services" "openssh" "settings" "PermitRootLogin" ]) + (mkRenamedOptionModule [ "services" "openssh" "logLevel" ] [ "services" "openssh" "settings" "LogLevel" ]) + (mkRenamedOptionModule [ "services" "openssh" "macs" ] [ "services" "openssh" "settings" "Macs" ]) + (mkRenamedOptionModule [ "services" "openssh" "ciphers" ] [ "services" "openssh" "settings" "Ciphers" ]) + (mkRenamedOptionModule [ "services" "openssh" "kexAlgorithms" ] [ "services" "openssh" "settings" "KexAlgorithms" ]) + (mkRenamedOptionModule [ "services" "openssh" "gatewayPorts" ] [ "services" "openssh" "settings" "GatewayPorts" ]) + (mkRenamedOptionModule [ "services" "openssh" "forwardX11" ] [ "services" "openssh" "settings" "X11Forwarding" ]) ]; ###### interface @@ -109,14 +137,6 @@ in ''; }; - forwardX11 = mkOption { - type = types.bool; - default = false; - description = lib.mdDoc '' - Whether to allow X11 connections to be forwarded. - ''; - }; - allowSFTP = mkOption { type = types.bool; default = true; @@ -145,24 +165,6 @@ in ''; }; - permitRootLogin = mkOption { - default = "prohibit-password"; - type = types.enum ["yes" "without-password" "prohibit-password" "forced-commands-only" "no"]; - description = lib.mdDoc '' - Whether the root user can login using ssh. - ''; - }; - - gatewayPorts = mkOption { - type = types.str; - default = "no"; - description = lib.mdDoc '' - Specifies whether remote hosts are allowed to connect to - ports forwarded for the client. See - {manpage}`sshd_config(5)`. - ''; - }; - ports = mkOption { type = types.listOf types.port; default = [22]; @@ -210,22 +212,6 @@ in ''; }; - passwordAuthentication = mkOption { - type = types.bool; - default = true; - description = lib.mdDoc '' - Specifies whether password authentication is allowed. - ''; - }; - - kbdInteractiveAuthentication = mkOption { - type = types.bool; - default = true; - description = lib.mdDoc '' - Specifies whether keyboard-interactive authentication is allowed. - ''; - }; - hostKeys = mkOption { type = types.listOf types.attrs; default = @@ -288,84 +274,135 @@ in ''; }; - kexAlgorithms = mkOption { - type = types.listOf types.str; - default = [ - "sntrup761x25519-sha512@openssh.com" - "curve25519-sha256" - "curve25519-sha256@libssh.org" - "diffie-hellman-group-exchange-sha256" - ]; - description = lib.mdDoc '' - Allowed key exchange algorithms - - Uses the lower bound recommended in both - <https://stribika.github.io/2015/01/04/secure-secure-shell.html> - and - <https://infosec.mozilla.org/guidelines/openssh#modern-openssh-67> - ''; - }; - - ciphers = mkOption { - type = types.listOf types.str; - default = [ - "chacha20-poly1305@openssh.com" - "aes256-gcm@openssh.com" - "aes128-gcm@openssh.com" - "aes256-ctr" - "aes192-ctr" - "aes128-ctr" - ]; - description = lib.mdDoc '' - Allowed ciphers - Defaults to recommended settings from both - <https://stribika.github.io/2015/01/04/secure-secure-shell.html> - and - <https://infosec.mozilla.org/guidelines/openssh#modern-openssh-67> - ''; - }; - macs = mkOption { - type = types.listOf types.str; - default = [ - "hmac-sha2-512-etm@openssh.com" - "hmac-sha2-256-etm@openssh.com" - "umac-128-etm@openssh.com" - "hmac-sha2-512" - "hmac-sha2-256" - "umac-128@openssh.com" - ]; - description = lib.mdDoc '' - Allowed MACs + settings = mkOption { + description = lib.mdDoc "Configuration for `sshd_config(5)`."; + default = { }; + example = literalExpression ''{ + UseDns = true; + PasswordAuthentication = false; + }''; + type = types.submodule ({name, ...}: { + freeformType = settingsFormat.type; + options = { + LogLevel = mkOption { + type = types.enum [ "QUIET" "FATAL" "ERROR" "INFO" "VERBOSE" "DEBUG" "DEBUG1" "DEBUG2" "DEBUG3" ]; + default = "INFO"; # upstream default + description = lib.mdDoc '' + Gives the verbosity level that is used when logging messages from sshd(8). Logging with a DEBUG level + violates the privacy of users and is not recommended. + ''; + }; + UseDns = mkOption { + type = types.bool; + # apply if cfg.useDns then "yes" else "no" + default = false; + description = lib.mdDoc '' + Specifies whether sshd(8) should look up the remote host name, and to check that the resolved host name for + the remote IP address maps back to the very same IP address. + If this option is set to no (the default) then only addresses and not host names may be used in + ~/.ssh/authorized_keys from and sshd_config Match Host directives. + ''; + }; + X11Forwarding = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc '' + Whether to allow X11 connections to be forwarded. + ''; + }; + PasswordAuthentication = mkOption { + type = types.bool; + default = true; + description = lib.mdDoc '' + Specifies whether password authentication is allowed. + ''; + }; + PermitRootLogin = mkOption { + default = "prohibit-password"; + type = types.enum ["yes" "without-password" "prohibit-password" "forced-commands-only" "no"]; + description = lib.mdDoc '' + Whether the root user can login using ssh. + ''; + }; + KbdInteractiveAuthentication = mkOption { + type = types.bool; + default = true; + description = lib.mdDoc '' + Specifies whether keyboard-interactive authentication is allowed. + ''; + }; + GatewayPorts = mkOption { + type = types.str; + default = "no"; + description = lib.mdDoc '' + Specifies whether remote hosts are allowed to connect to + ports forwarded for the client. See + {manpage}`sshd_config(5)`. + ''; + }; + KexAlgorithms = mkOption { + type = types.listOf types.str; + default = [ + "sntrup761x25519-sha512@openssh.com" + "curve25519-sha256" + "curve25519-sha256@libssh.org" + "diffie-hellman-group-exchange-sha256" + ]; + description = lib.mdDoc '' + Allowed key exchange algorithms - Defaults to recommended settings from both - <https://stribika.github.io/2015/01/04/secure-secure-shell.html> - and - <https://infosec.mozilla.org/guidelines/openssh#modern-openssh-67> - ''; - }; + Uses the lower bound recommended in both + <https://stribika.github.io/2015/01/04/secure-secure-shell.html> + and + <https://infosec.mozilla.org/guidelines/openssh#modern-openssh-67> + ''; + }; + Macs = mkOption { + type = types.listOf types.str; + default = [ + "hmac-sha2-512-etm@openssh.com" + "hmac-sha2-256-etm@openssh.com" + "umac-128-etm@openssh.com" + ]; + description = lib.mdDoc '' + Allowed MACs - logLevel = mkOption { - type = types.enum [ "QUIET" "FATAL" "ERROR" "INFO" "VERBOSE" "DEBUG" "DEBUG1" "DEBUG2" "DEBUG3" ]; - default = "INFO"; # upstream default - description = lib.mdDoc '' - Gives the verbosity level that is used when logging messages from sshd(8). The possible values are: - QUIET, FATAL, ERROR, INFO, VERBOSE, DEBUG, DEBUG1, DEBUG2, and DEBUG3. The default is INFO. DEBUG and DEBUG1 - are equivalent. DEBUG2 and DEBUG3 each specify higher levels of debugging output. Logging with a DEBUG level - violates the privacy of users and is not recommended. - ''; - }; + Defaults to recommended settings from both + <https://stribika.github.io/2015/01/04/secure-secure-shell.html> + and + <https://infosec.mozilla.org/guidelines/openssh#modern-openssh-67> + ''; + }; + StrictModes = mkOption { + type = types.bool; + default = true; + description = lib.mdDoc '' + Whether sshd should check file modes and ownership of directories + ''; + }; + Ciphers = mkOption { + type = types.listOf types.str; + default = [ + "chacha20-poly1305@openssh.com" + "aes256-gcm@openssh.com" + "aes128-gcm@openssh.com" + "aes256-ctr" + "aes192-ctr" + "aes128-ctr" + ]; + description = lib.mdDoc '' + Allowed ciphers - useDns = mkOption { - type = types.bool; - default = false; - description = lib.mdDoc '' - Specifies whether sshd(8) should look up the remote host name, and to check that the resolved host name for - the remote IP address maps back to the very same IP address. - If this option is set to no (the default) then only addresses and not host names may be used in - ~/.ssh/authorized_keys from and sshd_config Match Host directives. - ''; + Defaults to recommended settings from both + <https://stribika.github.io/2015/01/04/secure-secure-shell.html> + and + <https://infosec.mozilla.org/guidelines/openssh#modern-openssh-67> + ''; + }; + }; + }); }; extraConfig = mkOption { @@ -383,14 +420,6 @@ in the `moduli` file shipped with OpenSSH will be used. ''; }; - - strictModes = mkOption { - type = types.bool; - default = true; - description = '' - Whether sshd should check file modes and ownership of directories - ''; - }; }; users.users = mkOption { @@ -451,10 +480,10 @@ in mkdir -m 0755 -p "$(dirname '${k.path}')" ssh-keygen \ -t "${k.type}" \ - ${if k ? bits then "-b ${toString k.bits}" else ""} \ - ${if k ? rounds then "-a ${toString k.rounds}" else ""} \ - ${if k ? comment then "-C '${k.comment}'" else ""} \ - ${if k ? openSSHFormat && k.openSSHFormat then "-o" else ""} \ + ${optionalString (k ? bits) "-b ${toString k.bits}"} \ + ${optionalString (k ? rounds) "-a ${toString k.rounds}"} \ + ${optionalString (k ? comment) "-C '${k.comment}'"} \ + ${optionalString (k ? openSSHFormat && k.openSSHFormat) "-o"} \ -f "${k.path}" \ -N "" fi @@ -506,14 +535,14 @@ in security.pam.services.sshd = { startSession = true; showMotd = true; - unixAuth = cfg.passwordAuthentication; + unixAuth = cfg.settings.PasswordAuthentication; }; # These values are merged with the ones defined externally, see: # https://github.com/NixOS/nixpkgs/pull/10155 # https://github.com/NixOS/nixpkgs/pull/41745 services.openssh.authorizedKeysFiles = - [ "%h/.ssh/authorized_keys" "%h/.ssh/authorized_keys2" "/etc/ssh/authorized_keys.d/%u" ]; + [ "%h/.ssh/authorized_keys" "/etc/ssh/authorized_keys.d/%u" ]; services.openssh.extraConfig = mkOrder 0 '' @@ -527,30 +556,16 @@ in '') cfg.ports} ${concatMapStrings ({ port, addr, ... }: '' - ListenAddress ${addr}${if port != null then ":" + toString port else ""} + ListenAddress ${addr}${optionalString (port != null) (":" + toString port)} '') cfg.listenAddresses} ${optionalString cfgc.setXAuthLocation '' XAuthLocation ${pkgs.xorg.xauth}/bin/xauth ''} - - X11Forwarding ${if cfg.forwardX11 then "yes" else "no"} - ${optionalString cfg.allowSFTP '' Subsystem sftp ${cfg.sftpServerExecutable} ${concatStringsSep " " cfg.sftpFlags} ''} - - PermitRootLogin ${cfg.permitRootLogin} - GatewayPorts ${cfg.gatewayPorts} - PasswordAuthentication ${if cfg.passwordAuthentication then "yes" else "no"} - KbdInteractiveAuthentication ${if cfg.kbdInteractiveAuthentication then "yes" else "no"} - PrintMotd no # handled by pam_motd - - ${optionalString (cfg.authorizedKeysCommand != null) '' - AuthorizedKeysCommand ${cfg.authorizedKeysCommand} - AuthorizedKeysCommandUser ${cfg.authorizedKeysCommandUser} - ''} AuthorizedKeysFile ${toString cfg.authorizedKeysFiles} ${optionalString (cfg.authorizedKeysCommand != "none") '' AuthorizedKeysCommand ${cfg.authorizedKeysCommand} @@ -560,26 +575,30 @@ in ${flip concatMapStrings cfg.hostKeys (k: '' HostKey ${k.path} '')} - - KexAlgorithms ${concatStringsSep "," cfg.kexAlgorithms} - Ciphers ${concatStringsSep "," cfg.ciphers} - MACs ${concatStringsSep "," cfg.macs} - - LogLevel ${cfg.logLevel} - - UseDNS ${if cfg.useDns then "yes" else "no"} - - StrictModes ${if cfg.strictModes then "yes" else "no"} - ''; - assertions = [{ assertion = if cfg.forwardX11 then cfgc.setXAuthLocation else true; - message = "cannot enable X11 forwarding without setting xauth location";}] + assertions = [{ assertion = if cfg.settings.X11Forwarding then cfgc.setXAuthLocation else true; + message = "cannot enable X11 forwarding without setting xauth location";} + (let + duplicates = + # Filter out the groups with more than 1 element + lib.filter (l: lib.length l > 1) ( + # Grab the groups, we don't care about the group identifiers + lib.attrValues ( + # Group the settings that are the same in lower case + lib.groupBy lib.strings.toLower (attrNames cfg.settings) + ) + ); + formattedDuplicates = lib.concatMapStringsSep ", " (dupl: "(${lib.concatStringsSep ", " dupl})") duplicates; + in + { + assertion = lib.length duplicates == 0; + message = ''Duplicate sshd config key; does your capitalization match the option's? Duplicate keys: ${formattedDuplicates}''; + })] ++ forEach cfg.listenAddresses ({ addr, ... }: { assertion = addr != null; message = "addr must be specified in each listenAddresses entry"; }); - }; } |