at 17.09-beta 12 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.extraUsers (n: u: 53 length u.openssh.authorizedKeys.keys != 0 || length u.openssh.authorizedKeys.keyFiles != 0 54 )); 55 in listToAttrs (map mkAuthKeyFile usersWithKeys); 56 57 supportOldHostKeys = !versionAtLeast config.system.stateVersion "15.07"; 58 59in 60 61{ 62 63 ###### interface 64 65 options = { 66 67 services.openssh = { 68 69 enable = mkOption { 70 type = types.bool; 71 default = false; 72 description = '' 73 Whether to enable the OpenSSH secure shell daemon, which 74 allows secure remote logins. 75 ''; 76 }; 77 78 startWhenNeeded = mkOption { 79 type = types.bool; 80 default = false; 81 description = '' 82 If set, <command>sshd</command> is socket-activated; that 83 is, instead of having it permanently running as a daemon, 84 systemd will start an instance for each incoming connection. 85 ''; 86 }; 87 88 forwardX11 = mkOption { 89 type = types.bool; 90 default = false; 91 description = '' 92 Whether to allow X11 connections to be forwarded. 93 ''; 94 }; 95 96 allowSFTP = mkOption { 97 type = types.bool; 98 default = true; 99 description = '' 100 Whether to enable the SFTP subsystem in the SSH daemon. This 101 enables the use of commands such as <command>sftp</command> and 102 <command>sshfs</command>. 103 ''; 104 }; 105 106 permitRootLogin = mkOption { 107 default = "prohibit-password"; 108 type = types.enum ["yes" "without-password" "prohibit-password" "forced-commands-only" "no"]; 109 description = '' 110 Whether the root user can login using ssh. 111 ''; 112 }; 113 114 gatewayPorts = mkOption { 115 type = types.str; 116 default = "no"; 117 description = '' 118 Specifies whether remote hosts are allowed to connect to 119 ports forwarded for the client. See 120 <citerefentry><refentrytitle>sshd_config</refentrytitle> 121 <manvolnum>5</manvolnum></citerefentry>. 122 ''; 123 }; 124 125 ports = mkOption { 126 type = types.listOf types.int; 127 default = [22]; 128 description = '' 129 Specifies on which ports the SSH daemon listens. 130 ''; 131 }; 132 133 listenAddresses = mkOption { 134 type = with types; listOf (submodule { 135 options = { 136 addr = mkOption { 137 type = types.nullOr types.str; 138 default = null; 139 description = '' 140 Host, IPv4 or IPv6 address to listen to. 141 ''; 142 }; 143 port = mkOption { 144 type = types.nullOr types.int; 145 default = null; 146 description = '' 147 Port to listen to. 148 ''; 149 }; 150 }; 151 }); 152 default = []; 153 example = [ { addr = "192.168.3.1"; port = 22; } { addr = "0.0.0.0"; port = 64022; } ]; 154 description = '' 155 List of addresses and ports to listen on (ListenAddress directive 156 in config). If port is not specified for address sshd will listen 157 on all ports specified by <literal>ports</literal> option. 158 NOTE: this will override default listening on all local addresses and port 22. 159 NOTE: setting this option won't automatically enable given ports 160 in firewall configuration. 161 ''; 162 }; 163 164 passwordAuthentication = mkOption { 165 type = types.bool; 166 default = true; 167 description = '' 168 Specifies whether password authentication is allowed. 169 ''; 170 }; 171 172 challengeResponseAuthentication = mkOption { 173 type = types.bool; 174 default = true; 175 description = '' 176 Specifies whether challenge/response authentication is allowed. 177 ''; 178 }; 179 180 hostKeys = mkOption { 181 type = types.listOf types.attrs; 182 default = 183 [ { type = "rsa"; bits = 4096; path = "/etc/ssh/ssh_host_rsa_key"; } 184 { type = "ed25519"; path = "/etc/ssh/ssh_host_ed25519_key"; } 185 ] ++ optionals supportOldHostKeys 186 [ { type = "dsa"; path = "/etc/ssh/ssh_host_dsa_key"; } 187 { type = "ecdsa"; bits = 521; path = "/etc/ssh/ssh_host_ecdsa_key"; } 188 ]; 189 description = '' 190 NixOS can automatically generate SSH host keys. This option 191 specifies the path, type and size of each key. See 192 <citerefentry><refentrytitle>ssh-keygen</refentrytitle> 193 <manvolnum>1</manvolnum></citerefentry> for supported types 194 and sizes. 195 ''; 196 }; 197 198 authorizedKeysFiles = mkOption { 199 type = types.listOf types.str; 200 default = []; 201 description = "Files from which authorized keys are read."; 202 }; 203 204 extraConfig = mkOption { 205 type = types.lines; 206 default = ""; 207 description = "Verbatim contents of <filename>sshd_config</filename>."; 208 }; 209 210 moduliFile = mkOption { 211 example = "services.openssh.moduliFile = /etc/my-local-ssh-moduli;"; 212 type = types.path; 213 description = '' 214 Path to <literal>moduli</literal> file to install in 215 <literal>/etc/ssh/moduli</literal>. If this option is unset, then 216 the <literal>moduli</literal> file shipped with OpenSSH will be used. 217 ''; 218 }; 219 220 }; 221 222 users.users = mkOption { 223 options = [ userOptions ]; 224 }; 225 226 }; 227 228 229 ###### implementation 230 231 config = mkIf cfg.enable { 232 233 users.extraUsers.sshd = 234 { isSystemUser = true; 235 description = "SSH privilege separation user"; 236 }; 237 238 services.openssh.moduliFile = mkDefault "${cfgc.package}/etc/ssh/moduli"; 239 240 environment.etc = authKeysFiles // 241 { "ssh/moduli".source = cfg.moduliFile; }; 242 243 systemd = 244 let 245 service = 246 { description = "SSH Daemon"; 247 248 wantedBy = optional (!cfg.startWhenNeeded) "multi-user.target"; 249 250 stopIfChanged = false; 251 252 path = [ cfgc.package pkgs.gawk ]; 253 254 environment.LD_LIBRARY_PATH = nssModulesPath; 255 256 preStart = 257 '' 258 # Make sure we don't write to stdout, since in case of 259 # socket activation, it goes to the remote side (#19589). 260 exec >&2 261 262 mkdir -m 0755 -p /etc/ssh 263 264 ${flip concatMapStrings cfg.hostKeys (k: '' 265 if ! [ -f "${k.path}" ]; then 266 ssh-keygen -t "${k.type}" ${if k ? bits then "-b ${toString k.bits}" else ""} -f "${k.path}" -N "" 267 fi 268 '')} 269 ''; 270 271 serviceConfig = 272 { ExecStart = 273 (optionalString cfg.startWhenNeeded "-") + 274 "${cfgc.package}/bin/sshd " + (optionalString cfg.startWhenNeeded "-i ") + 275 "-f ${pkgs.writeText "sshd_config" cfg.extraConfig}"; 276 KillMode = "process"; 277 } // (if cfg.startWhenNeeded then { 278 StandardInput = "socket"; 279 StandardError = "journal"; 280 } else { 281 Restart = "always"; 282 Type = "simple"; 283 }); 284 }; 285 in 286 287 if cfg.startWhenNeeded then { 288 289 sockets.sshd = 290 { description = "SSH Socket"; 291 wantedBy = [ "sockets.target" ]; 292 socketConfig.ListenStream = cfg.ports; 293 socketConfig.Accept = true; 294 }; 295 296 services."sshd@" = service; 297 298 } else { 299 300 services.sshd = service; 301 302 }; 303 304 networking.firewall.allowedTCPPorts = cfg.ports; 305 306 security.pam.services.sshd = 307 { startSession = true; 308 showMotd = true; 309 unixAuth = cfg.passwordAuthentication; 310 }; 311 312 services.openssh.authorizedKeysFiles = 313 [ ".ssh/authorized_keys" ".ssh/authorized_keys2" "/etc/ssh/authorized_keys.d/%u" ]; 314 315 services.openssh.extraConfig = mkOrder 0 316 '' 317 Protocol 2 318 319 UsePAM yes 320 321 AddressFamily ${if config.networking.enableIPv6 then "any" else "inet"} 322 ${concatMapStrings (port: '' 323 Port ${toString port} 324 '') cfg.ports} 325 326 ${concatMapStrings ({ port, addr, ... }: '' 327 ListenAddress ${addr}${if port != null then ":" + toString port else ""} 328 '') cfg.listenAddresses} 329 330 ${optionalString cfgc.setXAuthLocation '' 331 XAuthLocation ${pkgs.xorg.xauth}/bin/xauth 332 ''} 333 334 ${if cfg.forwardX11 then '' 335 X11Forwarding yes 336 '' else '' 337 X11Forwarding no 338 ''} 339 340 ${optionalString cfg.allowSFTP '' 341 Subsystem sftp ${cfgc.package}/libexec/sftp-server 342 ''} 343 344 PermitRootLogin ${cfg.permitRootLogin} 345 GatewayPorts ${cfg.gatewayPorts} 346 PasswordAuthentication ${if cfg.passwordAuthentication then "yes" else "no"} 347 ChallengeResponseAuthentication ${if cfg.challengeResponseAuthentication then "yes" else "no"} 348 349 PrintMotd no # handled by pam_motd 350 351 AuthorizedKeysFile ${toString cfg.authorizedKeysFiles} 352 353 ${flip concatMapStrings cfg.hostKeys (k: '' 354 HostKey ${k.path} 355 '')} 356 357 # Allow DSA client keys for now. (These were deprecated 358 # in OpenSSH 7.0.) 359 PubkeyAcceptedKeyTypes +ssh-dss 360 361 # Re-enable DSA host keys for now. 362 ${optionalString supportOldHostKeys '' 363 HostKeyAlgorithms +ssh-dss 364 ''} 365 ''; 366 367 assertions = [{ assertion = if cfg.forwardX11 then cfgc.setXAuthLocation else true; 368 message = "cannot enable X11 forwarding without setting xauth location";}] 369 ++ flip map cfg.listenAddresses ({ addr, port, ... }: { 370 assertion = addr != null; 371 message = "addr must be specified in each listenAddresses entry"; 372 }); 373 374 }; 375 376}