1 # Global configuration for the SSH client.
3 { config, lib, pkgs, ... }:
7 cfg = config.programs.ssh;
9 askPasswordWrapper = pkgs.writeScript "ssh-askpass-wrapper"
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} "$@"
18 knownHosts = builtins.attrValues cfg.knownHosts;
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)
26 knownHostsFiles = [ "/etc/ssh/ssh_known_hosts" ]
27 ++ builtins.map pkgs.copyPathToStore cfg.knownHostsFiles;
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.";
44 askPassword = lib.mkOption {
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.";
51 forwardX11 = lib.mkOption {
52 type = with lib.types; nullOr bool;
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.
66 setXAuthLocation = lib.mkOption {
67 type = lib.types.bool;
69 Whether to set the path to {command}`xauth` for X11-forwarded connections.
70 This causes a dependency on X11 packages.
74 pubkeyAcceptedKeyTypes = lib.mkOption {
75 type = lib.types.listOf lib.types.str;
77 example = [ "ssh-ed25519" "ssh-rsa" ];
79 Specifies the key lib.types that will be used for public key authentication.
83 hostKeyAlgorithms = lib.mkOption {
84 type = lib.types.listOf lib.types.str;
86 example = [ "ssh-ed25519" "ssh-rsa" ];
88 Specifies the host key algorithms that the client wants to use in order of preference.
92 extraConfig = lib.mkOption {
93 type = lib.types.lines;
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)`
103 startAgent = lib.mkOption {
104 type = lib.types.bool;
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.
114 agentTimeout = lib.mkOption {
115 type = lib.types.nullOr lib.types.str;
119 How long to keep the private keys in memory. Use null to keep them forever.
123 agentPKCS11Whitelist = lib.mkOption {
124 type = lib.types.nullOr lib.types.str;
126 example = lib.literalExpression ''"''${pkgs.opensc}/lib/opensc-pkcs11.so"'';
128 A pattern-list of acceptable paths for PKCS#11 shared libraries
129 that may be used with the -s option to ssh-add.
133 package = lib.mkPackageOption pkgs "openssh" { };
135 knownHosts = lib.mkOption {
137 type = lib.types.attrsOf (lib.types.submodule ({ name, config, options, ... }: {
139 certAuthority = lib.mkOption {
140 type = lib.types.bool;
143 This public key is an SSH certificate authority, rather than an
144 individual host's key.
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}";
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
161 extraHostNames = lib.mkOption {
162 type = lib.types.listOf lib.types.str;
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.
170 publicKey = lib.mkOption {
172 type = lib.types.nullOr lib.types.str;
173 example = "ecdsa-sha2-nistp521 AAAAE2VjZHN...UEPg==";
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.
181 publicKeyFile = lib.mkOption {
183 type = lib.types.nullOr lib.types.path;
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.
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.
205 example = lib.literalExpression ''
208 extraHostNames = [ "myhost.mydomain.com" "10.10.1.4" ];
209 publicKeyFile = ./pubkeys/myhost_ssh_host_dsa_key.pub;
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;
220 knownHostsFiles = lib.mkOption {
222 type = with lib.types; listOf path;
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
229 example = lib.literalExpression ''
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
241 kexAlgorithms = lib.mkOption {
242 type = lib.types.nullOr (lib.types.listOf lib.types.str);
244 example = [ "curve25519-sha256@libssh.org" "diffie-hellman-group-exchange-sha256" ];
246 Specifies the available KEX (Key Exchange) algorithms.
250 ciphers = lib.mkOption {
251 type = lib.types.nullOr (lib.types.listOf lib.types.str);
253 example = [ "chacha20-poly1305@openssh.com" "aes256-gcm@openssh.com" ];
255 Specifies the ciphers allowed and their order of preference.
259 macs = lib.mkOption {
260 type = lib.types.nullOr (lib.types.listOf lib.types.str);
262 example = [ "hmac-sha2-512-etm@openssh.com" "hmac-sha1" ];
264 Specifies the MAC (message authentication code) algorithms in order of preference. The MAC algorithm is used
265 for data integrity protection.
274 programs.ssh.setXAuthLocation =
275 lib.mkDefault (config.services.xserver.enable || config.programs.ssh.forwardX11 == true || config.services.openssh.settings.X11Forwarding);
278 [ { assertion = cfg.forwardX11 == true -> cfg.setXAuthLocation;
279 message = "cannot enable X11 forwarding without setting XAuth location";
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";
287 # SSH configuration. Slight duplication of the sshd_config
288 # generation in the sshd service.
289 environment.etc."ssh/ssh_config".text =
291 # Custom options from `extraConfig`, to override generated options
294 # Generated options from other settings
296 GlobalKnownHostsFile ${builtins.concatStringsSep " " knownHostsFiles}
298 ${lib.optionalString (!config.networking.enableIPv6) "AddressFamily inet"}
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"}"}
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}"}
309 environment.etc."ssh/ssh_known_hosts".text = knownHostsText;
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";
317 { ExecStartPre = "${pkgs.coreutils}/bin/rm -f %t/ssh-agent";
319 "${cfg.package}/bin/ssh-agent " +
320 lib.optionalString (cfg.agentTimeout != null) ("-t ${cfg.agentTimeout} ") +
321 lib.optionalString (cfg.agentPKCS11Whitelist != null) ("-P ${cfg.agentPKCS11Whitelist} ") +
323 StandardOutput = "null";
325 Restart = "on-failure";
326 SuccessExitStatus = "0 2";
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
335 environment.extraInit = lib.optionalString cfg.startAgent
337 if [ -z "$SSH_AUTH_SOCK" -a -n "$XDG_RUNTIME_DIR" ]; then
338 export SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/ssh-agent"
342 environment.variables.SSH_ASKPASS = lib.optionalString cfg.enableAskPassword cfg.askPassword;