typioca: 2.7.0 -> 2.8.0
[NixPkgs.git] / nixos / modules / programs / ssh.nix
blob7c85d1e7c3d51565f780f00ba7f3948dbfb2b762
1 # Global configuration for the SSH client.
3 { config, lib, pkgs, ... }:
5 with lib;
7 let
9   cfg  = config.programs.ssh;
11   askPassword = cfg.askPassword;
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     '';
21   knownHosts = attrValues cfg.knownHosts;
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";
29   knownHostsFiles = [ "/etc/ssh/ssh_known_hosts" ]
30     ++ map pkgs.copyPathToStore cfg.knownHostsFiles;
34   ###### interface
36   options = {
38     programs.ssh = {
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       };
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       };
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       };
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       };
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       };
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       };
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       };
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       };
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       };
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       };
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       };
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       };
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       };
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       };
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       };
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     };
279   };
281   config = {
283     programs.ssh.setXAuthLocation =
284       mkDefault (config.services.xserver.enable || config.programs.ssh.forwardX11 || config.services.openssh.settings.X11Forwarding);
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       });
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}
303         # Generated options from other settings
304         Host *
305         AddressFamily ${if config.networking.enableIPv6 then "any" else "inet"}
306         GlobalKnownHostsFile ${concatStringsSep " " knownHostsFiles}
308         ${optionalString cfg.setXAuthLocation ''
309           XAuthLocation ${pkgs.xorg.xauth}/bin/xauth
310         ''}
312         ForwardX11 ${if cfg.forwardX11 then "yes" else "no"}
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       '';
321     environment.etc."ssh/ssh_known_hosts".text = knownHostsText;
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       };
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       '';
354     environment.variables.SSH_ASKPASS = optionalString cfg.enableAskPassword askPassword;
356   };