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