at 23.05-pre 6.5 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 7 cfg = config.boot.initrd.network.ssh; 8 9in 10 11{ 12 13 options.boot.initrd.network.ssh = { 14 enable = mkOption { 15 type = types.bool; 16 default = false; 17 description = lib.mdDoc '' 18 Start SSH service during initrd boot. It can be used to debug failing 19 boot on a remote server, enter pasphrase for an encrypted partition etc. 20 Service is killed when stage-1 boot is finished. 21 22 The sshd configuration is largely inherited from 23 {option}`services.openssh`. 24 ''; 25 }; 26 27 port = mkOption { 28 type = types.port; 29 default = 22; 30 description = lib.mdDoc '' 31 Port on which SSH initrd service should listen. 32 ''; 33 }; 34 35 shell = mkOption { 36 type = types.str; 37 default = "/bin/ash"; 38 description = lib.mdDoc '' 39 Login shell of the remote user. Can be used to limit actions user can do. 40 ''; 41 }; 42 43 hostKeys = mkOption { 44 type = types.listOf (types.either types.str types.path); 45 default = []; 46 example = [ 47 "/etc/secrets/initrd/ssh_host_rsa_key" 48 "/etc/secrets/initrd/ssh_host_ed25519_key" 49 ]; 50 description = lib.mdDoc '' 51 Specify SSH host keys to import into the initrd. 52 53 To generate keys, use 54 {manpage}`ssh-keygen(1)` 55 as root: 56 57 ``` 58 ssh-keygen -t rsa -N "" -f /etc/secrets/initrd/ssh_host_rsa_key 59 ssh-keygen -t ed25519 -N "" -f /etc/secrets/initrd/ssh_host_ed25519_key 60 ``` 61 62 ::: {.warning} 63 Unless your bootloader supports initrd secrets, these keys 64 are stored insecurely in the global Nix store. Do NOT use 65 your regular SSH host private keys for this purpose or 66 you'll expose them to regular users! 67 68 Additionally, even if your initrd supports secrets, if 69 you're using initrd SSH to unlock an encrypted disk then 70 using your regular host keys exposes the private keys on 71 your unencrypted boot partition. 72 ::: 73 ''; 74 }; 75 76 authorizedKeys = mkOption { 77 type = types.listOf types.str; 78 default = config.users.users.root.openssh.authorizedKeys.keys; 79 defaultText = literalExpression "config.users.users.root.openssh.authorizedKeys.keys"; 80 description = lib.mdDoc '' 81 Authorized keys for the root user on initrd. 82 ''; 83 }; 84 85 extraConfig = mkOption { 86 type = types.lines; 87 default = ""; 88 description = lib.mdDoc "Verbatim contents of {file}`sshd_config`."; 89 }; 90 }; 91 92 imports = 93 map (opt: mkRemovedOptionModule ([ "boot" "initrd" "network" "ssh" ] ++ [ opt ]) '' 94 The initrd SSH functionality now uses OpenSSH rather than Dropbear. 95 96 If you want to keep your existing initrd SSH host keys, convert them with 97 $ dropbearconvert dropbear openssh dropbear_host_$type_key ssh_host_$type_key 98 and then set options.boot.initrd.network.ssh.hostKeys. 99 '') [ "hostRSAKey" "hostDSSKey" "hostECDSAKey" ]; 100 101 config = let 102 # Nix complains if you include a store hash in initrd path names, so 103 # as an awful hack we drop the first character of the hash. 104 initrdKeyPath = path: if isString path 105 then path 106 else let name = builtins.baseNameOf path; in 107 builtins.unsafeDiscardStringContext ("/etc/ssh/" + 108 substring 1 (stringLength name) name); 109 110 sshdCfg = config.services.openssh; 111 112 sshdConfig = '' 113 Port ${toString cfg.port} 114 115 PasswordAuthentication no 116 ChallengeResponseAuthentication no 117 118 ${flip concatMapStrings cfg.hostKeys (path: '' 119 HostKey ${initrdKeyPath path} 120 '')} 121 122 KexAlgorithms ${concatStringsSep "," sshdCfg.kexAlgorithms} 123 Ciphers ${concatStringsSep "," sshdCfg.ciphers} 124 MACs ${concatStringsSep "," sshdCfg.macs} 125 126 LogLevel ${sshdCfg.logLevel} 127 128 ${if sshdCfg.useDns then '' 129 UseDNS yes 130 '' else '' 131 UseDNS no 132 ''} 133 134 ${cfg.extraConfig} 135 ''; 136 in mkIf (config.boot.initrd.network.enable && cfg.enable) { 137 assertions = [ 138 { 139 assertion = cfg.authorizedKeys != []; 140 message = "You should specify at least one authorized key for initrd SSH"; 141 } 142 143 { 144 assertion = cfg.hostKeys != []; 145 message = '' 146 You must now pre-generate the host keys for initrd SSH. 147 See the boot.initrd.network.ssh.hostKeys documentation 148 for instructions. 149 ''; 150 } 151 ]; 152 153 boot.initrd.extraUtilsCommands = '' 154 copy_bin_and_libs ${pkgs.openssh}/bin/sshd 155 cp -pv ${pkgs.glibc.out}/lib/libnss_files.so.* $out/lib 156 ''; 157 158 boot.initrd.extraUtilsCommandsTest = '' 159 # sshd requires a host key to check config, so we pass in the test's 160 tmpkey="$(mktemp initrd-ssh-testkey.XXXXXXXXXX)" 161 cp "${../../../tests/initrd-network-ssh/ssh_host_ed25519_key}" "$tmpkey" 162 # keys from Nix store are world-readable, which sshd doesn't like 163 chmod 600 "$tmpkey" 164 echo -n ${escapeShellArg sshdConfig} | 165 $out/bin/sshd -t -f /dev/stdin \ 166 -h "$tmpkey" 167 rm "$tmpkey" 168 ''; 169 170 boot.initrd.network.postCommands = '' 171 echo '${cfg.shell}' > /etc/shells 172 echo 'root:x:0:0:root:/root:${cfg.shell}' > /etc/passwd 173 echo 'sshd:x:1:1:sshd:/var/empty:/bin/nologin' >> /etc/passwd 174 echo 'passwd: files' > /etc/nsswitch.conf 175 176 mkdir -p /var/log /var/empty 177 touch /var/log/lastlog 178 179 mkdir -p /etc/ssh 180 echo -n ${escapeShellArg sshdConfig} > /etc/ssh/sshd_config 181 182 echo "export PATH=$PATH" >> /etc/profile 183 echo "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH" >> /etc/profile 184 185 mkdir -p /root/.ssh 186 ${concatStrings (map (key: '' 187 echo ${escapeShellArg key} >> /root/.ssh/authorized_keys 188 '') cfg.authorizedKeys)} 189 190 ${flip concatMapStrings cfg.hostKeys (path: '' 191 # keys from Nix store are world-readable, which sshd doesn't like 192 chmod 0600 "${initrdKeyPath path}" 193 '')} 194 195 /bin/sshd -e 196 ''; 197 198 boot.initrd.postMountCommands = '' 199 # Stop sshd cleanly before stage 2. 200 # 201 # If you want to keep it around to debug post-mount SSH issues, 202 # run `touch /.keep_sshd` (either from an SSH session or in 203 # another initrd hook like preDeviceCommands). 204 if ! [ -e /.keep_sshd ]; then 205 pkill -x sshd 206 fi 207 ''; 208 209 boot.initrd.secrets = listToAttrs 210 (map (path: nameValuePair (initrdKeyPath path) path) cfg.hostKeys); 211 }; 212 213}