2014-04-14 16:26:48 +02:00
|
|
|
{ config, lib, pkgs, ... }:
|
2010-02-01 17:05:02 +00:00
|
|
|
|
2014-04-14 16:26:48 +02:00
|
|
|
with lib;
|
2009-03-06 12:26:08 +00:00
|
|
|
|
2007-01-07 10:19:16 +00:00
|
|
|
let
|
2009-03-06 12:26:08 +00:00
|
|
|
|
2019-06-08 01:13:52 -04:00
|
|
|
# The splicing information needed for nativeBuildInputs isn't available
|
|
|
|
# on the derivations likely to be used as `cfgc.package`.
|
|
|
|
# This middle-ground solution ensures *an* sshd can do their basic validation
|
|
|
|
# on the configuration.
|
|
|
|
validationPackage = if pkgs.stdenv.buildPlatform == pkgs.stdenv.hostPlatform
|
2020-04-05 13:03:22 +02:00
|
|
|
then cfgc.package
|
|
|
|
else pkgs.buildPackages.openssh;
|
2019-06-08 01:13:52 -04:00
|
|
|
|
2023-01-15 16:32:46 +01:00
|
|
|
# reports boolean as yes / no
|
2023-02-07 00:11:18 +01:00
|
|
|
mkValueStringSshd = with lib; v:
|
2023-01-15 16:32:46 +01:00
|
|
|
if isInt v then toString v
|
|
|
|
else if isString v then v
|
|
|
|
else if true == v then "yes"
|
|
|
|
else if false == v then "no"
|
2023-02-07 00:11:18 +01:00
|
|
|
else if isList v then concatStringsSep "," v
|
2023-03-22 16:19:36 +01:00
|
|
|
else throw "unsupported type ${builtins.typeOf v}: ${(lib.generators.toPretty {}) v}";
|
2023-01-15 16:32:46 +01:00
|
|
|
|
|
|
|
# dont use the "=" operator
|
|
|
|
settingsFormat = (pkgs.formats.keyValue {
|
|
|
|
mkKeyValue = lib.generators.mkKeyValueDefault {
|
|
|
|
mkValueString = mkValueStringSshd;
|
|
|
|
} " ";});
|
|
|
|
|
2023-09-19 13:04:11 +02:00
|
|
|
configFile = settingsFormat.generate "sshd.conf-settings" cfg.settings;
|
|
|
|
sshconf = pkgs.runCommand "sshd.conf-final" { } ''
|
2023-01-15 16:32:46 +01:00
|
|
|
cat ${configFile} - >$out <<EOL
|
2019-04-02 00:30:43 +02:00
|
|
|
${cfg.extraConfig}
|
|
|
|
EOL
|
|
|
|
'';
|
|
|
|
|
2012-03-25 15:42:05 +00:00
|
|
|
cfg = config.services.openssh;
|
|
|
|
cfgc = config.programs.ssh;
|
2009-03-06 12:26:08 +00:00
|
|
|
|
2023-01-15 16:32:46 +01:00
|
|
|
|
2009-03-06 12:26:08 +00:00
|
|
|
nssModulesPath = config.system.nssModules.path;
|
2007-01-07 10:19:16 +00:00
|
|
|
|
2011-11-29 06:08:55 +00:00
|
|
|
userOptions = {
|
2012-08-17 13:48:22 -04:00
|
|
|
|
2019-01-26 21:44:05 +02:00
|
|
|
options.openssh.authorizedKeys = {
|
2011-11-29 06:08:55 +00:00
|
|
|
keys = mkOption {
|
types.singleLineStr: strings that don't contain '\n'
Add a new type, inheriting 'types.str' but checking whether the value
doesn't contain any newline characters.
The motivation comes from a problem with the
'users.users.${u}.openssh.authorizedKeys' option.
It is easy to unintentionally insert a newline character at the end of a
string, or even in the middle, for example:
restricted_ssh_keys = command: keys:
let
prefix = ''
command="${command}",no-pty,no-agent-forwarding,no-port-forwarding,no-X11-forwarding
'';
in map (key: "${prefix} ${key}") keys;
The 'prefix' string ends with a newline, which ends up in the middle of
a key entry after a few manipulations.
This is problematic because the key file is built by concatenating all
the keys with 'concatStringsSep "\n"', with result in two entries for
the faulty key:
''
command="...",options...
MY_KEY
''
This is hard to debug and might be dangerous. This is now caught at
build time.
2022-01-18 21:56:14 +01:00
|
|
|
type = types.listOf types.singleLineStr;
|
2011-11-29 06:08:55 +00:00
|
|
|
default = [];
|
|
|
|
description = lib.mdDoc ''
|
2012-11-20 15:13:17 +01:00
|
|
|
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 `keys` and
|
2011-11-29 06:08:55 +00:00
|
|
|
`keyFiles` options.
|
2018-01-18 21:24:36 +07:00
|
|
|
Warning: If you are using `NixOps` then don't use this
|
2017-06-22 11:50:09 +02:00
|
|
|
option since it will replace the key required for deployment via ssh.
|
2011-11-29 06:08:55 +00:00
|
|
|
'';
|
2021-06-15 11:49:19 +02:00
|
|
|
example = [
|
|
|
|
"ssh-rsa AAAAB3NzaC1yc2etc/etc/etcjwrsh8e596z6J0l7 example@host"
|
|
|
|
"ssh-ed25519 AAAAC3NzaCetcetera/etceteraJZMfk3QPfQ foo@bar"
|
|
|
|
];
|
2011-11-29 06:08:55 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
keyFiles = mkOption {
|
2014-05-01 16:27:16 -05:00
|
|
|
type = types.listOf types.path;
|
2011-11-29 06:08:55 +00:00
|
|
|
default = [];
|
|
|
|
description = lib.mdDoc ''
|
2012-11-20 15:13:17 +01:00
|
|
|
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
|
|
|
|
`keyFiles` and `keys` options.
|
2011-11-29 06:08:55 +00:00
|
|
|
'';
|
|
|
|
};
|
|
|
|
};
|
2012-08-17 13:48:22 -04:00
|
|
|
|
2011-11-29 06:08:55 +00:00
|
|
|
};
|
|
|
|
|
2012-12-11 17:14:52 +01:00
|
|
|
authKeysFiles = let
|
2015-08-27 15:24:14 +02:00
|
|
|
mkAuthKeyFile = u: nameValuePair "ssh/authorized_keys.d/${u.name}" {
|
2014-06-27 09:19:30 +02:00
|
|
|
mode = "0444";
|
2012-11-20 15:13:17 +01:00
|
|
|
source = pkgs.writeText "${u.name}-authorized_keys" ''
|
|
|
|
${concatStringsSep "\n" u.openssh.authorizedKeys.keys}
|
2013-11-12 13:48:19 +01:00
|
|
|
${concatMapStrings (f: readFile f + "\n") u.openssh.authorizedKeys.keyFiles}
|
2012-11-20 15:13:17 +01:00
|
|
|
'';
|
|
|
|
};
|
2018-06-30 01:58:35 +02:00
|
|
|
usersWithKeys = attrValues (flip filterAttrs config.users.users (n: u:
|
2012-11-20 15:13:17 +01:00
|
|
|
length u.openssh.authorizedKeys.keys != 0 || length u.openssh.authorizedKeys.keyFiles != 0
|
|
|
|
));
|
2015-08-27 15:24:14 +02:00
|
|
|
in listToAttrs (map mkAuthKeyFile usersWithKeys);
|
2011-11-29 06:08:55 +00:00
|
|
|
|
2007-01-07 10:19:16 +00:00
|
|
|
in
|
2006-11-23 17:43:28 +00:00
|
|
|
|
2009-07-15 15:53:39 +00:00
|
|
|
{
|
2019-12-10 02:51:19 +01:00
|
|
|
imports = [
|
2022-12-30 20:43:53 +01:00
|
|
|
(mkAliasOptionModuleMD [ "services" "sshd" "enable" ] [ "services" "openssh" "enable" ])
|
|
|
|
(mkAliasOptionModuleMD [ "services" "openssh" "knownHosts" ] [ "programs" "ssh" "knownHosts" ])
|
2022-01-14 21:59:11 +01:00
|
|
|
(mkRenamedOptionModule [ "services" "openssh" "challengeResponseAuthentication" ] [ "services" "openssh" "kbdInteractiveAuthentication" ])
|
2023-01-15 16:32:46 +01:00
|
|
|
|
|
|
|
(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" ])
|
2023-02-07 00:11:18 +01:00
|
|
|
(mkRenamedOptionModule [ "services" "openssh" "macs" ] [ "services" "openssh" "settings" "Macs" ])
|
2023-02-07 13:53:02 -05:00
|
|
|
(mkRenamedOptionModule [ "services" "openssh" "ciphers" ] [ "services" "openssh" "settings" "Ciphers" ])
|
2023-02-07 00:11:18 +01:00
|
|
|
(mkRenamedOptionModule [ "services" "openssh" "kexAlgorithms" ] [ "services" "openssh" "settings" "KexAlgorithms" ])
|
|
|
|
(mkRenamedOptionModule [ "services" "openssh" "gatewayPorts" ] [ "services" "openssh" "settings" "GatewayPorts" ])
|
|
|
|
(mkRenamedOptionModule [ "services" "openssh" "forwardX11" ] [ "services" "openssh" "settings" "X11Forwarding" ])
|
2019-12-10 02:51:19 +01:00
|
|
|
];
|
2007-06-08 15:41:12 +00:00
|
|
|
|
2009-07-15 15:53:39 +00:00
|
|
|
###### interface
|
2011-07-12 10:34:30 +00:00
|
|
|
|
2009-07-15 15:53:39 +00:00
|
|
|
options = {
|
2011-07-12 10:34:30 +00:00
|
|
|
|
2010-03-11 17:02:49 +00:00
|
|
|
services.openssh = {
|
2009-07-15 15:53:39 +00:00
|
|
|
|
|
|
|
enable = mkOption {
|
2013-10-30 17:37:45 +01:00
|
|
|
type = types.bool;
|
2009-07-15 15:53:39 +00:00
|
|
|
default = false;
|
|
|
|
description = lib.mdDoc ''
|
2010-03-11 17:02:49 +00:00
|
|
|
Whether to enable the OpenSSH secure shell daemon, which
|
|
|
|
allows secure remote logins.
|
2009-07-15 15:53:39 +00:00
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
2014-04-22 16:07:53 +02:00
|
|
|
startWhenNeeded = mkOption {
|
|
|
|
type = types.bool;
|
|
|
|
default = false;
|
|
|
|
description = lib.mdDoc ''
|
|
|
|
If set, {command}`sshd` is socket-activated; that
|
|
|
|
is, instead of having it permanently running as a daemon,
|
|
|
|
systemd will start an instance for each incoming connection.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
2009-07-15 15:53:39 +00:00
|
|
|
allowSFTP = mkOption {
|
2013-10-30 17:37:45 +01:00
|
|
|
type = types.bool;
|
2009-07-15 15:53:39 +00:00
|
|
|
default = true;
|
|
|
|
description = lib.mdDoc ''
|
|
|
|
Whether to enable the SFTP subsystem in the SSH daemon. This
|
|
|
|
enables the use of commands such as {command}`sftp` and
|
|
|
|
{command}`sshfs`.
|
|
|
|
'';
|
|
|
|
};
|
2006-11-23 17:43:28 +00:00
|
|
|
|
2020-11-21 15:47:13 -08:00
|
|
|
sftpServerExecutable = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
example = "internal-sftp";
|
|
|
|
description = lib.mdDoc ''
|
|
|
|
The sftp server executable. Can be a path or "internal-sftp" to use
|
|
|
|
the sftp server built into the sshd binary.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
2017-09-18 21:43:16 +02:00
|
|
|
sftpFlags = mkOption {
|
|
|
|
type = with types; listOf str;
|
|
|
|
default = [];
|
|
|
|
example = [ "-f AUTHPRIV" "-l INFO" ];
|
|
|
|
description = lib.mdDoc ''
|
|
|
|
Commandline flags to add to sftp-server.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
2009-12-10 14:45:41 +00:00
|
|
|
ports = mkOption {
|
2018-10-18 23:42:20 +02:00
|
|
|
type = types.listOf types.port;
|
2009-12-10 14:45:41 +00:00
|
|
|
default = [22];
|
|
|
|
description = lib.mdDoc ''
|
|
|
|
Specifies on which ports the SSH daemon listens.
|
|
|
|
'';
|
|
|
|
};
|
2011-07-12 10:34:27 +00:00
|
|
|
|
2018-01-18 21:24:36 +07:00
|
|
|
openFirewall = mkOption {
|
|
|
|
type = types.bool;
|
|
|
|
default = true;
|
|
|
|
description = lib.mdDoc ''
|
|
|
|
Whether to automatically open the specified ports in the firewall.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
2014-08-31 13:15:39 +02:00
|
|
|
listenAddresses = mkOption {
|
2016-09-11 18:08:31 +09:00
|
|
|
type = with types; listOf (submodule {
|
|
|
|
options = {
|
|
|
|
addr = mkOption {
|
|
|
|
type = types.nullOr types.str;
|
|
|
|
default = null;
|
|
|
|
description = lib.mdDoc ''
|
|
|
|
Host, IPv4 or IPv6 address to listen to.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
port = mkOption {
|
|
|
|
type = types.nullOr types.int;
|
|
|
|
default = null;
|
|
|
|
description = lib.mdDoc ''
|
|
|
|
Port to listen to.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
};
|
|
|
|
});
|
2014-08-31 13:15:39 +02:00
|
|
|
default = [];
|
|
|
|
example = [ { addr = "192.168.3.1"; port = 22; } { addr = "0.0.0.0"; port = 64022; } ];
|
|
|
|
description = lib.mdDoc ''
|
|
|
|
List of addresses and ports to listen on (ListenAddress directive
|
|
|
|
in config). If port is not specified for address sshd will listen
|
2014-09-01 13:02:39 +02:00
|
|
|
on all ports specified by `ports` option.
|
|
|
|
NOTE: this will override default listening on all local addresses and port 22.
|
2014-08-31 17:21:14 +02:00
|
|
|
NOTE: setting this option won't automatically enable given ports
|
|
|
|
in firewall configuration.
|
2014-08-31 13:15:39 +02:00
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
2013-08-24 01:01:10 +02:00
|
|
|
hostKeys = mkOption {
|
2013-10-30 17:37:45 +01:00
|
|
|
type = types.listOf types.attrs;
|
2013-08-24 01:01:10 +02:00
|
|
|
default =
|
2015-07-27 20:13:08 +02:00
|
|
|
[ { type = "rsa"; bits = 4096; path = "/etc/ssh/ssh_host_rsa_key"; }
|
|
|
|
{ type = "ed25519"; path = "/etc/ssh/ssh_host_ed25519_key"; }
|
2013-08-24 01:01:10 +02:00
|
|
|
];
|
2018-06-12 18:26:20 +02:00
|
|
|
example =
|
|
|
|
[ { type = "rsa"; bits = 4096; path = "/etc/ssh/ssh_host_rsa_key"; rounds = 100; openSSHFormat = true; }
|
|
|
|
{ type = "ed25519"; path = "/etc/ssh/ssh_host_ed25519_key"; rounds = 100; comment = "key comment"; }
|
|
|
|
];
|
2012-05-09 22:13:53 +00:00
|
|
|
description = lib.mdDoc ''
|
2013-08-24 01:01:10 +02:00
|
|
|
NixOS can automatically generate SSH host keys. This option
|
|
|
|
specifies the path, type and size of each key. See
|
|
|
|
{manpage}`ssh-keygen(1)` for supported types
|
|
|
|
and sizes.
|
2012-05-09 22:13:53 +00:00
|
|
|
'';
|
2012-02-22 20:28:54 +00:00
|
|
|
};
|
|
|
|
|
2019-09-16 19:21:23 +09:00
|
|
|
banner = mkOption {
|
|
|
|
type = types.nullOr types.lines;
|
|
|
|
default = null;
|
|
|
|
description = lib.mdDoc ''
|
|
|
|
Message to display to the remote user before authentication is allowed.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
2012-12-11 17:29:34 +01:00
|
|
|
authorizedKeysFiles = mkOption {
|
2014-05-01 16:27:16 -05:00
|
|
|
type = types.listOf types.str;
|
2012-12-11 17:29:34 +01:00
|
|
|
default = [];
|
2021-06-15 12:23:09 +02:00
|
|
|
description = lib.mdDoc ''
|
|
|
|
Specify the rules for which files to read on the host.
|
|
|
|
|
|
|
|
This is an advanced option. If you're looking to configure user
|
|
|
|
keys, you can generally use [](#opt-users.users._name_.openssh.authorizedKeys.keys)
|
|
|
|
or [](#opt-users.users._name_.openssh.authorizedKeys.keyFiles).
|
|
|
|
|
|
|
|
These are paths relative to the host root file system or home
|
|
|
|
directories and they are subject to certain token expansion rules.
|
|
|
|
See AuthorizedKeysFile in man sshd_config for details.
|
|
|
|
'';
|
2012-12-11 17:29:34 +01:00
|
|
|
};
|
|
|
|
|
2020-03-12 10:58:50 -04:00
|
|
|
authorizedKeysCommand = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
default = "none";
|
|
|
|
description = lib.mdDoc ''
|
|
|
|
Specifies a program to be used to look up the user's public
|
|
|
|
keys. The program must be owned by root, not writable by group
|
|
|
|
or others and specified by an absolute path.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
authorizedKeysCommandUser = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
default = "nobody";
|
|
|
|
description = lib.mdDoc ''
|
|
|
|
Specifies the user under whose account the AuthorizedKeysCommand
|
|
|
|
is run. It is recommended to use a dedicated user that has no
|
|
|
|
other role on the host than running authorized keys commands.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
2018-04-20 13:27:10 +02:00
|
|
|
|
2018-05-17 20:33:09 +03:00
|
|
|
|
2023-01-15 16:32:46 +01:00
|
|
|
settings = mkOption {
|
2023-01-16 00:17:20 +01:00
|
|
|
description = lib.mdDoc "Configuration for `sshd_config(5)`.";
|
2023-01-16 00:11:53 +01:00
|
|
|
default = { };
|
2023-06-30 17:13:31 +02:00
|
|
|
example = literalExpression ''
|
|
|
|
{
|
|
|
|
UseDns = true;
|
|
|
|
PasswordAuthentication = false;
|
|
|
|
}
|
|
|
|
'';
|
2023-01-15 16:32:46 +01:00
|
|
|
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.
|
|
|
|
'';
|
|
|
|
};
|
2023-02-07 00:11:18 +01:00
|
|
|
X11Forwarding = mkOption {
|
|
|
|
type = types.bool;
|
|
|
|
default = false;
|
|
|
|
description = lib.mdDoc ''
|
|
|
|
Whether to allow X11 connections to be forwarded.
|
|
|
|
'';
|
|
|
|
};
|
2023-01-15 16:32:46 +01:00
|
|
|
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.
|
|
|
|
'';
|
|
|
|
};
|
2023-02-07 00:11:18 +01:00
|
|
|
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
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
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>
|
|
|
|
'';
|
|
|
|
};
|
2019-09-08 00:25:04 +00:00
|
|
|
StrictModes = mkOption {
|
|
|
|
type = types.bool;
|
|
|
|
default = true;
|
|
|
|
description = lib.mdDoc ''
|
|
|
|
Whether sshd should check file modes and ownership of directories
|
|
|
|
'';
|
|
|
|
};
|
2023-02-07 00:11:18 +01:00
|
|
|
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>
|
|
|
|
'';
|
|
|
|
};
|
2023-01-15 16:32:46 +01:00
|
|
|
};
|
|
|
|
});
|
2018-05-17 20:33:09 +03:00
|
|
|
};
|
|
|
|
|
2010-10-18 10:31:41 +00:00
|
|
|
extraConfig = mkOption {
|
2013-10-30 17:37:45 +01:00
|
|
|
type = types.lines;
|
2010-10-18 10:31:41 +00:00
|
|
|
default = "";
|
|
|
|
description = lib.mdDoc "Verbatim contents of {file}`sshd_config`.";
|
|
|
|
};
|
2011-07-12 10:34:30 +00:00
|
|
|
|
2015-05-22 14:23:21 +02:00
|
|
|
moduliFile = mkOption {
|
2017-09-18 21:43:16 +02:00
|
|
|
example = "/etc/my-local-ssh-moduli;";
|
2015-05-22 14:23:21 +02:00
|
|
|
type = types.path;
|
|
|
|
description = lib.mdDoc ''
|
|
|
|
Path to `moduli` file to install in
|
|
|
|
`/etc/ssh/moduli`. If this option is unset, then
|
|
|
|
the `moduli` file shipped with OpenSSH will be used.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
2009-07-15 15:53:39 +00:00
|
|
|
};
|
|
|
|
|
2015-09-02 17:32:38 +02:00
|
|
|
users.users = mkOption {
|
2020-08-23 01:28:45 +02:00
|
|
|
type = with types; attrsOf (submodule userOptions);
|
2011-11-29 06:08:55 +00:00
|
|
|
};
|
|
|
|
|
2009-07-15 15:53:39 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
###### implementation
|
|
|
|
|
2013-08-24 01:01:10 +02:00
|
|
|
config = mkIf cfg.enable {
|
2009-07-15 15:53:39 +00:00
|
|
|
|
2018-06-30 01:58:35 +02:00
|
|
|
users.users.sshd =
|
2021-08-08 12:00:00 +00:00
|
|
|
{
|
|
|
|
isSystemUser = true;
|
|
|
|
group = "sshd";
|
2015-05-13 16:22:53 +02:00
|
|
|
description = "SSH privilege separation user";
|
2009-07-15 15:53:39 +00:00
|
|
|
};
|
2021-08-08 12:00:00 +00:00
|
|
|
users.groups.sshd = {};
|
2009-03-06 12:26:13 +00:00
|
|
|
|
2015-05-22 14:23:21 +02:00
|
|
|
services.openssh.moduliFile = mkDefault "${cfgc.package}/etc/ssh/moduli";
|
2020-11-21 15:47:13 -08:00
|
|
|
services.openssh.sftpServerExecutable = mkDefault "${cfgc.package}/libexec/sftp-server";
|
2015-05-22 14:23:21 +02:00
|
|
|
|
2015-08-27 15:24:14 +02:00
|
|
|
environment.etc = authKeysFiles //
|
2018-06-10 02:39:06 +03:00
|
|
|
{ "ssh/moduli".source = cfg.moduliFile;
|
2019-04-02 00:30:43 +02:00
|
|
|
"ssh/sshd_config".source = sshconf;
|
2018-06-10 02:39:06 +03:00
|
|
|
};
|
2010-02-01 17:05:02 +00:00
|
|
|
|
2014-04-22 16:07:53 +02:00
|
|
|
systemd =
|
|
|
|
let
|
2017-03-31 16:05:35 +02:00
|
|
|
service =
|
2014-04-22 16:07:53 +02:00
|
|
|
{ description = "SSH Daemon";
|
|
|
|
wantedBy = optional (!cfg.startWhenNeeded) "multi-user.target";
|
2017-12-24 14:57:14 +00:00
|
|
|
after = [ "network.target" ];
|
2014-04-22 16:07:53 +02:00
|
|
|
stopIfChanged = false;
|
2014-09-11 21:43:58 -07:00
|
|
|
path = [ cfgc.package pkgs.gawk ];
|
2014-04-22 16:07:53 +02:00
|
|
|
environment.LD_LIBRARY_PATH = nssModulesPath;
|
|
|
|
|
2018-12-29 18:26:53 +01:00
|
|
|
restartTriggers = optionals (!cfg.startWhenNeeded) [
|
|
|
|
config.environment.etc."ssh/sshd_config".source
|
|
|
|
];
|
|
|
|
|
2017-03-31 16:05:35 +02:00
|
|
|
preStart =
|
|
|
|
''
|
2017-03-31 16:16:27 +02:00
|
|
|
# Make sure we don't write to stdout, since in case of
|
|
|
|
# socket activation, it goes to the remote side (#19589).
|
|
|
|
exec >&2
|
|
|
|
|
2017-03-31 16:05:35 +02:00
|
|
|
${flip concatMapStrings cfg.hostKeys (k: ''
|
2021-10-12 12:21:53 +02:00
|
|
|
if ! [ -s "${k.path}" ]; then
|
2022-07-21 19:15:04 +02:00
|
|
|
if ! [ -h "${k.path}" ]; then
|
|
|
|
rm -f "${k.path}"
|
|
|
|
fi
|
2022-08-08 23:10:59 +02:00
|
|
|
mkdir -m 0755 -p "$(dirname '${k.path}')"
|
2018-06-12 18:26:20 +02:00
|
|
|
ssh-keygen \
|
|
|
|
-t "${k.type}" \
|
2023-03-19 21:44:31 +01:00
|
|
|
${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"} \
|
2018-06-12 18:26:20 +02:00
|
|
|
-f "${k.path}" \
|
|
|
|
-N ""
|
2017-03-31 16:05:35 +02:00
|
|
|
fi
|
|
|
|
'')}
|
|
|
|
'';
|
2014-04-22 16:07:53 +02:00
|
|
|
|
|
|
|
serviceConfig =
|
|
|
|
{ ExecStart =
|
2016-06-19 17:19:31 +08:00
|
|
|
(optionalString cfg.startWhenNeeded "-") +
|
2016-03-05 23:56:32 -05:00
|
|
|
"${cfgc.package}/bin/sshd " + (optionalString cfg.startWhenNeeded "-i ") +
|
2021-07-01 00:43:54 +02:00
|
|
|
"-D " + # don't detach into a daemon process
|
2018-06-10 02:39:06 +03:00
|
|
|
"-f /etc/ssh/sshd_config";
|
2014-04-22 16:07:53 +02:00
|
|
|
KillMode = "process";
|
|
|
|
} // (if cfg.startWhenNeeded then {
|
|
|
|
StandardInput = "socket";
|
2017-03-31 16:16:27 +02:00
|
|
|
StandardError = "journal";
|
2014-04-22 16:07:53 +02:00
|
|
|
} else {
|
|
|
|
Restart = "always";
|
2016-12-29 09:49:19 -05:00
|
|
|
Type = "simple";
|
2014-04-22 16:07:53 +02:00
|
|
|
});
|
2018-12-29 18:26:53 +01:00
|
|
|
|
2014-04-22 16:07:53 +02:00
|
|
|
};
|
|
|
|
in
|
2012-06-18 15:28:31 -04:00
|
|
|
|
2014-04-22 16:07:53 +02:00
|
|
|
if cfg.startWhenNeeded then {
|
2013-01-05 01:05:25 +01:00
|
|
|
|
2014-04-22 16:07:53 +02:00
|
|
|
sockets.sshd =
|
|
|
|
{ description = "SSH Socket";
|
|
|
|
wantedBy = [ "sockets.target" ];
|
2019-02-25 00:48:01 +01:00
|
|
|
socketConfig.ListenStream = if cfg.listenAddresses != [] then
|
|
|
|
map (l: "${l.addr}:${toString (if l.port != null then l.port else 22)}") cfg.listenAddresses
|
|
|
|
else
|
|
|
|
cfg.ports;
|
2014-04-22 16:07:53 +02:00
|
|
|
socketConfig.Accept = true;
|
2020-11-15 20:37:17 -05:00
|
|
|
# Prevent brute-force attacks from shutting down socket
|
|
|
|
socketConfig.TriggerLimitIntervalSec = 0;
|
2014-04-22 16:07:53 +02:00
|
|
|
};
|
2012-08-17 13:48:22 -04:00
|
|
|
|
2017-03-31 16:05:35 +02:00
|
|
|
services."sshd@" = service;
|
2012-06-18 15:28:31 -04:00
|
|
|
|
2014-04-22 16:07:53 +02:00
|
|
|
} else {
|
2012-06-18 15:28:31 -04:00
|
|
|
|
2017-03-31 16:05:35 +02:00
|
|
|
services.sshd = service;
|
2012-06-18 15:28:31 -04:00
|
|
|
|
|
|
|
};
|
|
|
|
|
2023-06-25 11:47:43 +02:00
|
|
|
networking.firewall.allowedTCPPorts = optionals cfg.openFirewall cfg.ports;
|
2010-10-18 10:31:41 +00:00
|
|
|
|
2013-10-15 15:05:49 +02:00
|
|
|
security.pam.services.sshd =
|
2014-04-22 15:39:11 +02:00
|
|
|
{ startSession = true;
|
2013-10-15 15:05:49 +02:00
|
|
|
showMotd = true;
|
2023-01-15 16:32:46 +01:00
|
|
|
unixAuth = cfg.settings.PasswordAuthentication;
|
2013-10-15 15:05:49 +02:00
|
|
|
};
|
2012-08-17 13:48:22 -04:00
|
|
|
|
2018-06-12 18:30:53 +02:00
|
|
|
# These values are merged with the ones defined externally, see:
|
|
|
|
# https://github.com/NixOS/nixpkgs/pull/10155
|
|
|
|
# https://github.com/NixOS/nixpkgs/pull/41745
|
2012-12-11 17:29:34 +01:00
|
|
|
services.openssh.authorizedKeysFiles =
|
2023-04-13 13:56:08 +02:00
|
|
|
[ "%h/.ssh/authorized_keys" "/etc/ssh/authorized_keys.d/%u" ];
|
2012-12-11 17:29:34 +01:00
|
|
|
|
2016-02-23 18:03:33 +01:00
|
|
|
services.openssh.extraConfig = mkOrder 0
|
2010-10-18 10:31:41 +00:00
|
|
|
''
|
2013-10-15 15:05:49 +02:00
|
|
|
UsePAM yes
|
2010-10-18 10:31:41 +00:00
|
|
|
|
2019-09-16 19:21:23 +09:00
|
|
|
Banner ${if cfg.banner == null then "none" else pkgs.writeText "ssh_banner" cfg.banner}
|
|
|
|
|
2012-10-24 19:01:27 +02:00
|
|
|
AddressFamily ${if config.networking.enableIPv6 then "any" else "inet"}
|
2010-10-18 10:31:41 +00:00
|
|
|
${concatMapStrings (port: ''
|
|
|
|
Port ${toString port}
|
|
|
|
'') cfg.ports}
|
|
|
|
|
2015-04-15 00:20:38 +02:00
|
|
|
${concatMapStrings ({ port, addr, ... }: ''
|
2023-03-19 21:44:31 +01:00
|
|
|
ListenAddress ${addr}${optionalString (port != null) (":" + toString port)}
|
2014-08-31 13:15:39 +02:00
|
|
|
'') cfg.listenAddresses}
|
|
|
|
|
2012-03-25 15:42:05 +00:00
|
|
|
${optionalString cfgc.setXAuthLocation ''
|
|
|
|
XAuthLocation ${pkgs.xorg.xauth}/bin/xauth
|
|
|
|
''}
|
2010-10-18 10:31:41 +00:00
|
|
|
${optionalString cfg.allowSFTP ''
|
2020-11-21 15:47:13 -08:00
|
|
|
Subsystem sftp ${cfg.sftpServerExecutable} ${concatStringsSep " " cfg.sftpFlags}
|
2010-10-18 10:31:41 +00:00
|
|
|
''}
|
2012-10-23 09:10:48 -04:00
|
|
|
PrintMotd no # handled by pam_motd
|
2012-12-11 17:29:34 +01:00
|
|
|
AuthorizedKeysFile ${toString cfg.authorizedKeysFiles}
|
2020-03-14 19:50:11 -04:00
|
|
|
${optionalString (cfg.authorizedKeysCommand != "none") ''
|
|
|
|
AuthorizedKeysCommand ${cfg.authorizedKeysCommand}
|
|
|
|
AuthorizedKeysCommandUser ${cfg.authorizedKeysCommandUser}
|
|
|
|
''}
|
2013-08-24 01:01:10 +02:00
|
|
|
|
|
|
|
${flip concatMapStrings cfg.hostKeys (k: ''
|
|
|
|
HostKey ${k.path}
|
|
|
|
'')}
|
2010-10-18 10:31:41 +00:00
|
|
|
'';
|
|
|
|
|
2023-09-19 13:04:11 +02:00
|
|
|
system.checks = [
|
|
|
|
(pkgs.runCommand "check-sshd-config"
|
|
|
|
{
|
|
|
|
nativeBuildInputs = [ validationPackage ];
|
|
|
|
} ''
|
|
|
|
${concatMapStringsSep "\n"
|
|
|
|
(lport: "sshd -G -T -C lport=${toString lport} -f ${sshconf} > /dev/null")
|
|
|
|
cfg.ports}
|
|
|
|
${concatMapStringsSep "\n"
|
2023-09-27 22:59:13 +02:00
|
|
|
(la: "sshd -G -T -C ${escapeShellArg "laddr=${la.addr},lport=${toString la.port}"} -f ${sshconf} > /dev/null")
|
2023-09-19 13:04:11 +02:00
|
|
|
cfg.listenAddresses}
|
|
|
|
touch $out
|
|
|
|
'')
|
|
|
|
];
|
|
|
|
|
2023-02-07 00:11:18 +01:00
|
|
|
assertions = [{ assertion = if cfg.settings.X11Forwarding then cfgc.setXAuthLocation else true;
|
2023-05-12 06:14:43 -07:00
|
|
|
message = "cannot enable X11 forwarding without setting xauth location";}
|
2023-05-29 10:40:36 +02:00
|
|
|
(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}'';
|
|
|
|
})]
|
2019-08-05 14:03:38 +03:00
|
|
|
++ forEach cfg.listenAddresses ({ addr, ... }: {
|
2014-08-31 13:15:39 +02:00
|
|
|
assertion = addr != null;
|
2014-09-02 10:06:04 +02:00
|
|
|
message = "addr must be specified in each listenAddresses entry";
|
2014-04-27 17:01:06 -05:00
|
|
|
});
|
2009-03-06 12:26:08 +00:00
|
|
|
};
|
2009-07-15 15:53:39 +00:00
|
|
|
|
2006-11-23 17:43:28 +00:00
|
|
|
}
|