211 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
			
		
		
	
	
			211 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
| { config, lib, pkgs, ... }:
 | |
| 
 | |
| with lib;
 | |
| 
 | |
| let
 | |
| 
 | |
|   cfg = config.boot.initrd.network.ssh;
 | |
| 
 | |
| in
 | |
| 
 | |
| {
 | |
| 
 | |
|   options.boot.initrd.network.ssh = {
 | |
|     enable = mkOption {
 | |
|       type = types.bool;
 | |
|       default = false;
 | |
|       description = ''
 | |
|         Start SSH service during initrd boot. It can be used to debug failing
 | |
|         boot on a remote server, enter pasphrase for an encrypted partition etc.
 | |
|         Service is killed when stage-1 boot is finished.
 | |
| 
 | |
|         The sshd configuration is largely inherited from
 | |
|         <option>services.openssh</option>.
 | |
|       '';
 | |
|     };
 | |
| 
 | |
|     port = mkOption {
 | |
|       type = types.int;
 | |
|       default = 22;
 | |
|       description = ''
 | |
|         Port on which SSH initrd service should listen.
 | |
|       '';
 | |
|     };
 | |
| 
 | |
|     shell = mkOption {
 | |
|       type = types.str;
 | |
|       default = "/bin/ash";
 | |
|       description = ''
 | |
|         Login shell of the remote user. Can be used to limit actions user can do.
 | |
|       '';
 | |
|     };
 | |
| 
 | |
|     hostKeys = mkOption {
 | |
|       type = types.listOf (types.either types.str types.path);
 | |
|       default = [];
 | |
|       example = [
 | |
|         "/etc/secrets/initrd/ssh_host_rsa_key"
 | |
|         "/etc/secrets/initrd/ssh_host_ed25519_key"
 | |
|       ];
 | |
|       description = ''
 | |
|         Specify SSH host keys to import into the initrd.
 | |
| 
 | |
|         To generate keys, use
 | |
|         <citerefentry><refentrytitle>ssh-keygen</refentrytitle><manvolnum>1</manvolnum></citerefentry>:
 | |
| 
 | |
|         <screen>
 | |
|         <prompt># </prompt>ssh-keygen -t rsa -N "" -f /etc/secrets/initrd/ssh_host_rsa_key
 | |
|         <prompt># </prompt>ssh-keygen -t ed25519 -N "" -f /etc/secrets/initrd/ssh_host_ed25519_key
 | |
|         </screen>
 | |
| 
 | |
|         <warning>
 | |
|           <para>
 | |
|             Unless your bootloader supports initrd secrets, these keys
 | |
|             are stored insecurely in the global Nix store. Do NOT use
 | |
|             your regular SSH host private keys for this purpose or
 | |
|             you'll expose them to regular users!
 | |
|           </para>
 | |
|           <para>
 | |
|             Additionally, even if your initrd supports secrets, if
 | |
|             you're using initrd SSH to unlock an encrypted disk then
 | |
|             using your regular host keys exposes the private keys on
 | |
|             your unencrypted boot partition.
 | |
|           </para>
 | |
|         </warning>
 | |
|       '';
 | |
|     };
 | |
| 
 | |
|     authorizedKeys = mkOption {
 | |
|       type = types.listOf types.str;
 | |
|       default = config.users.users.root.openssh.authorizedKeys.keys;
 | |
|       defaultText = "config.users.users.root.openssh.authorizedKeys.keys";
 | |
|       description = ''
 | |
|         Authorized keys for the root user on initrd.
 | |
|       '';
 | |
|     };
 | |
| 
 | |
|     extraConfig = mkOption {
 | |
|       type = types.lines;
 | |
|       default = "";
 | |
|       description = "Verbatim contents of <filename>sshd_config</filename>.";
 | |
|     };
 | |
|   };
 | |
| 
 | |
|   imports =
 | |
|     map (opt: mkRemovedOptionModule ([ "boot" "initrd" "network" "ssh" ] ++ [ opt ]) ''
 | |
|       The initrd SSH functionality now uses OpenSSH rather than Dropbear.
 | |
| 
 | |
|       If you want to keep your existing initrd SSH host keys, convert them with
 | |
|         $ dropbearconvert dropbear openssh dropbear_host_$type_key ssh_host_$type_key
 | |
|       and then set options.boot.initrd.network.ssh.hostKeys.
 | |
|     '') [ "hostRSAKey" "hostDSSKey" "hostECDSAKey" ];
 | |
| 
 | |
|   config = let
 | |
|     # Nix complains if you include a store hash in initrd path names, so
 | |
|     # as an awful hack we drop the first character of the hash.
 | |
|     initrdKeyPath = path: if isString path
 | |
|       then path
 | |
|       else let name = builtins.baseNameOf path; in
 | |
|         builtins.unsafeDiscardStringContext ("/etc/ssh/" +
 | |
|           substring 1 (stringLength name) name);
 | |
| 
 | |
|     sshdCfg = config.services.openssh;
 | |
| 
 | |
|     sshdConfig = ''
 | |
|       Port ${toString cfg.port}
 | |
| 
 | |
|       PasswordAuthentication no
 | |
|       ChallengeResponseAuthentication no
 | |
| 
 | |
|       ${flip concatMapStrings cfg.hostKeys (path: ''
 | |
|         HostKey ${initrdKeyPath path}
 | |
|       '')}
 | |
| 
 | |
|       KexAlgorithms ${concatStringsSep "," sshdCfg.kexAlgorithms}
 | |
|       Ciphers ${concatStringsSep "," sshdCfg.ciphers}
 | |
|       MACs ${concatStringsSep "," sshdCfg.macs}
 | |
| 
 | |
|       LogLevel ${sshdCfg.logLevel}
 | |
| 
 | |
|       ${if sshdCfg.useDns then ''
 | |
|         UseDNS yes
 | |
|       '' else ''
 | |
|         UseDNS no
 | |
|       ''}
 | |
| 
 | |
|       ${cfg.extraConfig}
 | |
|     '';
 | |
|   in mkIf (config.boot.initrd.network.enable && cfg.enable) {
 | |
|     assertions = [
 | |
|       {
 | |
|         assertion = cfg.authorizedKeys != [];
 | |
|         message = "You should specify at least one authorized key for initrd SSH";
 | |
|       }
 | |
| 
 | |
|       {
 | |
|         assertion = cfg.hostKeys != [];
 | |
|         message = ''
 | |
|           You must now pre-generate the host keys for initrd SSH.
 | |
|           See the boot.initrd.network.ssh.hostKeys documentation
 | |
|           for instructions.
 | |
|         '';
 | |
|       }
 | |
|     ];
 | |
| 
 | |
|     boot.initrd.extraUtilsCommands = ''
 | |
|       copy_bin_and_libs ${pkgs.openssh}/bin/sshd
 | |
|       cp -pv ${pkgs.glibc.out}/lib/libnss_files.so.* $out/lib
 | |
|     '';
 | |
| 
 | |
|     boot.initrd.extraUtilsCommandsTest = ''
 | |
|       # sshd requires a host key to check config, so we pass in the test's
 | |
|       echo -n ${escapeShellArg sshdConfig} |
 | |
|         $out/bin/sshd -t -f /dev/stdin \
 | |
|         -h ${../../../tests/initrd-network-ssh/ssh_host_ed25519_key}
 | |
|     '';
 | |
| 
 | |
|     boot.initrd.network.postCommands = ''
 | |
|       echo '${cfg.shell}' > /etc/shells
 | |
|       echo 'root:x:0:0:root:/root:${cfg.shell}' > /etc/passwd
 | |
|       echo 'sshd:x:1:1:sshd:/var/empty:/bin/nologin' >> /etc/passwd
 | |
|       echo 'passwd: files' > /etc/nsswitch.conf
 | |
| 
 | |
|       mkdir -p /var/log /var/empty
 | |
|       touch /var/log/lastlog
 | |
| 
 | |
|       mkdir -p /etc/ssh
 | |
|       echo -n ${escapeShellArg sshdConfig} > /etc/ssh/sshd_config
 | |
| 
 | |
|       echo "export PATH=$PATH" >> /etc/profile
 | |
|       echo "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH" >> /etc/profile
 | |
| 
 | |
|       mkdir -p /root/.ssh
 | |
|       ${concatStrings (map (key: ''
 | |
|         echo ${escapeShellArg key} >> /root/.ssh/authorized_keys
 | |
|       '') cfg.authorizedKeys)}
 | |
| 
 | |
|       ${flip concatMapStrings cfg.hostKeys (path: ''
 | |
|         # keys from Nix store are world-readable, which sshd doesn't like
 | |
|         chmod 0600 "${initrdKeyPath path}"
 | |
|       '')}
 | |
| 
 | |
|       /bin/sshd -e
 | |
|     '';
 | |
| 
 | |
|     boot.initrd.postMountCommands = ''
 | |
|       # Stop sshd cleanly before stage 2.
 | |
|       #
 | |
|       # If you want to keep it around to debug post-mount SSH issues,
 | |
|       # run `touch /.keep_sshd` (either from an SSH session or in
 | |
|       # another initrd hook like preDeviceCommands).
 | |
|       if ! [ -e /.keep_sshd ]; then
 | |
|         pkill -x sshd
 | |
|       fi
 | |
|     '';
 | |
| 
 | |
|     boot.initrd.secrets = listToAttrs
 | |
|       (map (path: nameValuePair (initrdKeyPath path) path) cfg.hostKeys);
 | |
|   };
 | |
| 
 | |
| }
 | 
