at 15.09-beta 14 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 knownHosts = map (h: getAttr h cfg.knownHosts) (attrNames cfg.knownHosts); 13 14 knownHostsText = flip (concatMapStringsSep "\n") knownHosts 15 (h: 16 concatStringsSep "," h.hostNames + " " 17 + (if h.publicKey != null then h.publicKey else readFile h.publicKeyFile) 18 ); 19 20 userOptions = { 21 22 openssh.authorizedKeys = { 23 keys = mkOption { 24 type = types.listOf types.str; 25 default = []; 26 description = '' 27 A list of verbatim OpenSSH public keys that should be added to the 28 user's authorized keys. The keys are added to a file that the SSH 29 daemon reads in addition to the the user's authorized_keys file. 30 You can combine the <literal>keys</literal> and 31 <literal>keyFiles</literal> options. 32 ''; 33 }; 34 35 keyFiles = mkOption { 36 type = types.listOf types.path; 37 default = []; 38 description = '' 39 A list of files each containing one OpenSSH public key that should be 40 added to the user's authorized keys. The contents of the files are 41 read at build time and added to a file that the SSH daemon reads in 42 addition to the the user's authorized_keys file. You can combine the 43 <literal>keyFiles</literal> and <literal>keys</literal> options. 44 ''; 45 }; 46 }; 47 48 }; 49 50 authKeysFiles = let 51 mkAuthKeyFile = u: { 52 target = "ssh/authorized_keys.d/${u.name}"; 53 mode = "0444"; 54 source = pkgs.writeText "${u.name}-authorized_keys" '' 55 ${concatStringsSep "\n" u.openssh.authorizedKeys.keys} 56 ${concatMapStrings (f: readFile f + "\n") u.openssh.authorizedKeys.keyFiles} 57 ''; 58 }; 59 usersWithKeys = attrValues (flip filterAttrs config.users.extraUsers (n: u: 60 length u.openssh.authorizedKeys.keys != 0 || length u.openssh.authorizedKeys.keyFiles != 0 61 )); 62 in map mkAuthKeyFile usersWithKeys; 63 64in 65 66{ 67 68 ###### interface 69 70 options = { 71 72 services.openssh = { 73 74 enable = mkOption { 75 type = types.bool; 76 default = false; 77 description = '' 78 Whether to enable the OpenSSH secure shell daemon, which 79 allows secure remote logins. 80 ''; 81 }; 82 83 startWhenNeeded = mkOption { 84 type = types.bool; 85 default = false; 86 description = '' 87 If set, <command>sshd</command> is socket-activated; that 88 is, instead of having it permanently running as a daemon, 89 systemd will start an instance for each incoming connection. 90 ''; 91 }; 92 93 forwardX11 = mkOption { 94 type = types.bool; 95 default = cfgc.setXAuthLocation; 96 description = '' 97 Whether to allow X11 connections to be forwarded. 98 ''; 99 }; 100 101 allowSFTP = mkOption { 102 type = types.bool; 103 default = true; 104 description = '' 105 Whether to enable the SFTP subsystem in the SSH daemon. This 106 enables the use of commands such as <command>sftp</command> and 107 <command>sshfs</command>. 108 ''; 109 }; 110 111 permitRootLogin = mkOption { 112 default = "without-password"; 113 type = types.enum ["yes" "without-password" "forced-commands-only" "no"]; 114 description = '' 115 Whether the root user can login using ssh. 116 ''; 117 }; 118 119 gatewayPorts = mkOption { 120 type = types.str; 121 default = "no"; 122 description = '' 123 Specifies whether remote hosts are allowed to connect to 124 ports forwarded for the client. See 125 <citerefentry><refentrytitle>sshd_config</refentrytitle> 126 <manvolnum>5</manvolnum></citerefentry>. 127 ''; 128 }; 129 130 ports = mkOption { 131 type = types.listOf types.int; 132 default = [22]; 133 description = '' 134 Specifies on which ports the SSH daemon listens. 135 ''; 136 }; 137 138 listenAddresses = mkOption { 139 type = types.listOf types.optionSet; 140 default = []; 141 example = [ { addr = "192.168.3.1"; port = 22; } { addr = "0.0.0.0"; port = 64022; } ]; 142 description = '' 143 List of addresses and ports to listen on (ListenAddress directive 144 in config). If port is not specified for address sshd will listen 145 on all ports specified by <literal>ports</literal> option. 146 NOTE: this will override default listening on all local addresses and port 22. 147 NOTE: setting this option won't automatically enable given ports 148 in firewall configuration. 149 ''; 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 168 passwordAuthentication = mkOption { 169 type = types.bool; 170 default = true; 171 description = '' 172 Specifies whether password authentication is allowed. 173 ''; 174 }; 175 176 challengeResponseAuthentication = mkOption { 177 type = types.bool; 178 default = true; 179 description = '' 180 Specifies whether challenge/response authentication is allowed. 181 ''; 182 }; 183 184 hostKeys = mkOption { 185 type = types.listOf types.attrs; 186 default = 187 [ { type = "rsa"; bits = 4096; path = "/etc/ssh/ssh_host_rsa_key"; } 188 { type = "ed25519"; path = "/etc/ssh/ssh_host_ed25519_key"; } 189 ] ++ optionals (!versionAtLeast config.system.stateVersion "15.07") 190 [ { type = "dsa"; path = "/etc/ssh/ssh_host_dsa_key"; } 191 { type = "ecdsa"; bits = 521; path = "/etc/ssh/ssh_host_ecdsa_key"; } 192 ]; 193 description = '' 194 NixOS can automatically generate SSH host keys. This option 195 specifies the path, type and size of each key. See 196 <citerefentry><refentrytitle>ssh-keygen</refentrytitle> 197 <manvolnum>1</manvolnum></citerefentry> for supported types 198 and sizes. 199 ''; 200 }; 201 202 authorizedKeysFiles = mkOption { 203 type = types.listOf types.str; 204 default = []; 205 description = "Files from with authorized keys are read."; 206 }; 207 208 extraConfig = mkOption { 209 type = types.lines; 210 default = ""; 211 description = "Verbatim contents of <filename>sshd_config</filename>."; 212 }; 213 214 knownHosts = mkOption { 215 default = {}; 216 type = types.loaOf types.optionSet; 217 description = '' 218 The set of system-wide known SSH hosts. 219 ''; 220 example = [ 221 { 222 hostNames = [ "myhost" "myhost.mydomain.com" "10.10.1.4" ]; 223 publicKeyFile = literalExample "./pubkeys/myhost_ssh_host_dsa_key.pub"; 224 } 225 { 226 hostNames = [ "myhost2" ]; 227 publicKeyFile = literalExample "./pubkeys/myhost2_ssh_host_dsa_key.pub"; 228 } 229 ]; 230 options = { 231 hostNames = mkOption { 232 type = types.listOf types.str; 233 default = []; 234 description = '' 235 A list of host names and/or IP numbers used for accessing 236 the host's ssh service. 237 ''; 238 }; 239 publicKey = mkOption { 240 default = null; 241 type = types.nullOr types.str; 242 example = "ecdsa-sha2-nistp521 AAAAE2VjZHN...UEPg=="; 243 description = '' 244 The public key data for the host. You can fetch a public key 245 from a running SSH server with the <command>ssh-keyscan</command> 246 command. The public key should not include any host names, only 247 the key type and the key itself. 248 ''; 249 }; 250 publicKeyFile = mkOption { 251 default = null; 252 type = types.nullOr types.path; 253 description = '' 254 The path to the public key file for the host. The public 255 key file is read at build time and saved in the Nix store. 256 You can fetch a public key file from a running SSH server 257 with the <command>ssh-keyscan</command> command. The content 258 of the file should follow the same format as described for 259 the <literal>publicKey</literal> option. 260 ''; 261 }; 262 }; 263 }; 264 265 moduliFile = mkOption { 266 example = "services.openssh.moduliFile = /etc/my-local-ssh-moduli;"; 267 type = types.path; 268 description = '' 269 Path to <literal>moduli</literal> file to install in 270 <literal>/etc/ssh/moduli</literal>. If this option is unset, then 271 the <literal>moduli</literal> file shipped with OpenSSH will be used. 272 ''; 273 }; 274 275 }; 276 277 users.extraUsers = mkOption { 278 options = [ userOptions ]; 279 }; 280 281 }; 282 283 284 ###### implementation 285 286 config = mkIf cfg.enable { 287 288 users.extraUsers.sshd = 289 { isSystemUser = true; 290 description = "SSH privilege separation user"; 291 }; 292 293 services.openssh.moduliFile = mkDefault "${cfgc.package}/etc/ssh/moduli"; 294 295 environment.etc = authKeysFiles ++ [ 296 { source = cfg.moduliFile; 297 target = "ssh/moduli"; 298 } 299 { text = knownHostsText; 300 target = "ssh/ssh_known_hosts"; 301 } 302 ]; 303 304 systemd = 305 let 306 service = 307 { description = "SSH Daemon"; 308 309 wantedBy = optional (!cfg.startWhenNeeded) "multi-user.target"; 310 311 stopIfChanged = false; 312 313 path = [ cfgc.package pkgs.gawk ]; 314 315 environment.LD_LIBRARY_PATH = nssModulesPath; 316 317 preStart = 318 '' 319 mkdir -m 0755 -p /etc/ssh 320 321 ${flip concatMapStrings cfg.hostKeys (k: '' 322 if ! [ -f "${k.path}" ]; then 323 ssh-keygen -t "${k.type}" ${if k ? bits then "-b ${toString k.bits}" else ""} -f "${k.path}" -N "" 324 fi 325 '')} 326 ''; 327 328 serviceConfig = 329 { ExecStart = 330 "${cfgc.package}/sbin/sshd " + (optionalString cfg.startWhenNeeded "-i ") + 331 "-f ${pkgs.writeText "sshd_config" cfg.extraConfig}"; 332 KillMode = "process"; 333 } // (if cfg.startWhenNeeded then { 334 StandardInput = "socket"; 335 } else { 336 Restart = "always"; 337 Type = "forking"; 338 PIDFile = "/run/sshd.pid"; 339 }); 340 }; 341 in 342 343 if cfg.startWhenNeeded then { 344 345 sockets.sshd = 346 { description = "SSH Socket"; 347 wantedBy = [ "sockets.target" ]; 348 socketConfig.ListenStream = cfg.ports; 349 socketConfig.Accept = true; 350 }; 351 352 services."sshd@" = service; 353 354 } else { 355 356 services.sshd = service; 357 358 }; 359 360 networking.firewall.allowedTCPPorts = cfg.ports; 361 362 security.pam.services.sshd = 363 { startSession = true; 364 showMotd = true; 365 unixAuth = cfg.passwordAuthentication; 366 }; 367 368 services.openssh.authorizedKeysFiles = 369 [ ".ssh/authorized_keys" ".ssh/authorized_keys2" "/etc/ssh/authorized_keys.d/%u" ]; 370 371 services.openssh.extraConfig = 372 '' 373 PidFile /run/sshd.pid 374 375 Protocol 2 376 377 UsePAM yes 378 379 UsePrivilegeSeparation sandbox 380 381 AddressFamily ${if config.networking.enableIPv6 then "any" else "inet"} 382 ${concatMapStrings (port: '' 383 Port ${toString port} 384 '') cfg.ports} 385 386 ${concatMapStrings ({ port, addr, ... }: '' 387 ListenAddress ${addr}${if port != null then ":" + toString port else ""} 388 '') cfg.listenAddresses} 389 390 ${optionalString cfgc.setXAuthLocation '' 391 XAuthLocation ${pkgs.xorg.xauth}/bin/xauth 392 ''} 393 394 ${if cfg.forwardX11 then '' 395 X11Forwarding yes 396 '' else '' 397 X11Forwarding no 398 ''} 399 400 ${optionalString cfg.allowSFTP '' 401 Subsystem sftp ${cfgc.package}/libexec/sftp-server 402 ''} 403 404 PermitRootLogin ${cfg.permitRootLogin} 405 GatewayPorts ${cfg.gatewayPorts} 406 PasswordAuthentication ${if cfg.passwordAuthentication then "yes" else "no"} 407 ChallengeResponseAuthentication ${if cfg.challengeResponseAuthentication then "yes" else "no"} 408 409 PrintMotd no # handled by pam_motd 410 411 AuthorizedKeysFile ${toString cfg.authorizedKeysFiles} 412 413 ${flip concatMapStrings cfg.hostKeys (k: '' 414 HostKey ${k.path} 415 '')} 416 ''; 417 418 assertions = [{ assertion = if cfg.forwardX11 then cfgc.setXAuthLocation else true; 419 message = "cannot enable X11 forwarding without setting xauth location";}] 420 ++ flip mapAttrsToList cfg.knownHosts (name: data: { 421 assertion = (data.publicKey == null && data.publicKeyFile != null) || 422 (data.publicKey != null && data.publicKeyFile == null); 423 message = "knownHost ${name} must contain either a publicKey or publicKeyFile"; 424 }) 425 ++ flip map cfg.listenAddresses ({ addr, port, ... }: { 426 assertion = addr != null; 427 message = "addr must be specified in each listenAddresses entry"; 428 }); 429 430 }; 431 432}