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