at v206 7.9 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 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 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 default = config.services.xserver.enable; 59 description = '' 60 Whether to set the path to <command>xauth</command> for X11-forwarded connections. 61 This causes a dependency on X11 packages. 62 ''; 63 }; 64 65 extraConfig = mkOption { 66 type = types.lines; 67 default = ""; 68 description = '' 69 Extra configuration text appended to <filename>ssh_config</filename>. 70 See <citerefentry><refentrytitle>ssh_config</refentrytitle><manvolnum>5</manvolnum></citerefentry> 71 for help. 72 ''; 73 }; 74 75 startAgent = mkOption { 76 type = types.bool; 77 default = true; 78 description = '' 79 Whether to start the OpenSSH agent when you log in. The OpenSSH agent 80 remembers private keys for you so that you don't have to type in 81 passphrases every time you make an SSH connection. Use 82 <command>ssh-add</command> to add a key to the agent. 83 ''; 84 }; 85 86 agentTimeout = mkOption { 87 type = types.nullOr types.str; 88 default = null; 89 example = "1h"; 90 description = '' 91 How long to keep the private keys in memory. Use null to keep them forever. 92 ''; 93 }; 94 95 package = mkOption { 96 default = pkgs.openssh; 97 description = '' 98 The package used for the openssh client and daemon. 99 ''; 100 }; 101 102 knownHosts = mkOption { 103 default = {}; 104 type = types.loaOf (types.submodule ({ name, ... }: { 105 options = { 106 hostNames = mkOption { 107 type = types.listOf types.str; 108 default = []; 109 description = '' 110 A list of host names and/or IP numbers used for accessing 111 the host's ssh service. 112 ''; 113 }; 114 publicKey = mkOption { 115 default = null; 116 type = types.nullOr types.str; 117 example = "ecdsa-sha2-nistp521 AAAAE2VjZHN...UEPg=="; 118 description = '' 119 The public key data for the host. You can fetch a public key 120 from a running SSH server with the <command>ssh-keyscan</command> 121 command. The public key should not include any host names, only 122 the key type and the key itself. 123 ''; 124 }; 125 publicKeyFile = mkOption { 126 default = null; 127 type = types.nullOr types.path; 128 description = '' 129 The path to the public key file for the host. The public 130 key file is read at build time and saved in the Nix store. 131 You can fetch a public key file from a running SSH server 132 with the <command>ssh-keyscan</command> command. The content 133 of the file should follow the same format as described for 134 the <literal>publicKey</literal> option. 135 ''; 136 }; 137 }; 138 config = { 139 hostNames = mkDefault [ name ]; 140 }; 141 })); 142 description = '' 143 The set of system-wide known SSH hosts. 144 ''; 145 example = [ 146 { 147 hostNames = [ "myhost" "myhost.mydomain.com" "10.10.1.4" ]; 148 publicKeyFile = literalExample "./pubkeys/myhost_ssh_host_dsa_key.pub"; 149 } 150 { 151 hostNames = [ "myhost2" ]; 152 publicKeyFile = literalExample "./pubkeys/myhost2_ssh_host_dsa_key.pub"; 153 } 154 ]; 155 }; 156 157 }; 158 159 }; 160 161 config = { 162 163 assertions = 164 [ { assertion = cfg.forwardX11 -> cfg.setXAuthLocation; 165 message = "cannot enable X11 forwarding without setting XAuth location"; 166 } 167 ] ++ flip mapAttrsToList cfg.knownHosts (name: data: { 168 assertion = (data.publicKey == null && data.publicKeyFile != null) || 169 (data.publicKey != null && data.publicKeyFile == null); 170 message = "knownHost ${name} must contain either a publicKey or publicKeyFile"; 171 }); 172 173 # SSH configuration. Slight duplication of the sshd_config 174 # generation in the sshd service. 175 environment.etc."ssh/ssh_config".text = 176 '' 177 AddressFamily ${if config.networking.enableIPv6 then "any" else "inet"} 178 179 ${optionalString cfg.setXAuthLocation '' 180 XAuthLocation ${pkgs.xorg.xauth}/bin/xauth 181 ''} 182 183 ForwardX11 ${if cfg.forwardX11 then "yes" else "no"} 184 185 ${cfg.extraConfig} 186 ''; 187 188 environment.etc."ssh/ssh_known_hosts".text = knownHostsText; 189 190 # FIXME: this should really be socket-activated for über-awesomeness. 191 systemd.user.services.ssh-agent = 192 { enable = cfg.startAgent; 193 description = "SSH Agent"; 194 wantedBy = [ "default.target" ]; 195 serviceConfig = 196 { ExecStartPre = "${pkgs.coreutils}/bin/rm -f %t/ssh-agent"; 197 ExecStart = 198 "${cfg.package}/bin/ssh-agent " + 199 optionalString (cfg.agentTimeout != null) ("-t ${cfg.agentTimeout} ") + 200 "-a %t/ssh-agent"; 201 StandardOutput = "null"; 202 Type = "forking"; 203 Restart = "on-failure"; 204 SuccessExitStatus = "0 2"; 205 }; 206 # Allow ssh-agent to ask for confirmation. This requires the 207 # unit to know about the user's $DISPLAY (via ‘systemctl 208 # import-environment’). 209 environment.SSH_ASKPASS = optionalString config.services.xserver.enable askPasswordWrapper; 210 environment.DISPLAY = "fake"; # required to make ssh-agent start $SSH_ASKPASS 211 }; 212 213 environment.extraInit = optionalString cfg.startAgent 214 '' 215 if [ -z "$SSH_AUTH_SOCK" -a -n "$XDG_RUNTIME_DIR" ]; then 216 export SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/ssh-agent" 217 fi 218 ''; 219 220 environment.interactiveShellInit = optionalString config.services.xserver.enable 221 '' 222 export SSH_ASKPASS=${askPassword} 223 ''; 224 225 programs.ssh.askPassword = mkDefault "${pkgs.x11_ssh_askpass}/libexec/x11-ssh-askpass"; 226 227 }; 228}