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