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