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