1# Global configuration for the SSH client.
2
3{ config, lib, pkgs, ... }:
4
5with lib;
6
7let
8
9 cfg = config.programs.ssh;
10 cfgd = config.services.openssh;
11
12 askPassword = cfg.askPassword;
13
14 askPasswordWrapper = pkgs.writeScript "ssh-askpass-wrapper"
15 ''
16 #! ${pkgs.stdenv.shell} -e
17 export DISPLAY="$(systemctl --user show-environment | ${pkgs.gnused}/bin/sed 's/^DISPLAY=\(.*\)/\1/; t; d')"
18 exec ${askPassword}
19 '';
20
21 knownHosts = map (h: getAttr h cfg.knownHosts) (attrNames cfg.knownHosts);
22
23 knownHostsText = flip (concatMapStringsSep "\n") knownHosts
24 (h: assert h.hostNames != [];
25 concatStringsSep "," h.hostNames + " "
26 + (if h.publicKey != null then h.publicKey else readFile h.publicKeyFile)
27 );
28
29in
30{
31 ###### interface
32
33 options = {
34
35 programs.ssh = {
36
37 askPassword = mkOption {
38 type = types.str;
39 default = "${pkgs.x11_ssh_askpass}/libexec/x11-ssh-askpass";
40 description = ''Program used by SSH to ask for passwords.'';
41 };
42
43 forwardX11 = mkOption {
44 type = types.bool;
45 default = false;
46 description = ''
47 Whether to request X11 forwarding on outgoing connections by default.
48 This is useful for running graphical programs on the remote machine and have them display to your local X11 server.
49 Historically, this value has depended on the value used by the local sshd daemon, but there really isn't a relation between the two.
50 Note: there are some security risks to forwarding an X11 connection.
51 NixOS's X server is built with the SECURITY extension, which prevents some obvious attacks.
52 To enable or disable forwarding on a per-connection basis, see the -X and -x options to ssh.
53 The -Y option to ssh enables trusted forwarding, which bypasses the SECURITY extension.
54 '';
55 };
56
57 setXAuthLocation = mkOption {
58 type = types.bool;
59 default = config.services.xserver.enable;
60 description = ''
61 Whether to set the path to <command>xauth</command> for X11-forwarded connections.
62 This causes a dependency on X11 packages.
63 '';
64 };
65
66 extraConfig = mkOption {
67 type = types.lines;
68 default = "";
69 description = ''
70 Extra configuration text appended to <filename>ssh_config</filename>.
71 See <citerefentry><refentrytitle>ssh_config</refentrytitle><manvolnum>5</manvolnum></citerefentry>
72 for help.
73 '';
74 };
75
76 startAgent = mkOption {
77 type = types.bool;
78 default = true;
79 description = ''
80 Whether to start the OpenSSH agent when you log in. The OpenSSH agent
81 remembers private keys for you so that you don't have to type in
82 passphrases every time you make an SSH connection. Use
83 <command>ssh-add</command> to add a key to the agent.
84 '';
85 };
86
87 agentTimeout = mkOption {
88 type = types.nullOr types.str;
89 default = null;
90 example = "1h";
91 description = ''
92 How long to keep the private keys in memory. Use null to keep them forever.
93 '';
94 };
95
96 package = mkOption {
97 default = pkgs.openssh;
98 description = ''
99 The package used for the openssh client and daemon.
100 '';
101 };
102
103 knownHosts = mkOption {
104 default = {};
105 type = types.loaOf (types.submodule ({ name, ... }: {
106 options = {
107 hostNames = mkOption {
108 type = types.listOf types.str;
109 default = [];
110 description = ''
111 A list of host names and/or IP numbers used for accessing
112 the host's ssh service.
113 '';
114 };
115 publicKey = mkOption {
116 default = null;
117 type = types.nullOr types.str;
118 example = "ecdsa-sha2-nistp521 AAAAE2VjZHN...UEPg==";
119 description = ''
120 The public key data for the host. You can fetch a public key
121 from a running SSH server with the <command>ssh-keyscan</command>
122 command. The public key should not include any host names, only
123 the key type and the key itself.
124 '';
125 };
126 publicKeyFile = mkOption {
127 default = null;
128 type = types.nullOr types.path;
129 description = ''
130 The path to the public key file for the host. The public
131 key file is read at build time and saved in the Nix store.
132 You can fetch a public key file from a running SSH server
133 with the <command>ssh-keyscan</command> command. The content
134 of the file should follow the same format as described for
135 the <literal>publicKey</literal> option.
136 '';
137 };
138 };
139 config = {
140 hostNames = mkDefault [ name ];
141 };
142 }));
143 description = ''
144 The set of system-wide known SSH hosts.
145 '';
146 example = [
147 {
148 hostNames = [ "myhost" "myhost.mydomain.com" "10.10.1.4" ];
149 publicKeyFile = literalExample "./pubkeys/myhost_ssh_host_dsa_key.pub";
150 }
151 {
152 hostNames = [ "myhost2" ];
153 publicKeyFile = literalExample "./pubkeys/myhost2_ssh_host_dsa_key.pub";
154 }
155 ];
156 };
157
158 };
159
160 };
161
162 config = {
163
164 assertions =
165 [ { assertion = cfg.forwardX11 -> cfg.setXAuthLocation;
166 message = "cannot enable X11 forwarding without setting XAuth location";
167 }
168 ] ++ flip mapAttrsToList cfg.knownHosts (name: data: {
169 assertion = (data.publicKey == null && data.publicKeyFile != null) ||
170 (data.publicKey != null && data.publicKeyFile == null);
171 message = "knownHost ${name} must contain either a publicKey or publicKeyFile";
172 });
173
174 # SSH configuration. Slight duplication of the sshd_config
175 # generation in the sshd service.
176 environment.etc."ssh/ssh_config".text =
177 ''
178 AddressFamily ${if config.networking.enableIPv6 then "any" else "inet"}
179
180 ${optionalString cfg.setXAuthLocation ''
181 XAuthLocation ${pkgs.xorg.xauth}/bin/xauth
182 ''}
183
184 ForwardX11 ${if cfg.forwardX11 then "yes" else "no"}
185
186 ${cfg.extraConfig}
187 '';
188
189 environment.etc."ssh/ssh_known_hosts".text = knownHostsText;
190
191 # FIXME: this should really be socket-activated for über-awesomeness.
192 systemd.user.services.ssh-agent =
193 { enable = cfg.startAgent;
194 description = "SSH Agent";
195 wantedBy = [ "default.target" ];
196 serviceConfig =
197 { ExecStartPre = "${pkgs.coreutils}/bin/rm -f %t/ssh-agent";
198 ExecStart =
199 "${cfg.package}/bin/ssh-agent " +
200 optionalString (cfg.agentTimeout != null) ("-t ${cfg.agentTimeout} ") +
201 "-a %t/ssh-agent";
202 StandardOutput = "null";
203 Type = "forking";
204 Restart = "on-failure";
205 SuccessExitStatus = "0 2";
206 };
207 # Allow ssh-agent to ask for confirmation. This requires the
208 # unit to know about the user's $DISPLAY (via ‘systemctl
209 # import-environment’).
210 environment.SSH_ASKPASS = optionalString config.services.xserver.enable askPasswordWrapper;
211 environment.DISPLAY = "fake"; # required to make ssh-agent start $SSH_ASKPASS
212 };
213
214 environment.extraInit = optionalString cfg.startAgent
215 ''
216 if [ -z "$SSH_AUTH_SOCK" -a -n "$XDG_RUNTIME_DIR" ]; then
217 export SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/ssh-agent"
218 fi
219 '';
220
221 environment.interactiveShellInit = optionalString config.services.xserver.enable
222 ''
223 export SSH_ASKPASS=${askPassword}
224 '';
225
226 };
227}