at 22.05-pre 11 kB view raw
1# Global configuration for the SSH client. 2 3{ config, lib, pkgs, ... }: 4 5with lib; 6 7let 8 9 cfg = config.programs.ssh; 10 11 askPassword = cfg.askPassword; 12 13 askPasswordWrapper = pkgs.writeScript "ssh-askpass-wrapper" 14 '' 15 #! ${pkgs.runtimeShell} -e 16 export DISPLAY="$(systemctl --user show-environment | ${pkgs.gnused}/bin/sed 's/^DISPLAY=\(.*\)/\1/; t; d')" 17 exec ${askPassword} "$@" 18 ''; 19 20 knownHosts = map (h: getAttr h cfg.knownHosts) (attrNames cfg.knownHosts); 21 22 knownHostsText = (flip (concatMapStringsSep "\n") knownHosts 23 (h: assert h.hostNames != []; 24 optionalString h.certAuthority "@cert-authority " + concatStringsSep "," h.hostNames + " " 25 + (if h.publicKey != null then h.publicKey else readFile h.publicKeyFile) 26 )) + "\n"; 27 28in 29{ 30 ###### interface 31 32 options = { 33 34 programs.ssh = { 35 36 askPassword = mkOption { 37 type = types.str; 38 default = "${pkgs.x11_ssh_askpass}/libexec/x11-ssh-askpass"; 39 defaultText = literalExpression ''"''${pkgs.x11_ssh_askpass}/libexec/x11-ssh-askpass"''; 40 description = "Program used by SSH to ask for passwords."; 41 }; 42 43 forwardX11 = mkOption { 44 type = types.bool; 45 default = false; 46 description = '' 47 Whether to request X11 forwarding on outgoing connections by default. 48 This is useful for running graphical programs on the remote machine and have them display to your local X11 server. 49 Historically, this value has depended on the value used by the local sshd daemon, but there really isn't a relation between the two. 50 Note: there are some security risks to forwarding an X11 connection. 51 NixOS's X server is built with the SECURITY extension, which prevents some obvious attacks. 52 To enable or disable forwarding on a per-connection basis, see the -X and -x options to ssh. 53 The -Y option to ssh enables trusted forwarding, which bypasses the SECURITY extension. 54 ''; 55 }; 56 57 setXAuthLocation = mkOption { 58 type = types.bool; 59 description = '' 60 Whether to set the path to <command>xauth</command> for X11-forwarded connections. 61 This causes a dependency on X11 packages. 62 ''; 63 }; 64 65 pubkeyAcceptedKeyTypes = mkOption { 66 type = types.listOf types.str; 67 default = []; 68 example = [ "ssh-ed25519" "ssh-rsa" ]; 69 description = '' 70 Specifies the key types that will be used for public key authentication. 71 ''; 72 }; 73 74 hostKeyAlgorithms = mkOption { 75 type = types.listOf types.str; 76 default = []; 77 example = [ "ssh-ed25519" "ssh-rsa" ]; 78 description = '' 79 Specifies the host key algorithms that the client wants to use in order of preference. 80 ''; 81 }; 82 83 extraConfig = mkOption { 84 type = types.lines; 85 default = ""; 86 description = '' 87 Extra configuration text prepended to <filename>ssh_config</filename>. Other generated 88 options will be added after a <code>Host *</code> pattern. 89 See <citerefentry><refentrytitle>ssh_config</refentrytitle><manvolnum>5</manvolnum></citerefentry> 90 for help. 91 ''; 92 }; 93 94 startAgent = mkOption { 95 type = types.bool; 96 default = false; 97 description = '' 98 Whether to start the OpenSSH agent when you log in. The OpenSSH agent 99 remembers private keys for you so that you don't have to type in 100 passphrases every time you make an SSH connection. Use 101 <command>ssh-add</command> to add a key to the agent. 102 ''; 103 }; 104 105 agentTimeout = mkOption { 106 type = types.nullOr types.str; 107 default = null; 108 example = "1h"; 109 description = '' 110 How long to keep the private keys in memory. Use null to keep them forever. 111 ''; 112 }; 113 114 agentPKCS11Whitelist = mkOption { 115 type = types.nullOr types.str; 116 default = null; 117 example = literalExpression ''"''${pkgs.opensc}/lib/opensc-pkcs11.so"''; 118 description = '' 119 A pattern-list of acceptable paths for PKCS#11 shared libraries 120 that may be used with the -s option to ssh-add. 121 ''; 122 }; 123 124 package = mkOption { 125 type = types.package; 126 default = pkgs.openssh; 127 defaultText = literalExpression "pkgs.openssh"; 128 description = '' 129 The package used for the openssh client and daemon. 130 ''; 131 }; 132 133 knownHosts = mkOption { 134 default = {}; 135 type = types.attrsOf (types.submodule ({ name, ... }: { 136 options = { 137 certAuthority = mkOption { 138 type = types.bool; 139 default = false; 140 description = '' 141 This public key is an SSH certificate authority, rather than an 142 individual host's key. 143 ''; 144 }; 145 hostNames = mkOption { 146 type = types.listOf types.str; 147 default = []; 148 description = '' 149 A list of host names and/or IP numbers used for accessing 150 the host's ssh service. 151 ''; 152 }; 153 publicKey = mkOption { 154 default = null; 155 type = types.nullOr types.str; 156 example = "ecdsa-sha2-nistp521 AAAAE2VjZHN...UEPg=="; 157 description = '' 158 The public key data for the host. You can fetch a public key 159 from a running SSH server with the <command>ssh-keyscan</command> 160 command. The public key should not include any host names, only 161 the key type and the key itself. 162 ''; 163 }; 164 publicKeyFile = mkOption { 165 default = null; 166 type = types.nullOr types.path; 167 description = '' 168 The path to the public key file for the host. The public 169 key file is read at build time and saved in the Nix store. 170 You can fetch a public key file from a running SSH server 171 with the <command>ssh-keyscan</command> command. The content 172 of the file should follow the same format as described for 173 the <literal>publicKey</literal> option. 174 ''; 175 }; 176 }; 177 config = { 178 hostNames = mkDefault [ name ]; 179 }; 180 })); 181 description = '' 182 The set of system-wide known SSH hosts. 183 ''; 184 example = literalExpression '' 185 { 186 myhost = { 187 hostNames = [ "myhost" "myhost.mydomain.com" "10.10.1.4" ]; 188 publicKeyFile = ./pubkeys/myhost_ssh_host_dsa_key.pub; 189 }; 190 myhost2 = { 191 hostNames = [ "myhost2" ]; 192 publicKeyFile = ./pubkeys/myhost2_ssh_host_dsa_key.pub; 193 }; 194 } 195 ''; 196 }; 197 198 kexAlgorithms = mkOption { 199 type = types.nullOr (types.listOf types.str); 200 default = null; 201 example = [ "curve25519-sha256@libssh.org" "diffie-hellman-group-exchange-sha256" ]; 202 description = '' 203 Specifies the available KEX (Key Exchange) algorithms. 204 ''; 205 }; 206 207 ciphers = mkOption { 208 type = types.nullOr (types.listOf types.str); 209 default = null; 210 example = [ "chacha20-poly1305@openssh.com" "aes256-gcm@openssh.com" ]; 211 description = '' 212 Specifies the ciphers allowed and their order of preference. 213 ''; 214 }; 215 216 macs = mkOption { 217 type = types.nullOr (types.listOf types.str); 218 default = null; 219 example = [ "hmac-sha2-512-etm@openssh.com" "hmac-sha1" ]; 220 description = '' 221 Specifies the MAC (message authentication code) algorithms in order of preference. The MAC algorithm is used 222 for data integrity protection. 223 ''; 224 }; 225 }; 226 227 }; 228 229 config = { 230 231 programs.ssh.setXAuthLocation = 232 mkDefault (config.services.xserver.enable || config.programs.ssh.forwardX11 || config.services.openssh.forwardX11); 233 234 assertions = 235 [ { assertion = cfg.forwardX11 -> cfg.setXAuthLocation; 236 message = "cannot enable X11 forwarding without setting XAuth location"; 237 } 238 ] ++ flip mapAttrsToList cfg.knownHosts (name: data: { 239 assertion = (data.publicKey == null && data.publicKeyFile != null) || 240 (data.publicKey != null && data.publicKeyFile == null); 241 message = "knownHost ${name} must contain either a publicKey or publicKeyFile"; 242 }); 243 244 # SSH configuration. Slight duplication of the sshd_config 245 # generation in the sshd service. 246 environment.etc."ssh/ssh_config".text = 247 '' 248 # Custom options from `extraConfig`, to override generated options 249 ${cfg.extraConfig} 250 251 # Generated options from other settings 252 Host * 253 AddressFamily ${if config.networking.enableIPv6 then "any" else "inet"} 254 255 ${optionalString cfg.setXAuthLocation '' 256 XAuthLocation ${pkgs.xorg.xauth}/bin/xauth 257 ''} 258 259 ForwardX11 ${if cfg.forwardX11 then "yes" else "no"} 260 261 ${optionalString (cfg.pubkeyAcceptedKeyTypes != []) "PubkeyAcceptedKeyTypes ${concatStringsSep "," cfg.pubkeyAcceptedKeyTypes}"} 262 ${optionalString (cfg.hostKeyAlgorithms != []) "HostKeyAlgorithms ${concatStringsSep "," cfg.hostKeyAlgorithms}"} 263 ${optionalString (cfg.kexAlgorithms != null) "KexAlgorithms ${concatStringsSep "," cfg.kexAlgorithms}"} 264 ${optionalString (cfg.ciphers != null) "Ciphers ${concatStringsSep "," cfg.ciphers}"} 265 ${optionalString (cfg.macs != null) "MACs ${concatStringsSep "," cfg.macs}"} 266 ''; 267 268 environment.etc."ssh/ssh_known_hosts".text = knownHostsText; 269 270 # FIXME: this should really be socket-activated for über-awesomeness. 271 systemd.user.services.ssh-agent = mkIf cfg.startAgent 272 { description = "SSH Agent"; 273 wantedBy = [ "default.target" ]; 274 unitConfig.ConditionUser = "!@system"; 275 serviceConfig = 276 { ExecStartPre = "${pkgs.coreutils}/bin/rm -f %t/ssh-agent"; 277 ExecStart = 278 "${cfg.package}/bin/ssh-agent " + 279 optionalString (cfg.agentTimeout != null) ("-t ${cfg.agentTimeout} ") + 280 optionalString (cfg.agentPKCS11Whitelist != null) ("-P ${cfg.agentPKCS11Whitelist} ") + 281 "-a %t/ssh-agent"; 282 StandardOutput = "null"; 283 Type = "forking"; 284 Restart = "on-failure"; 285 SuccessExitStatus = "0 2"; 286 }; 287 # Allow ssh-agent to ask for confirmation. This requires the 288 # unit to know about the user's $DISPLAY (via ‘systemctl 289 # import-environment’). 290 environment.SSH_ASKPASS = optionalString config.services.xserver.enable askPasswordWrapper; 291 environment.DISPLAY = "fake"; # required to make ssh-agent start $SSH_ASKPASS 292 }; 293 294 environment.extraInit = optionalString cfg.startAgent 295 '' 296 if [ -z "$SSH_AUTH_SOCK" -a -n "$XDG_RUNTIME_DIR" ]; then 297 export SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/ssh-agent" 298 fi 299 ''; 300 301 environment.variables.SSH_ASKPASS = optionalString config.services.xserver.enable askPassword; 302 303 }; 304}