at 18.09-beta 16 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 7 cfg = config.services.openssh; 8 cfgc = config.programs.ssh; 9 10 nssModulesPath = config.system.nssModules.path; 11 12 userOptions = { 13 14 openssh.authorizedKeys = { 15 keys = mkOption { 16 type = types.listOf types.str; 17 default = []; 18 description = '' 19 A list of verbatim OpenSSH public keys that should be added to the 20 user's authorized keys. The keys are added to a file that the SSH 21 daemon reads in addition to the the user's authorized_keys file. 22 You can combine the <literal>keys</literal> and 23 <literal>keyFiles</literal> options. 24 Warning: If you are using <literal>NixOps</literal> then don't use this 25 option since it will replace the key required for deployment via ssh. 26 ''; 27 }; 28 29 keyFiles = mkOption { 30 type = types.listOf types.path; 31 default = []; 32 description = '' 33 A list of files each containing one OpenSSH public key that should be 34 added to the user's authorized keys. The contents of the files are 35 read at build time and added to a file that the SSH daemon reads in 36 addition to the the user's authorized_keys file. You can combine the 37 <literal>keyFiles</literal> and <literal>keys</literal> options. 38 ''; 39 }; 40 }; 41 42 }; 43 44 authKeysFiles = let 45 mkAuthKeyFile = u: nameValuePair "ssh/authorized_keys.d/${u.name}" { 46 mode = "0444"; 47 source = pkgs.writeText "${u.name}-authorized_keys" '' 48 ${concatStringsSep "\n" u.openssh.authorizedKeys.keys} 49 ${concatMapStrings (f: readFile f + "\n") u.openssh.authorizedKeys.keyFiles} 50 ''; 51 }; 52 usersWithKeys = attrValues (flip filterAttrs config.users.users (n: u: 53 length u.openssh.authorizedKeys.keys != 0 || length u.openssh.authorizedKeys.keyFiles != 0 54 )); 55 in listToAttrs (map mkAuthKeyFile usersWithKeys); 56 57in 58 59{ 60 61 ###### interface 62 63 options = { 64 65 services.openssh = { 66 67 enable = mkOption { 68 type = types.bool; 69 default = false; 70 description = '' 71 Whether to enable the OpenSSH secure shell daemon, which 72 allows secure remote logins. 73 ''; 74 }; 75 76 startWhenNeeded = mkOption { 77 type = types.bool; 78 default = false; 79 description = '' 80 If set, <command>sshd</command> is socket-activated; that 81 is, instead of having it permanently running as a daemon, 82 systemd will start an instance for each incoming connection. 83 ''; 84 }; 85 86 forwardX11 = mkOption { 87 type = types.bool; 88 default = false; 89 description = '' 90 Whether to allow X11 connections to be forwarded. 91 ''; 92 }; 93 94 allowSFTP = mkOption { 95 type = types.bool; 96 default = true; 97 description = '' 98 Whether to enable the SFTP subsystem in the SSH daemon. This 99 enables the use of commands such as <command>sftp</command> and 100 <command>sshfs</command>. 101 ''; 102 }; 103 104 sftpFlags = mkOption { 105 type = with types; listOf str; 106 default = []; 107 example = [ "-f AUTHPRIV" "-l INFO" ]; 108 description = '' 109 Commandline flags to add to sftp-server. 110 ''; 111 }; 112 113 permitRootLogin = mkOption { 114 default = "prohibit-password"; 115 type = types.enum ["yes" "without-password" "prohibit-password" "forced-commands-only" "no"]; 116 description = '' 117 Whether the root user can login using ssh. 118 ''; 119 }; 120 121 gatewayPorts = mkOption { 122 type = types.str; 123 default = "no"; 124 description = '' 125 Specifies whether remote hosts are allowed to connect to 126 ports forwarded for the client. See 127 <citerefentry><refentrytitle>sshd_config</refentrytitle> 128 <manvolnum>5</manvolnum></citerefentry>. 129 ''; 130 }; 131 132 ports = mkOption { 133 type = types.listOf types.int; 134 default = [22]; 135 description = '' 136 Specifies on which ports the SSH daemon listens. 137 ''; 138 }; 139 140 openFirewall = mkOption { 141 type = types.bool; 142 default = true; 143 description = '' 144 Whether to automatically open the specified ports in the firewall. 145 ''; 146 }; 147 148 listenAddresses = mkOption { 149 type = with types; listOf (submodule { 150 options = { 151 addr = mkOption { 152 type = types.nullOr types.str; 153 default = null; 154 description = '' 155 Host, IPv4 or IPv6 address to listen to. 156 ''; 157 }; 158 port = mkOption { 159 type = types.nullOr types.int; 160 default = null; 161 description = '' 162 Port to listen to. 163 ''; 164 }; 165 }; 166 }); 167 default = []; 168 example = [ { addr = "192.168.3.1"; port = 22; } { addr = "0.0.0.0"; port = 64022; } ]; 169 description = '' 170 List of addresses and ports to listen on (ListenAddress directive 171 in config). If port is not specified for address sshd will listen 172 on all ports specified by <literal>ports</literal> option. 173 NOTE: this will override default listening on all local addresses and port 22. 174 NOTE: setting this option won't automatically enable given ports 175 in firewall configuration. 176 ''; 177 }; 178 179 passwordAuthentication = mkOption { 180 type = types.bool; 181 default = true; 182 description = '' 183 Specifies whether password authentication is allowed. 184 ''; 185 }; 186 187 challengeResponseAuthentication = mkOption { 188 type = types.bool; 189 default = true; 190 description = '' 191 Specifies whether challenge/response authentication is allowed. 192 ''; 193 }; 194 195 hostKeys = mkOption { 196 type = types.listOf types.attrs; 197 default = 198 [ { type = "rsa"; bits = 4096; path = "/etc/ssh/ssh_host_rsa_key"; } 199 { type = "ed25519"; path = "/etc/ssh/ssh_host_ed25519_key"; } 200 ]; 201 example = 202 [ { type = "rsa"; bits = 4096; path = "/etc/ssh/ssh_host_rsa_key"; rounds = 100; openSSHFormat = true; } 203 { type = "ed25519"; path = "/etc/ssh/ssh_host_ed25519_key"; rounds = 100; comment = "key comment"; } 204 ]; 205 description = '' 206 NixOS can automatically generate SSH host keys. This option 207 specifies the path, type and size of each key. See 208 <citerefentry><refentrytitle>ssh-keygen</refentrytitle> 209 <manvolnum>1</manvolnum></citerefentry> for supported types 210 and sizes. 211 ''; 212 }; 213 214 authorizedKeysFiles = mkOption { 215 type = types.listOf types.str; 216 default = []; 217 description = "Files from which authorized keys are read."; 218 }; 219 220 kexAlgorithms = mkOption { 221 type = types.listOf types.str; 222 default = [ 223 "curve25519-sha256@libssh.org" 224 "diffie-hellman-group-exchange-sha256" 225 ]; 226 description = '' 227 Allowed key exchange algorithms 228 </para> 229 <para> 230 Defaults to recommended settings from both 231 <link xlink:href="https://stribika.github.io/2015/01/04/secure-secure-shell.html" /> 232 and 233 <link xlink:href="https://wiki.mozilla.org/Security/Guidelines/OpenSSH#Modern_.28OpenSSH_6.7.2B.29" /> 234 ''; 235 }; 236 237 ciphers = mkOption { 238 type = types.listOf types.str; 239 default = [ 240 "chacha20-poly1305@openssh.com" 241 "aes256-gcm@openssh.com" 242 "aes128-gcm@openssh.com" 243 "aes256-ctr" 244 "aes192-ctr" 245 "aes128-ctr" 246 ]; 247 description = '' 248 Allowed ciphers 249 </para> 250 <para> 251 Defaults to recommended settings from both 252 <link xlink:href="https://stribika.github.io/2015/01/04/secure-secure-shell.html" /> 253 and 254 <link xlink:href="https://wiki.mozilla.org/Security/Guidelines/OpenSSH#Modern_.28OpenSSH_6.7.2B.29" /> 255 ''; 256 }; 257 258 macs = mkOption { 259 type = types.listOf types.str; 260 default = [ 261 "hmac-sha2-512-etm@openssh.com" 262 "hmac-sha2-256-etm@openssh.com" 263 "umac-128-etm@openssh.com" 264 "hmac-sha2-512" 265 "hmac-sha2-256" 266 "umac-128@openssh.com" 267 ]; 268 description = '' 269 Allowed MACs 270 </para> 271 <para> 272 Defaults to recommended settings from both 273 <link xlink:href="https://stribika.github.io/2015/01/04/secure-secure-shell.html" /> 274 and 275 <link xlink:href="https://wiki.mozilla.org/Security/Guidelines/OpenSSH#Modern_.28OpenSSH_6.7.2B.29" /> 276 ''; 277 }; 278 279 logLevel = mkOption { 280 type = types.enum [ "QUIET" "FATAL" "ERROR" "INFO" "VERBOSE" "DEBUG" "DEBUG1" "DEBUG2" "DEBUG3" ]; 281 default = "VERBOSE"; 282 description = '' 283 Gives the verbosity level that is used when logging messages from sshd(8). The possible values are: 284 QUIET, FATAL, ERROR, INFO, VERBOSE, DEBUG, DEBUG1, DEBUG2, and DEBUG3. The default is VERBOSE. DEBUG and DEBUG1 285 are equivalent. DEBUG2 and DEBUG3 each specify higher levels of debugging output. Logging with a DEBUG level 286 violates the privacy of users and is not recommended. 287 288 LogLevel VERBOSE logs user's key fingerprint on login. 289 Needed to have a clear audit track of which key was used to log in. 290 ''; 291 }; 292 293 useDns = mkOption { 294 type = types.bool; 295 default = false; 296 description = '' 297 Specifies whether sshd(8) should look up the remote host name, and to check that the resolved host name for 298 the remote IP address maps back to the very same IP address. 299 If this option is set to no (the default) then only addresses and not host names may be used in 300 ~/.ssh/authorized_keys from and sshd_config Match Host directives. 301 ''; 302 }; 303 304 extraConfig = mkOption { 305 type = types.lines; 306 default = ""; 307 description = "Verbatim contents of <filename>sshd_config</filename>."; 308 }; 309 310 moduliFile = mkOption { 311 example = "/etc/my-local-ssh-moduli;"; 312 type = types.path; 313 description = '' 314 Path to <literal>moduli</literal> file to install in 315 <literal>/etc/ssh/moduli</literal>. If this option is unset, then 316 the <literal>moduli</literal> file shipped with OpenSSH will be used. 317 ''; 318 }; 319 320 }; 321 322 users.users = mkOption { 323 options = [ userOptions ]; 324 }; 325 326 }; 327 328 329 ###### implementation 330 331 config = mkIf cfg.enable { 332 333 users.users.sshd = 334 { isSystemUser = true; 335 description = "SSH privilege separation user"; 336 }; 337 338 services.openssh.moduliFile = mkDefault "${cfgc.package}/etc/ssh/moduli"; 339 340 environment.etc = authKeysFiles // 341 { "ssh/moduli".source = cfg.moduliFile; 342 "ssh/sshd_config".text = cfg.extraConfig; 343 }; 344 345 systemd = 346 let 347 service = 348 { description = "SSH Daemon"; 349 wantedBy = optional (!cfg.startWhenNeeded) "multi-user.target"; 350 after = [ "network.target" ]; 351 stopIfChanged = false; 352 path = [ cfgc.package pkgs.gawk ]; 353 environment.LD_LIBRARY_PATH = nssModulesPath; 354 355 preStart = 356 '' 357 # Make sure we don't write to stdout, since in case of 358 # socket activation, it goes to the remote side (#19589). 359 exec >&2 360 361 mkdir -m 0755 -p /etc/ssh 362 363 ${flip concatMapStrings cfg.hostKeys (k: '' 364 if ! [ -f "${k.path}" ]; then 365 ssh-keygen \ 366 -t "${k.type}" \ 367 ${if k ? bits then "-b ${toString k.bits}" else ""} \ 368 ${if k ? rounds then "-a ${toString k.rounds}" else ""} \ 369 ${if k ? comment then "-C '${k.comment}'" else ""} \ 370 ${if k ? openSSHFormat && k.openSSHFormat then "-o" else ""} \ 371 -f "${k.path}" \ 372 -N "" 373 fi 374 '')} 375 ''; 376 377 serviceConfig = 378 { ExecStart = 379 (optionalString cfg.startWhenNeeded "-") + 380 "${cfgc.package}/bin/sshd " + (optionalString cfg.startWhenNeeded "-i ") + 381 "-f /etc/ssh/sshd_config"; 382 KillMode = "process"; 383 } // (if cfg.startWhenNeeded then { 384 StandardInput = "socket"; 385 StandardError = "journal"; 386 } else { 387 Restart = "always"; 388 Type = "simple"; 389 }); 390 }; 391 in 392 393 if cfg.startWhenNeeded then { 394 395 sockets.sshd = 396 { description = "SSH Socket"; 397 wantedBy = [ "sockets.target" ]; 398 socketConfig.ListenStream = cfg.ports; 399 socketConfig.Accept = true; 400 }; 401 402 services."sshd@" = service; 403 404 } else { 405 406 services.sshd = service; 407 408 }; 409 410 networking.firewall.allowedTCPPorts = if cfg.openFirewall then cfg.ports else []; 411 412 security.pam.services.sshd = 413 { startSession = true; 414 showMotd = true; 415 unixAuth = cfg.passwordAuthentication; 416 }; 417 418 # These values are merged with the ones defined externally, see: 419 # https://github.com/NixOS/nixpkgs/pull/10155 420 # https://github.com/NixOS/nixpkgs/pull/41745 421 services.openssh.authorizedKeysFiles = 422 [ ".ssh/authorized_keys" ".ssh/authorized_keys2" "/etc/ssh/authorized_keys.d/%u" ]; 423 424 services.openssh.extraConfig = mkOrder 0 425 '' 426 Protocol 2 427 428 UsePAM yes 429 430 AddressFamily ${if config.networking.enableIPv6 then "any" else "inet"} 431 ${concatMapStrings (port: '' 432 Port ${toString port} 433 '') cfg.ports} 434 435 ${concatMapStrings ({ port, addr, ... }: '' 436 ListenAddress ${addr}${if port != null then ":" + toString port else ""} 437 '') cfg.listenAddresses} 438 439 ${optionalString cfgc.setXAuthLocation '' 440 XAuthLocation ${pkgs.xorg.xauth}/bin/xauth 441 ''} 442 443 ${if cfg.forwardX11 then '' 444 X11Forwarding yes 445 '' else '' 446 X11Forwarding no 447 ''} 448 449 ${optionalString cfg.allowSFTP '' 450 Subsystem sftp ${cfgc.package}/libexec/sftp-server ${concatStringsSep " " cfg.sftpFlags} 451 ''} 452 453 PermitRootLogin ${cfg.permitRootLogin} 454 GatewayPorts ${cfg.gatewayPorts} 455 PasswordAuthentication ${if cfg.passwordAuthentication then "yes" else "no"} 456 ChallengeResponseAuthentication ${if cfg.challengeResponseAuthentication then "yes" else "no"} 457 458 PrintMotd no # handled by pam_motd 459 460 AuthorizedKeysFile ${toString cfg.authorizedKeysFiles} 461 462 ${flip concatMapStrings cfg.hostKeys (k: '' 463 HostKey ${k.path} 464 '')} 465 466 KexAlgorithms ${concatStringsSep "," cfg.kexAlgorithms} 467 Ciphers ${concatStringsSep "," cfg.ciphers} 468 MACs ${concatStringsSep "," cfg.macs} 469 470 LogLevel ${cfg.logLevel} 471 472 ${if cfg.useDns then '' 473 UseDNS yes 474 '' else '' 475 UseDNS no 476 ''} 477 478 ''; 479 480 assertions = [{ assertion = if cfg.forwardX11 then cfgc.setXAuthLocation else true; 481 message = "cannot enable X11 forwarding without setting xauth location";}] 482 ++ flip map cfg.listenAddresses ({ addr, ... }: { 483 assertion = addr != null; 484 message = "addr must be specified in each listenAddresses entry"; 485 }); 486 487 }; 488 489}