at master 16 kB view raw
1# Global configuration for the SSH client. 2 3{ 4 config, 5 lib, 6 pkgs, 7 ... 8}: 9 10let 11 12 cfg = config.programs.ssh; 13 14 askPasswordWrapper = pkgs.writeScript "ssh-askpass-wrapper" '' 15 #! ${pkgs.runtimeShell} -e 16 eval export $(systemctl --user show-environment | ${lib.getExe pkgs.gnugrep} -E '^(DISPLAY|WAYLAND_DISPLAY|XAUTHORITY)=') 17 exec ${cfg.askPassword} "$@" 18 ''; 19 20 knownHosts = builtins.attrValues cfg.knownHosts; 21 22 knownHostsText = 23 (lib.flip (lib.concatMapStringsSep "\n") knownHosts ( 24 h: 25 assert h.hostNames != [ ]; 26 lib.optionalString h.certAuthority "@cert-authority " 27 + builtins.concatStringsSep "," h.hostNames 28 + " " 29 + (if h.publicKey != null then h.publicKey else builtins.readFile h.publicKeyFile) 30 )) 31 + "\n"; 32 33 knownHostsFiles = [ 34 "/etc/ssh/ssh_known_hosts" 35 ] 36 ++ builtins.map pkgs.copyPathToStore cfg.knownHostsFiles; 37 38in 39{ 40 ###### interface 41 42 options = { 43 44 programs.ssh = { 45 46 enableAskPassword = lib.mkOption { 47 type = lib.types.bool; 48 default = config.services.xserver.enable; 49 defaultText = lib.literalExpression "config.services.xserver.enable"; 50 description = "Whether to configure SSH_ASKPASS in the environment."; 51 }; 52 53 systemd-ssh-proxy.enable = lib.mkOption { 54 type = lib.types.bool; 55 default = true; 56 description = '' 57 Whether to enable systemd's ssh proxy plugin. 58 See {manpage}`systemd-ssh-proxy(1)`. 59 ''; 60 }; 61 62 askPassword = lib.mkOption { 63 type = lib.types.str; 64 default = "${pkgs.x11_ssh_askpass}/libexec/x11-ssh-askpass"; 65 defaultText = lib.literalExpression ''"''${pkgs.x11_ssh_askpass}/libexec/x11-ssh-askpass"''; 66 description = "Program used by SSH to ask for passwords."; 67 }; 68 69 forwardX11 = lib.mkOption { 70 type = with lib.types; nullOr bool; 71 default = false; 72 description = '' 73 Whether to request X11 forwarding on outgoing connections by default. 74 If set to null, the option is not set at all. 75 This is useful for running graphical programs on the remote machine and have them display to your local X11 server. 76 Historically, this value has depended on the value used by the local sshd daemon, but there really isn't a relation between the two. 77 Note: there are some security risks to forwarding an X11 connection. 78 NixOS's X server is built with the SECURITY extension, which prevents some obvious attacks. 79 To enable or disable forwarding on a per-connection basis, see the -X and -x options to ssh. 80 The -Y option to ssh enables trusted forwarding, which bypasses the SECURITY extension. 81 ''; 82 }; 83 84 setXAuthLocation = lib.mkOption { 85 type = lib.types.bool; 86 description = '' 87 Whether to set the path to {command}`xauth` for X11-forwarded connections. 88 This causes a dependency on X11 packages. 89 ''; 90 }; 91 92 pubkeyAcceptedKeyTypes = lib.mkOption { 93 type = lib.types.listOf lib.types.str; 94 default = [ ]; 95 example = [ 96 "ssh-ed25519" 97 "ssh-rsa" 98 ]; 99 description = '' 100 Specifies the key lib.types that will be used for public key authentication. 101 ''; 102 }; 103 104 hostKeyAlgorithms = lib.mkOption { 105 type = lib.types.listOf lib.types.str; 106 default = [ ]; 107 example = [ 108 "ssh-ed25519" 109 "ssh-rsa" 110 ]; 111 description = '' 112 Specifies the host key algorithms that the client wants to use in order of preference. 113 ''; 114 }; 115 116 extraConfig = lib.mkOption { 117 type = lib.types.lines; 118 default = ""; 119 description = '' 120 Extra configuration text prepended to {file}`ssh_config`. Other generated 121 options will be added after a `Host *` pattern. 122 See {manpage}`ssh_config(5)` 123 for help. 124 ''; 125 }; 126 127 startAgent = lib.mkOption { 128 type = lib.types.bool; 129 default = false; 130 description = '' 131 Whether to start the OpenSSH agent when you log in. The OpenSSH agent 132 remembers private keys for you so that you don't have to type in 133 passphrases every time you make an SSH connection. Use 134 {command}`ssh-add` to add a key to the agent. 135 ''; 136 }; 137 138 agentTimeout = lib.mkOption { 139 type = lib.types.nullOr lib.types.str; 140 default = null; 141 example = "1h"; 142 description = '' 143 How long to keep the private keys in memory. Use null to keep them forever. 144 ''; 145 }; 146 147 agentPKCS11Whitelist = lib.mkOption { 148 type = lib.types.nullOr lib.types.str; 149 default = null; 150 example = lib.literalExpression ''"''${pkgs.opensc}/lib/opensc-pkcs11.so"''; 151 description = '' 152 A pattern-list of acceptable paths for PKCS#11 shared libraries 153 that may be used with the -s option to ssh-add. 154 ''; 155 }; 156 157 package = lib.mkPackageOption pkgs "openssh" { }; 158 159 knownHosts = lib.mkOption { 160 default = { }; 161 type = lib.types.attrsOf ( 162 lib.types.submodule ( 163 { 164 name, 165 config, 166 options, 167 ... 168 }: 169 { 170 options = { 171 certAuthority = lib.mkOption { 172 type = lib.types.bool; 173 default = false; 174 description = '' 175 This public key is an SSH certificate authority, rather than an 176 individual host's key. 177 ''; 178 }; 179 hostNames = lib.mkOption { 180 type = lib.types.listOf lib.types.str; 181 default = [ name ] ++ config.extraHostNames; 182 defaultText = lib.literalExpression "[ ${name} ] ++ config.${options.extraHostNames}"; 183 description = '' 184 A list of host names and/or IP numbers used for accessing 185 the host's ssh service. This list includes the name of the 186 containing `knownHosts` attribute by default 187 for convenience. If you wish to configure multiple host keys 188 for the same host use multiple `knownHosts` 189 entries with different attribute names and the same 190 `hostNames` list. 191 ''; 192 }; 193 extraHostNames = lib.mkOption { 194 type = lib.types.listOf lib.types.str; 195 default = [ ]; 196 description = '' 197 A list of additional host names and/or IP numbers used for 198 accessing the host's ssh service. This list is ignored if 199 `hostNames` is set explicitly. 200 ''; 201 }; 202 publicKey = lib.mkOption { 203 default = null; 204 type = lib.types.nullOr lib.types.str; 205 example = "ecdsa-sha2-nistp521 AAAAE2VjZHN...UEPg=="; 206 description = '' 207 The public key data for the host. You can fetch a public key 208 from a running SSH server with the {command}`ssh-keyscan` 209 command. The public key should not include any host names, only 210 the key type and the key itself. 211 ''; 212 }; 213 publicKeyFile = lib.mkOption { 214 default = null; 215 type = lib.types.nullOr lib.types.path; 216 description = '' 217 The path to the public key file for the host. The public 218 key file is read at build time and saved in the Nix store. 219 You can fetch a public key file from a running SSH server 220 with the {command}`ssh-keyscan` command. The content 221 of the file should follow the same format as described for 222 the `publicKey` option. Only a single key 223 is supported. If a host has multiple keys, use 224 {option}`programs.ssh.knownHostsFiles` instead. 225 ''; 226 }; 227 }; 228 } 229 ) 230 ); 231 description = '' 232 The set of system-wide known SSH hosts. To make simple setups more 233 convenient the name of an attribute in this set is used as a host name 234 for the entry. This behaviour can be disabled by setting 235 `hostNames` explicitly. You can use 236 `extraHostNames` to add additional host names without 237 disabling this default. 238 ''; 239 example = lib.literalExpression '' 240 { 241 myhost = { 242 extraHostNames = [ "myhost.mydomain.com" "10.10.1.4" ]; 243 publicKeyFile = ./pubkeys/myhost_ssh_host_dsa_key.pub; 244 }; 245 "myhost2.net".publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILIRuJ8p1Fi+m6WkHV0KWnRfpM1WxoW8XAS+XvsSKsTK"; 246 "myhost2.net/dsa" = { 247 hostNames = [ "myhost2.net" ]; 248 publicKeyFile = ./pubkeys/myhost2_ssh_host_dsa_key.pub; 249 }; 250 } 251 ''; 252 }; 253 254 knownHostsFiles = lib.mkOption { 255 default = [ ]; 256 type = with lib.types; listOf path; 257 description = '' 258 Files containing SSH host keys to set as global known hosts. 259 `/etc/ssh/ssh_known_hosts` (which is 260 generated by {option}`programs.ssh.knownHosts`) is 261 always included. 262 ''; 263 example = lib.literalExpression '' 264 [ 265 ./known_hosts 266 (writeText "github.keys" ''' 267 github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk= 268 github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg= 269 github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl 270 ''') 271 ] 272 ''; 273 }; 274 275 kexAlgorithms = lib.mkOption { 276 type = lib.types.nullOr (lib.types.listOf lib.types.str); 277 default = null; 278 example = [ 279 "curve25519-sha256@libssh.org" 280 "diffie-hellman-group-exchange-sha256" 281 ]; 282 description = '' 283 Specifies the available KEX (Key Exchange) algorithms. 284 ''; 285 }; 286 287 ciphers = lib.mkOption { 288 type = lib.types.nullOr (lib.types.listOf lib.types.str); 289 default = null; 290 example = [ 291 "chacha20-poly1305@openssh.com" 292 "aes256-gcm@openssh.com" 293 ]; 294 description = '' 295 Specifies the ciphers allowed and their order of preference. 296 ''; 297 }; 298 299 macs = lib.mkOption { 300 type = lib.types.nullOr (lib.types.listOf lib.types.str); 301 default = null; 302 example = [ 303 "hmac-sha2-512-etm@openssh.com" 304 "hmac-sha1" 305 ]; 306 description = '' 307 Specifies the MAC (message authentication code) algorithms in order of preference. The MAC algorithm is used 308 for data integrity protection. 309 ''; 310 }; 311 }; 312 313 }; 314 315 config = { 316 317 programs.ssh.setXAuthLocation = lib.mkDefault ( 318 config.services.xserver.enable 319 || config.programs.ssh.forwardX11 == true 320 || config.services.openssh.settings.X11Forwarding 321 ); 322 323 assertions = [ 324 { 325 assertion = cfg.forwardX11 == true -> cfg.setXAuthLocation; 326 message = "cannot enable X11 forwarding without setting XAuth location"; 327 } 328 ] 329 ++ lib.flip lib.mapAttrsToList cfg.knownHosts ( 330 name: data: { 331 assertion = 332 (data.publicKey == null && data.publicKeyFile != null) 333 || (data.publicKey != null && data.publicKeyFile == null); 334 message = "knownHost ${name} must contain either a publicKey or publicKeyFile"; 335 } 336 ); 337 338 environment.corePackages = [ cfg.package ]; 339 340 # SSH configuration. Slight duplication of the sshd_config 341 # generation in the sshd service. 342 environment.etc."ssh/ssh_config".text = '' 343 # Custom options from `extraConfig`, to override generated options 344 ${cfg.extraConfig} 345 346 # Generated options from other settings 347 Host * 348 ${lib.optionalString cfg.systemd-ssh-proxy.enable '' 349 # See systemd-ssh-proxy(1) 350 Include ${config.systemd.package}/lib/systemd/ssh_config.d/20-systemd-ssh-proxy.conf 351 ''} 352 353 GlobalKnownHostsFile ${builtins.concatStringsSep " " knownHostsFiles} 354 355 ${lib.optionalString (!config.networking.enableIPv6) "AddressFamily inet"} 356 ${lib.optionalString cfg.setXAuthLocation "XAuthLocation ${pkgs.xorg.xauth}/bin/xauth"} 357 ${lib.optionalString (cfg.forwardX11 != null) 358 "ForwardX11 ${if cfg.forwardX11 then "yes" else "no"}" 359 } 360 361 ${lib.optionalString ( 362 cfg.pubkeyAcceptedKeyTypes != [ ] 363 ) "PubkeyAcceptedKeyTypes ${builtins.concatStringsSep "," cfg.pubkeyAcceptedKeyTypes}"} 364 ${lib.optionalString ( 365 cfg.hostKeyAlgorithms != [ ] 366 ) "HostKeyAlgorithms ${builtins.concatStringsSep "," cfg.hostKeyAlgorithms}"} 367 ${lib.optionalString ( 368 cfg.kexAlgorithms != null 369 ) "KexAlgorithms ${builtins.concatStringsSep "," cfg.kexAlgorithms}"} 370 ${lib.optionalString (cfg.ciphers != null) "Ciphers ${builtins.concatStringsSep "," cfg.ciphers}"} 371 ${lib.optionalString (cfg.macs != null) "MACs ${builtins.concatStringsSep "," cfg.macs}"} 372 ''; 373 374 environment.etc."ssh/ssh_known_hosts".text = knownHostsText; 375 376 # FIXME: this should really be socket-activated for über-awesomeness. 377 systemd.user.services.ssh-agent = lib.mkIf cfg.startAgent { 378 description = "SSH Agent"; 379 wantedBy = [ "default.target" ]; 380 unitConfig.ConditionUser = "!@system"; 381 serviceConfig = { 382 ExecStartPre = "${pkgs.coreutils}/bin/rm -f %t/ssh-agent"; 383 ExecStart = 384 "${cfg.package}/bin/ssh-agent " 385 + lib.optionalString (cfg.agentTimeout != null) ("-t ${cfg.agentTimeout} ") 386 + lib.optionalString (cfg.agentPKCS11Whitelist != null) ("-P ${cfg.agentPKCS11Whitelist} ") 387 + "-a %t/ssh-agent"; 388 StandardOutput = "null"; 389 Type = "forking"; 390 Restart = "on-failure"; 391 SuccessExitStatus = "0 2"; 392 }; 393 # Allow ssh-agent to ask for confirmation. This requires the 394 # unit to know about the user's $DISPLAY (via ‘systemctl 395 # import-environment’). 396 environment.SSH_ASKPASS = lib.optionalString cfg.enableAskPassword askPasswordWrapper; 397 environment.DISPLAY = "fake"; # required to make ssh-agent start $SSH_ASKPASS 398 }; 399 400 environment.extraInit = lib.optionalString cfg.startAgent '' 401 if [ -z "$SSH_AUTH_SOCK" -a -n "$XDG_RUNTIME_DIR" ]; then 402 export SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/ssh-agent" 403 fi 404 ''; 405 406 environment.variables.SSH_ASKPASS = lib.optionalString cfg.enableAskPassword cfg.askPassword; 407 408 }; 409}