at 18.03-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 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 description = '' 202 NixOS can automatically generate SSH host keys. This option 203 specifies the path, type and size of each key. See 204 <citerefentry><refentrytitle>ssh-keygen</refentrytitle> 205 <manvolnum>1</manvolnum></citerefentry> for supported types 206 and sizes. 207 ''; 208 }; 209 210 authorizedKeysFiles = mkOption { 211 type = types.listOf types.str; 212 default = []; 213 description = "Files from which authorized keys are read."; 214 }; 215 216 extraConfig = mkOption { 217 type = types.lines; 218 default = ""; 219 description = "Verbatim contents of <filename>sshd_config</filename>."; 220 }; 221 222 moduliFile = mkOption { 223 example = "/etc/my-local-ssh-moduli;"; 224 type = types.path; 225 description = '' 226 Path to <literal>moduli</literal> file to install in 227 <literal>/etc/ssh/moduli</literal>. If this option is unset, then 228 the <literal>moduli</literal> file shipped with OpenSSH will be used. 229 ''; 230 }; 231 232 }; 233 234 users.users = mkOption { 235 options = [ userOptions ]; 236 }; 237 238 }; 239 240 241 ###### implementation 242 243 config = mkIf cfg.enable { 244 245 users.extraUsers.sshd = 246 { isSystemUser = true; 247 description = "SSH privilege separation user"; 248 }; 249 250 services.openssh.moduliFile = mkDefault "${cfgc.package}/etc/ssh/moduli"; 251 252 environment.etc = authKeysFiles // 253 { "ssh/moduli".source = cfg.moduliFile; }; 254 255 systemd = 256 let 257 service = 258 { description = "SSH Daemon"; 259 wantedBy = optional (!cfg.startWhenNeeded) "multi-user.target"; 260 after = [ "network.target" ]; 261 stopIfChanged = false; 262 path = [ cfgc.package pkgs.gawk ]; 263 environment.LD_LIBRARY_PATH = nssModulesPath; 264 265 preStart = 266 '' 267 # Make sure we don't write to stdout, since in case of 268 # socket activation, it goes to the remote side (#19589). 269 exec >&2 270 271 mkdir -m 0755 -p /etc/ssh 272 273 ${flip concatMapStrings cfg.hostKeys (k: '' 274 if ! [ -f "${k.path}" ]; then 275 ssh-keygen -t "${k.type}" ${if k ? bits then "-b ${toString k.bits}" else ""} -f "${k.path}" -N "" 276 fi 277 '')} 278 ''; 279 280 serviceConfig = 281 { ExecStart = 282 (optionalString cfg.startWhenNeeded "-") + 283 "${cfgc.package}/bin/sshd " + (optionalString cfg.startWhenNeeded "-i ") + 284 "-f ${pkgs.writeText "sshd_config" cfg.extraConfig}"; 285 KillMode = "process"; 286 } // (if cfg.startWhenNeeded then { 287 StandardInput = "socket"; 288 StandardError = "journal"; 289 } else { 290 Restart = "always"; 291 Type = "simple"; 292 }); 293 }; 294 in 295 296 if cfg.startWhenNeeded then { 297 298 sockets.sshd = 299 { description = "SSH Socket"; 300 wantedBy = [ "sockets.target" ]; 301 socketConfig.ListenStream = cfg.ports; 302 socketConfig.Accept = true; 303 }; 304 305 services."sshd@" = service; 306 307 } else { 308 309 services.sshd = service; 310 311 }; 312 313 networking.firewall.allowedTCPPorts = if cfg.openFirewall then cfg.ports else []; 314 315 security.pam.services.sshd = 316 { startSession = true; 317 showMotd = true; 318 unixAuth = cfg.passwordAuthentication; 319 }; 320 321 services.openssh.authorizedKeysFiles = 322 [ ".ssh/authorized_keys" ".ssh/authorized_keys2" "/etc/ssh/authorized_keys.d/%u" ]; 323 324 services.openssh.extraConfig = mkOrder 0 325 '' 326 Protocol 2 327 328 UsePAM yes 329 330 AddressFamily ${if config.networking.enableIPv6 then "any" else "inet"} 331 ${concatMapStrings (port: '' 332 Port ${toString port} 333 '') cfg.ports} 334 335 ${concatMapStrings ({ port, addr, ... }: '' 336 ListenAddress ${addr}${if port != null then ":" + toString port else ""} 337 '') cfg.listenAddresses} 338 339 ${optionalString cfgc.setXAuthLocation '' 340 XAuthLocation ${pkgs.xorg.xauth}/bin/xauth 341 ''} 342 343 ${if cfg.forwardX11 then '' 344 X11Forwarding yes 345 '' else '' 346 X11Forwarding no 347 ''} 348 349 ${optionalString cfg.allowSFTP '' 350 Subsystem sftp ${cfgc.package}/libexec/sftp-server ${concatStringsSep " " cfg.sftpFlags} 351 ''} 352 353 PermitRootLogin ${cfg.permitRootLogin} 354 GatewayPorts ${cfg.gatewayPorts} 355 PasswordAuthentication ${if cfg.passwordAuthentication then "yes" else "no"} 356 ChallengeResponseAuthentication ${if cfg.challengeResponseAuthentication then "yes" else "no"} 357 358 PrintMotd no # handled by pam_motd 359 360 AuthorizedKeysFile ${toString cfg.authorizedKeysFiles} 361 362 ${flip concatMapStrings cfg.hostKeys (k: '' 363 HostKey ${k.path} 364 '')} 365 366 ### Recommended settings from both: 367 # https://stribika.github.io/2015/01/04/secure-secure-shell.html 368 # and 369 # https://wiki.mozilla.org/Security/Guidelines/OpenSSH#Modern_.28OpenSSH_6.7.2B.29 370 371 KexAlgorithms curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256 372 Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr 373 MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,umac-128@openssh.com 374 375 # LogLevel VERBOSE logs user's key fingerprint on login. 376 # Needed to have a clear audit track of which key was used to log in. 377 LogLevel VERBOSE 378 ''; 379 380 assertions = [{ assertion = if cfg.forwardX11 then cfgc.setXAuthLocation else true; 381 message = "cannot enable X11 forwarding without setting xauth location";}] 382 ++ flip map cfg.listenAddresses ({ addr, port, ... }: { 383 assertion = addr != null; 384 message = "addr must be specified in each listenAddresses entry"; 385 }); 386 387 }; 388 389}