at 16.09-beta 11 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 ''; 25 }; 26 27 keyFiles = mkOption { 28 type = types.listOf types.path; 29 default = []; 30 description = '' 31 A list of files each containing one OpenSSH public key that should be 32 added to the user's authorized keys. The contents of the files are 33 read at build time and added to a file that the SSH daemon reads in 34 addition to the the user's authorized_keys file. You can combine the 35 <literal>keyFiles</literal> and <literal>keys</literal> options. 36 ''; 37 }; 38 }; 39 40 }; 41 42 authKeysFiles = let 43 mkAuthKeyFile = u: nameValuePair "ssh/authorized_keys.d/${u.name}" { 44 mode = "0444"; 45 source = pkgs.writeText "${u.name}-authorized_keys" '' 46 ${concatStringsSep "\n" u.openssh.authorizedKeys.keys} 47 ${concatMapStrings (f: readFile f + "\n") u.openssh.authorizedKeys.keyFiles} 48 ''; 49 }; 50 usersWithKeys = attrValues (flip filterAttrs config.users.extraUsers (n: u: 51 length u.openssh.authorizedKeys.keys != 0 || length u.openssh.authorizedKeys.keyFiles != 0 52 )); 53 in listToAttrs (map mkAuthKeyFile usersWithKeys); 54 55 supportOldHostKeys = !versionAtLeast config.system.stateVersion "15.07"; 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 = cfgc.setXAuthLocation; 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 permitRootLogin = mkOption { 105 default = "without-password"; 106 type = types.enum ["yes" "without-password" "forced-commands-only" "no"]; 107 description = '' 108 Whether the root user can login using ssh. 109 ''; 110 }; 111 112 gatewayPorts = mkOption { 113 type = types.str; 114 default = "no"; 115 description = '' 116 Specifies whether remote hosts are allowed to connect to 117 ports forwarded for the client. See 118 <citerefentry><refentrytitle>sshd_config</refentrytitle> 119 <manvolnum>5</manvolnum></citerefentry>. 120 ''; 121 }; 122 123 ports = mkOption { 124 type = types.listOf types.int; 125 default = [22]; 126 description = '' 127 Specifies on which ports the SSH daemon listens. 128 ''; 129 }; 130 131 listenAddresses = mkOption { 132 type = types.listOf types.optionSet; 133 default = []; 134 example = [ { addr = "192.168.3.1"; port = 22; } { addr = "0.0.0.0"; port = 64022; } ]; 135 description = '' 136 List of addresses and ports to listen on (ListenAddress directive 137 in config). If port is not specified for address sshd will listen 138 on all ports specified by <literal>ports</literal> option. 139 NOTE: this will override default listening on all local addresses and port 22. 140 NOTE: setting this option won't automatically enable given ports 141 in firewall configuration. 142 ''; 143 options = { 144 addr = mkOption { 145 type = types.nullOr types.str; 146 default = null; 147 description = '' 148 Host, IPv4 or IPv6 address to listen to. 149 ''; 150 }; 151 port = mkOption { 152 type = types.nullOr types.int; 153 default = null; 154 description = '' 155 Port to listen to. 156 ''; 157 }; 158 }; 159 }; 160 161 passwordAuthentication = mkOption { 162 type = types.bool; 163 default = true; 164 description = '' 165 Specifies whether password authentication is allowed. 166 ''; 167 }; 168 169 challengeResponseAuthentication = mkOption { 170 type = types.bool; 171 default = true; 172 description = '' 173 Specifies whether challenge/response authentication is allowed. 174 ''; 175 }; 176 177 hostKeys = mkOption { 178 type = types.listOf types.attrs; 179 default = 180 [ { type = "rsa"; bits = 4096; path = "/etc/ssh/ssh_host_rsa_key"; } 181 { type = "ed25519"; path = "/etc/ssh/ssh_host_ed25519_key"; } 182 ] ++ optionals supportOldHostKeys 183 [ { type = "dsa"; path = "/etc/ssh/ssh_host_dsa_key"; } 184 { type = "ecdsa"; bits = 521; path = "/etc/ssh/ssh_host_ecdsa_key"; } 185 ]; 186 description = '' 187 NixOS can automatically generate SSH host keys. This option 188 specifies the path, type and size of each key. See 189 <citerefentry><refentrytitle>ssh-keygen</refentrytitle> 190 <manvolnum>1</manvolnum></citerefentry> for supported types 191 and sizes. 192 ''; 193 }; 194 195 authorizedKeysFiles = mkOption { 196 type = types.listOf types.str; 197 default = []; 198 description = "Files from which authorized keys are read."; 199 }; 200 201 extraConfig = mkOption { 202 type = types.lines; 203 default = ""; 204 description = "Verbatim contents of <filename>sshd_config</filename>."; 205 }; 206 207 moduliFile = mkOption { 208 example = "services.openssh.moduliFile = /etc/my-local-ssh-moduli;"; 209 type = types.path; 210 description = '' 211 Path to <literal>moduli</literal> file to install in 212 <literal>/etc/ssh/moduli</literal>. If this option is unset, then 213 the <literal>moduli</literal> file shipped with OpenSSH will be used. 214 ''; 215 }; 216 217 }; 218 219 users.users = mkOption { 220 options = [ userOptions ]; 221 }; 222 223 }; 224 225 226 ###### implementation 227 228 config = mkIf cfg.enable { 229 230 users.extraUsers.sshd = 231 { isSystemUser = true; 232 description = "SSH privilege separation user"; 233 }; 234 235 services.openssh.moduliFile = mkDefault "${cfgc.package}/etc/ssh/moduli"; 236 237 environment.etc = authKeysFiles // 238 { "ssh/moduli".source = cfg.moduliFile; }; 239 240 systemd = 241 let 242 service = 243 { description = "SSH Daemon"; 244 245 wantedBy = optional (!cfg.startWhenNeeded) "multi-user.target"; 246 247 stopIfChanged = false; 248 249 path = [ cfgc.package pkgs.gawk ]; 250 251 environment.LD_LIBRARY_PATH = nssModulesPath; 252 253 preStart = 254 '' 255 mkdir -m 0755 -p /etc/ssh 256 257 ${flip concatMapStrings cfg.hostKeys (k: '' 258 if ! [ -f "${k.path}" ]; then 259 ssh-keygen -t "${k.type}" ${if k ? bits then "-b ${toString k.bits}" else ""} -f "${k.path}" -N "" 260 fi 261 '')} 262 ''; 263 264 serviceConfig = 265 { ExecStart = 266 (optionalString cfg.startWhenNeeded "-") + 267 "${cfgc.package}/bin/sshd " + (optionalString cfg.startWhenNeeded "-i ") + 268 "-f ${pkgs.writeText "sshd_config" cfg.extraConfig}"; 269 KillMode = "process"; 270 } // (if cfg.startWhenNeeded then { 271 StandardInput = "socket"; 272 } else { 273 Restart = "always"; 274 Type = "forking"; 275 PIDFile = "/run/sshd.pid"; 276 }); 277 }; 278 in 279 280 if cfg.startWhenNeeded then { 281 282 sockets.sshd = 283 { description = "SSH Socket"; 284 wantedBy = [ "sockets.target" ]; 285 socketConfig.ListenStream = cfg.ports; 286 socketConfig.Accept = true; 287 }; 288 289 services."sshd@" = service; 290 291 } else { 292 293 services.sshd = service; 294 295 }; 296 297 networking.firewall.allowedTCPPorts = cfg.ports; 298 299 security.pam.services.sshd = 300 { startSession = true; 301 showMotd = true; 302 unixAuth = cfg.passwordAuthentication; 303 }; 304 305 services.openssh.authorizedKeysFiles = 306 [ ".ssh/authorized_keys" ".ssh/authorized_keys2" "/etc/ssh/authorized_keys.d/%u" ]; 307 308 services.openssh.extraConfig = mkOrder 0 309 '' 310 PidFile /run/sshd.pid 311 312 Protocol 2 313 314 UsePAM yes 315 316 UsePrivilegeSeparation sandbox 317 318 AddressFamily ${if config.networking.enableIPv6 then "any" else "inet"} 319 ${concatMapStrings (port: '' 320 Port ${toString port} 321 '') cfg.ports} 322 323 ${concatMapStrings ({ port, addr, ... }: '' 324 ListenAddress ${addr}${if port != null then ":" + toString port else ""} 325 '') cfg.listenAddresses} 326 327 ${optionalString cfgc.setXAuthLocation '' 328 XAuthLocation ${pkgs.xorg.xauth}/bin/xauth 329 ''} 330 331 ${if cfg.forwardX11 then '' 332 X11Forwarding yes 333 '' else '' 334 X11Forwarding no 335 ''} 336 337 ${optionalString cfg.allowSFTP '' 338 Subsystem sftp ${cfgc.package}/libexec/sftp-server 339 ''} 340 341 PermitRootLogin ${cfg.permitRootLogin} 342 GatewayPorts ${cfg.gatewayPorts} 343 PasswordAuthentication ${if cfg.passwordAuthentication then "yes" else "no"} 344 ChallengeResponseAuthentication ${if cfg.challengeResponseAuthentication then "yes" else "no"} 345 346 PrintMotd no # handled by pam_motd 347 348 AuthorizedKeysFile ${toString cfg.authorizedKeysFiles} 349 350 ${flip concatMapStrings cfg.hostKeys (k: '' 351 HostKey ${k.path} 352 '')} 353 354 # Allow DSA client keys for now. (These were deprecated 355 # in OpenSSH 7.0.) 356 PubkeyAcceptedKeyTypes +ssh-dss 357 358 # Re-enable DSA host keys for now. 359 ${optionalString supportOldHostKeys '' 360 HostKeyAlgorithms +ssh-dss 361 ''} 362 ''; 363 364 assertions = [{ assertion = if cfg.forwardX11 then cfgc.setXAuthLocation else true; 365 message = "cannot enable X11 forwarding without setting xauth location";}] 366 ++ flip map cfg.listenAddresses ({ addr, port, ... }: { 367 assertion = addr != null; 368 message = "addr must be specified in each listenAddresses entry"; 369 }); 370 371 }; 372 373}