{ config, pkgs, ... }: with pkgs.lib; let cfg = config.services.openssh; nssModulesPath = config.system.nssModules.path; permitRootLoginCheck = v: v == "yes" || v == "without-password" || v == "forced-commands-only" || v == "no"; userOptions = { openssh.authorizedKeys = { preserveExistingKeys = mkOption { type = types.bool; default = true; description = '' If this option is enabled, the keys specified in keys and/or keyFiles 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 preserveExisting 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 keys and keyFiles options. ''; }; keyFiles = mkOption { type = types.listOf types.string; 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 keyFiles and keys options. ''; }; }; }; mkAuthkeyScript = let marker1 = "### NixOS will regenerate this line and every line below it."; 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; authKeyFiles = concatStringsSep "," u.openssh.authorizedKeys.keyFiles; 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" IFS="," for f in $authKeyFiles; do if [ -f "$f" ]; then authKeys="$(${pkgs.coreutils}/bin/cat "$f"),$authKeys" fi done if [ -n "$authKeys" ]; then eval authfile=~$userName/.ssh/authorized_keys ${pkgs.coreutils}/bin/mkdir -p "$(dirname $authfile)" ${pkgs.coreutils}/bin/touch "$authfile" if [ "$preserveExisting" == "false" ]; then rm -f "$authfile" authKeys="${marker2},$authKeys" else ${pkgs.gnused}/bin/sed -i '/^### NixOS.*$/,$d' "$authfile" authKeys="${marker1},$authKeys" fi for key in $authKeys; do ${pkgs.coreutils}/bin/echo "$key" >> "$authfile"; done fi unset IFS } ${userLoop} ''; 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 = true; 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 sftp and sshfs. ''; }; permitRootLogin = mkOption { default = "yes"; check = permitRootLoginCheck; description = '' Whether the root user can login using ssh. Valid values are yes, without-password, forced-commands-only or no. If without-password doesn't work try yes. ''; }; gatewayPorts = mkOption { default = "no"; description = '' Specifies whether remote hosts are allowed to connect to ports forwarded for the client. See sshd_config 5. ''; }; 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 false is most probably not going to have the desired effect unless usePAM is disabled as well. ''; }; extraConfig = mkOption { default = ""; description = "Verbatim contents of sshd_config."; }; }; users.extraUsers = mkOption { options = [ userOptions ]; }; }; ###### implementation config = mkIf config.services.openssh.enable { users.extraUsers = singleton { name = "sshd"; uid = config.ids.uids.sshd; description = "SSH privilege separation user"; home = "/var/empty"; }; environment.etc = singleton { source = "${pkgs.openssh}/etc/ssh/moduli"; target = "ssh/moduli"; }; jobs.sshd = { description = "OpenSSH server"; startOn = "started network-interfaces"; environment = { LD_LIBRARY_PATH = nssModulesPath; # Duplicated from bashrc. OpenSSH needs a patch for this. LOCALE_ARCHIVE = "/var/run/current-system/sw/lib/locale/locale-archive"; }; preStart = '' ${mkAuthkeyScript} mkdir -m 0755 -p /etc/ssh if ! test -f /etc/ssh/ssh_host_dsa_key; then ${pkgs.openssh}/bin/ssh-keygen -t dsa -b 1024 -f /etc/ssh/ssh_host_dsa_key -N "" fi ''; daemonType = "fork"; exec = '' ${pkgs.openssh}/sbin/sshd -h /etc/ssh/ssh_host_dsa_key \ -f ${pkgs.writeText "sshd_config" cfg.extraConfig} ''; }; networking.firewall.allowedTCPPorts = cfg.ports; services.openssh.extraConfig = '' Protocol 2 UsePAM ${if cfg.usePAM then "yes" else "no"} ${concatMapStrings (port: '' Port ${toString port} '') cfg.ports} ${if cfg.forwardX11 then '' X11Forwarding yes XAuthLocation ${pkgs.xlibs.xauth}/bin/xauth '' 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"} ''; }; }