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