2010-02-01 18:05:02 +01:00
|
|
|
{ config, pkgs, ... }:
|
|
|
|
|
|
|
|
with pkgs.lib;
|
2009-03-06 13:26:08 +01:00
|
|
|
|
2007-01-07 11:19:16 +01:00
|
|
|
let
|
2009-03-06 13:26:08 +01:00
|
|
|
|
2012-03-25 17:42:05 +02:00
|
|
|
cfg = config.services.openssh;
|
|
|
|
cfgc = config.programs.ssh;
|
2009-03-06 13:26:08 +01:00
|
|
|
|
|
|
|
nssModulesPath = config.system.nssModules.path;
|
2007-01-07 11:19:16 +01:00
|
|
|
|
2009-08-19 17:04:05 +02:00
|
|
|
permitRootLoginCheck = v:
|
|
|
|
v == "yes" ||
|
|
|
|
v == "without-password" ||
|
|
|
|
v == "forced-commands-only" ||
|
|
|
|
v == "no";
|
2009-03-06 13:26:08 +01:00
|
|
|
|
2012-02-22 21:28:54 +01:00
|
|
|
hostKeyTypeNames = {
|
|
|
|
dsa1024 = "dsa";
|
|
|
|
rsa1024 = "rsa";
|
|
|
|
ecdsa521 = "ecdsa";
|
|
|
|
};
|
|
|
|
|
|
|
|
hostKeyTypeBits = {
|
|
|
|
dsa1024 = 1024;
|
|
|
|
rsa1024 = 1024;
|
|
|
|
ecdsa521 = 521;
|
|
|
|
};
|
|
|
|
|
|
|
|
hktn = attrByPath [cfg.hostKeyType] (throw "unknown host key type `${cfg.hostKeyType}'") hostKeyTypeNames;
|
|
|
|
hktb = attrByPath [cfg.hostKeyType] (throw "unknown host key type `${cfg.hostKeyType}'") hostKeyTypeBits;
|
|
|
|
|
2012-05-10 00:11:07 +02:00
|
|
|
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}"
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
2011-11-29 07:08:55 +01:00
|
|
|
userOptions = {
|
2012-08-17 19:48:22 +02:00
|
|
|
|
2011-11-29 07:08:55 +01:00
|
|
|
openssh.authorizedKeys = {
|
|
|
|
|
|
|
|
preserveExistingKeys = mkOption {
|
|
|
|
type = types.bool;
|
|
|
|
default = true;
|
|
|
|
description = ''
|
|
|
|
If this option is enabled, the keys specified in
|
|
|
|
<literal>keys</literal> and/or <literal>keyFiles</literal> will be
|
|
|
|
placed in a special section of the user's authorized_keys file
|
|
|
|
and any existing keys will be preserved. That section will be
|
|
|
|
regenerated each time NixOS is activated. However, if
|
|
|
|
<literal>preserveExisting</literal> isn't enabled, the complete file
|
|
|
|
will be generated, and any user modifications will be wiped out.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
keys = mkOption {
|
|
|
|
type = types.listOf types.string;
|
|
|
|
default = [];
|
|
|
|
description = ''
|
|
|
|
A list of verbatim OpenSSH public keys that should be inserted into the
|
|
|
|
user's authorized_keys file. You can combine the <literal>keys</literal> and
|
|
|
|
<literal>keyFiles</literal> options.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
keyFiles = mkOption {
|
2012-02-25 18:31:39 +01:00
|
|
|
#type = types.listOf types.string;
|
2011-11-29 07:08:55 +01:00
|
|
|
default = [];
|
|
|
|
description = ''
|
|
|
|
A list of files each containing one OpenSSH public keys that should be
|
|
|
|
inserted into the user's authorized_keys file. You can combine
|
|
|
|
the <literal>keyFiles</literal> and
|
|
|
|
<literal>keys</literal> options.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
};
|
2012-08-17 19:48:22 +02:00
|
|
|
|
2011-11-29 07:08:55 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
mkAuthkeyScript =
|
|
|
|
let
|
2012-04-25 16:14:20 +02:00
|
|
|
marker1 = "### NixOS auto-added key. Do not edit!";
|
2011-11-29 07:08:55 +01:00
|
|
|
marker2 = "### NixOS will regenerate this file. Do not edit!";
|
|
|
|
users = map (userName: getAttr userName config.users.extraUsers) (attrNames config.users.extraUsers);
|
|
|
|
usersWithKeys = flip filter users (u:
|
|
|
|
length u.openssh.authorizedKeys.keys != 0 || length u.openssh.authorizedKeys.keyFiles != 0
|
|
|
|
);
|
|
|
|
userLoop = flip concatMapStrings usersWithKeys (u:
|
|
|
|
let
|
|
|
|
authKeys = concatStringsSep "," u.openssh.authorizedKeys.keys;
|
2012-07-13 23:59:03 +02:00
|
|
|
authKeyFiles = concatStrings (map (x: " ${x}") u.openssh.authorizedKeys.keyFiles);
|
2011-11-29 07:08:55 +01:00
|
|
|
preserveExisting = if u.openssh.authorizedKeys.preserveExistingKeys then "true" else "false";
|
|
|
|
in ''
|
|
|
|
mkAuthKeysFile "${u.name}" "${authKeys}" "${authKeyFiles}" "${preserveExisting}"
|
|
|
|
''
|
|
|
|
);
|
|
|
|
in ''
|
|
|
|
mkAuthKeysFile() {
|
|
|
|
local userName="$1"
|
|
|
|
local authKeys="$2"
|
|
|
|
local authKeyFiles="$3"
|
|
|
|
local preserveExisting="$4"
|
|
|
|
|
2012-07-13 17:48:47 +02:00
|
|
|
eval homeDir=~$userName
|
|
|
|
if ! [ -d "$homeDir" ]; then
|
|
|
|
echo "User $userName does not exist"
|
|
|
|
return
|
|
|
|
fi
|
|
|
|
if ! [ -d "$homeDir/.ssh" ]; then
|
|
|
|
mkdir -v -m 700 "$homeDir/.ssh"
|
|
|
|
chown "$userName":users "$homeDir/.ssh"
|
|
|
|
fi
|
|
|
|
local authKeysFile="$homeDir/.ssh/authorized_keys"
|
|
|
|
touch "$authKeysFile"
|
2012-04-25 17:44:47 +02:00
|
|
|
if [ "$preserveExisting" == false ]; then
|
2012-07-13 17:48:47 +02:00
|
|
|
rm -f "$authKeysFile"
|
|
|
|
echo "${marker2}" > "$authKeysFile"
|
2012-04-25 17:44:47 +02:00
|
|
|
else
|
2012-07-13 17:48:47 +02:00
|
|
|
sed -i '/${marker1}/ d' "$authKeysFile"
|
2012-04-25 17:44:47 +02:00
|
|
|
fi
|
|
|
|
IFS=,
|
|
|
|
for f in $authKeys; do
|
2012-07-13 17:48:47 +02:00
|
|
|
echo "$f ${marker1}" >> "$authKeysFile"
|
2012-04-25 17:44:47 +02:00
|
|
|
done
|
|
|
|
unset IFS
|
2011-11-29 07:08:55 +01:00
|
|
|
for f in $authKeyFiles; do
|
|
|
|
if [ -f "$f" ]; then
|
2012-07-13 17:48:47 +02:00
|
|
|
echo "$(cat "$f") ${marker1}" >> "$authKeysFile"
|
2011-11-29 07:08:55 +01:00
|
|
|
fi
|
|
|
|
done
|
2012-07-13 17:48:47 +02:00
|
|
|
chown "$userName" "$authKeysFile"
|
2011-11-29 07:08:55 +01:00
|
|
|
}
|
2012-04-25 17:44:47 +02:00
|
|
|
|
2011-11-29 07:08:55 +01:00
|
|
|
${userLoop}
|
|
|
|
'';
|
|
|
|
|
2007-01-07 11:19:16 +01:00
|
|
|
in
|
2006-11-23 18:43:28 +01:00
|
|
|
|
2009-07-15 17:53:39 +02:00
|
|
|
{
|
2007-06-08 17:41:12 +02:00
|
|
|
|
2009-07-15 17:53:39 +02:00
|
|
|
###### interface
|
2011-07-12 12:34:30 +02:00
|
|
|
|
2009-07-15 17:53:39 +02:00
|
|
|
options = {
|
2011-07-12 12:34:30 +02:00
|
|
|
|
2010-03-11 18:02:49 +01:00
|
|
|
services.openssh = {
|
2009-07-15 17:53:39 +02:00
|
|
|
|
|
|
|
enable = mkOption {
|
|
|
|
default = false;
|
|
|
|
description = ''
|
2010-03-11 18:02:49 +01:00
|
|
|
Whether to enable the OpenSSH secure shell daemon, which
|
|
|
|
allows secure remote logins.
|
2009-07-15 17:53:39 +02:00
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
forwardX11 = mkOption {
|
2012-03-25 17:42:05 +02:00
|
|
|
default = cfgc.setXAuthLocation;
|
2009-07-15 17:53:39 +02:00
|
|
|
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>.
|
|
|
|
'';
|
|
|
|
};
|
2006-11-23 18:43:28 +01:00
|
|
|
|
2009-07-15 17:53:39 +02:00
|
|
|
permitRootLogin = mkOption {
|
2012-08-17 19:32:23 +02:00
|
|
|
default = "without-password";
|
2009-08-19 17:04:05 +02:00
|
|
|
check = permitRootLoginCheck;
|
2009-07-15 17:53:39 +02:00
|
|
|
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>.
|
|
|
|
'';
|
|
|
|
};
|
2009-12-10 15:45:41 +01:00
|
|
|
|
|
|
|
ports = mkOption {
|
|
|
|
default = [22];
|
|
|
|
description = ''
|
|
|
|
Specifies on which ports the SSH daemon listens.
|
|
|
|
'';
|
|
|
|
};
|
2011-07-12 12:34:27 +02:00
|
|
|
|
|
|
|
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.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
2012-04-01 12:54:17 +02:00
|
|
|
challengeResponseAuthentication = mkOption {
|
|
|
|
default = true;
|
|
|
|
description = ''
|
|
|
|
Specifies whether challenge/response authentication is allowed.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
2012-02-22 21:28:54 +01:00
|
|
|
hostKeyType = mkOption {
|
|
|
|
default = "dsa1024";
|
2012-05-10 00:13:53 +02:00
|
|
|
description = ''
|
|
|
|
Type of host key to generate (dsa1024/rsa1024/ecdsa521), if
|
|
|
|
the file specified by <literal>hostKeyPath</literal> does not
|
|
|
|
exist when the service starts.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
hostKeyPath = mkOption {
|
|
|
|
default = "/etc/ssh/ssh_host_${hktn}_key";
|
|
|
|
description = ''
|
|
|
|
Path to the server's private key. If there is no key file
|
|
|
|
on this path, it will be generated when the service is
|
|
|
|
started for the first time. Otherwise, the ssh daemon will
|
|
|
|
use the specified key directly in-place.
|
|
|
|
'';
|
2012-02-22 21:28:54 +01:00
|
|
|
};
|
|
|
|
|
2010-10-18 12:31:41 +02:00
|
|
|
extraConfig = mkOption {
|
|
|
|
default = "";
|
|
|
|
description = "Verbatim contents of <filename>sshd_config</filename>.";
|
|
|
|
};
|
2011-07-12 12:34:30 +02:00
|
|
|
|
2012-05-10 00:11:07 +02:00
|
|
|
knownHosts = mkOption {
|
|
|
|
default = {};
|
|
|
|
type = types.loaOf types.optionSet;
|
|
|
|
description = ''
|
|
|
|
The set of system-wide known SSH hosts.
|
|
|
|
'';
|
|
|
|
example = [
|
2012-08-17 19:48:22 +02:00
|
|
|
{
|
2012-05-10 00:11:07 +02:00
|
|
|
hostNames = [ "myhost" "myhost.mydomain.com" "10.10.1.4" ];
|
|
|
|
publicKeyFile = ./pubkeys/myhost_ssh_host_dsa_key.pub;
|
|
|
|
}
|
2012-08-17 19:48:22 +02:00
|
|
|
{
|
2012-05-10 00:11:07 +02:00
|
|
|
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.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2009-07-15 17:53:39 +02:00
|
|
|
};
|
|
|
|
|
2011-11-29 07:08:55 +01:00
|
|
|
users.extraUsers = mkOption {
|
|
|
|
options = [ userOptions ];
|
|
|
|
};
|
|
|
|
|
2009-07-15 17:53:39 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
###### implementation
|
|
|
|
|
2010-03-11 18:02:49 +01:00
|
|
|
config = mkIf config.services.openssh.enable {
|
2009-07-15 17:53:39 +02:00
|
|
|
|
2010-02-01 18:05:02 +01:00
|
|
|
users.extraUsers = singleton
|
2010-03-11 19:07:20 +01:00
|
|
|
{ name = "sshd";
|
2009-05-29 16:25:56 +02:00
|
|
|
uid = config.ids.uids.sshd;
|
2009-03-06 13:26:13 +01:00
|
|
|
description = "SSH privilege separation user";
|
|
|
|
home = "/var/empty";
|
2009-07-15 17:53:39 +02:00
|
|
|
};
|
2009-03-06 13:26:13 +01:00
|
|
|
|
2012-05-10 00:11:07 +02:00
|
|
|
environment.etc = [
|
2010-03-11 18:02:49 +01:00
|
|
|
{ source = "${pkgs.openssh}/etc/ssh/moduli";
|
2010-02-01 18:05:02 +01:00
|
|
|
target = "ssh/moduli";
|
2012-05-10 00:11:07 +02:00
|
|
|
}
|
|
|
|
{ source = knownHostsFile;
|
|
|
|
target = "ssh/ssh_known_hosts";
|
|
|
|
}
|
|
|
|
];
|
2010-02-01 18:05:02 +01:00
|
|
|
|
2012-08-23 16:25:27 +02:00
|
|
|
boot.systemd.services."set-ssh-keys" =
|
2012-06-18 21:28:31 +02:00
|
|
|
{ description = "Update authorized SSH keys";
|
|
|
|
|
|
|
|
wantedBy = [ "multi-user.target" ];
|
|
|
|
|
|
|
|
script = mkAuthkeyScript;
|
|
|
|
|
2012-10-01 22:27:42 +02:00
|
|
|
serviceConfig.Type = "oneshot";
|
|
|
|
serviceConfig.RemainAfterExit = true;
|
2012-06-18 21:28:31 +02:00
|
|
|
};
|
2012-08-17 19:48:22 +02:00
|
|
|
|
2012-08-23 16:25:27 +02:00
|
|
|
boot.systemd.services.sshd =
|
2012-07-19 23:33:22 +02:00
|
|
|
{ description = "SSH Daemon";
|
2012-06-18 21:28:31 +02:00
|
|
|
|
|
|
|
wantedBy = [ "multi-user.target" ];
|
|
|
|
after = [ "set-ssh-keys.service" ];
|
|
|
|
|
|
|
|
path = [ pkgs.openssh ];
|
2012-08-17 19:48:22 +02:00
|
|
|
|
2012-06-18 21:28:31 +02:00
|
|
|
environment.LD_LIBRARY_PATH = nssModulesPath;
|
2012-07-16 23:27:11 +02:00
|
|
|
environment.LOCALE_ARCHIVE = "/run/current-system/sw/lib/locale/locale-archive";
|
2012-06-18 21:28:31 +02:00
|
|
|
|
|
|
|
preStart =
|
|
|
|
''
|
|
|
|
mkdir -m 0755 -p /etc/ssh
|
|
|
|
|
|
|
|
if ! test -f ${cfg.hostKeyPath}; then
|
|
|
|
ssh-keygen -t ${hktn} -b ${toString hktb} -f ${cfg.hostKeyPath} -N ""
|
|
|
|
fi
|
|
|
|
'';
|
|
|
|
|
|
|
|
serviceConfig =
|
2012-10-01 22:27:42 +02:00
|
|
|
{ ExecStart =
|
2012-10-05 05:38:27 +02:00
|
|
|
"${pkgs.openssh}/sbin/sshd -h ${cfg.hostKeyPath} " +
|
2012-10-01 22:27:42 +02:00
|
|
|
"-f ${pkgs.writeText "sshd_config" cfg.extraConfig}";
|
|
|
|
Restart = "always";
|
2012-10-05 05:38:27 +02:00
|
|
|
Type = "forking";
|
2012-10-01 22:27:42 +02:00
|
|
|
KillMode = "process";
|
|
|
|
PIDFile = "/run/sshd.pid";
|
|
|
|
};
|
2012-06-18 21:28:31 +02:00
|
|
|
};
|
|
|
|
|
2010-02-01 18:05:02 +01:00
|
|
|
networking.firewall.allowedTCPPorts = cfg.ports;
|
2010-10-18 12:31:41 +02:00
|
|
|
|
2012-10-23 15:10:48 +02:00
|
|
|
security.pam.services = optional cfg.usePAM { name = "sshd"; startSession = true; showMotd = true; };
|
2012-08-17 19:48:22 +02:00
|
|
|
|
2010-10-18 12:31:41 +02:00
|
|
|
services.openssh.extraConfig =
|
|
|
|
''
|
2012-06-15 00:44:56 +02:00
|
|
|
PidFile /run/sshd.pid
|
2012-08-17 19:48:22 +02:00
|
|
|
|
2010-10-18 12:31:41 +02:00
|
|
|
Protocol 2
|
|
|
|
|
2011-07-12 12:34:27 +02:00
|
|
|
UsePAM ${if cfg.usePAM then "yes" else "no"}
|
2010-10-18 12:31:41 +02:00
|
|
|
|
|
|
|
${concatMapStrings (port: ''
|
|
|
|
Port ${toString port}
|
|
|
|
'') cfg.ports}
|
|
|
|
|
2012-03-25 17:42:05 +02:00
|
|
|
${optionalString cfgc.setXAuthLocation ''
|
|
|
|
XAuthLocation ${pkgs.xorg.xauth}/bin/xauth
|
|
|
|
''}
|
|
|
|
|
2010-10-18 12:31:41 +02:00
|
|
|
${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}
|
2011-07-12 12:34:27 +02:00
|
|
|
PasswordAuthentication ${if cfg.passwordAuthentication then "yes" else "no"}
|
2012-04-01 12:54:17 +02:00
|
|
|
ChallengeResponseAuthentication ${if cfg.challengeResponseAuthentication then "yes" else "no"}
|
2012-10-23 15:10:48 +02:00
|
|
|
|
|
|
|
PrintMotd no # handled by pam_motd
|
2010-10-18 12:31:41 +02:00
|
|
|
'';
|
|
|
|
|
2012-04-26 10:13:24 +02:00
|
|
|
assertions = [{ assertion = if cfg.forwardX11 then cfgc.setXAuthLocation else true;
|
2012-04-01 12:54:06 +02:00
|
|
|
message = "cannot enable X11 forwarding without setting xauth location";}];
|
2012-10-23 15:10:48 +02:00
|
|
|
|
2009-03-06 13:26:08 +01:00
|
|
|
};
|
2009-07-15 17:53:39 +02:00
|
|
|
|
2006-11-23 18:43:28 +01:00
|
|
|
}
|