at 25.11-pre 11 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7 8with lib; 9 10let 11 12 cfg = config.boot.initrd.network.ssh; 13 shell = if cfg.shell == null then "/bin/ash" else cfg.shell; 14 inherit (config.programs.ssh) package; 15 16 enabled = 17 let 18 initrd = config.boot.initrd; 19 in 20 (initrd.network.enable || initrd.systemd.network.enable) && cfg.enable; 21 22in 23 24{ 25 26 options.boot.initrd.network.ssh = { 27 enable = mkOption { 28 type = types.bool; 29 default = false; 30 description = '' 31 Start SSH service during initrd boot. It can be used to debug failing 32 boot on a remote server, enter pasphrase for an encrypted partition etc. 33 Service is killed when stage-1 boot is finished. 34 35 The sshd configuration is largely inherited from 36 {option}`services.openssh`. 37 ''; 38 }; 39 40 port = mkOption { 41 type = types.port; 42 default = 22; 43 description = '' 44 Port on which SSH initrd service should listen. 45 ''; 46 }; 47 48 shell = mkOption { 49 type = types.nullOr types.str; 50 default = null; 51 defaultText = ''"/bin/ash"''; 52 description = '' 53 Login shell of the remote user. Can be used to limit actions user can do. 54 ''; 55 }; 56 57 hostKeys = mkOption { 58 type = types.listOf (types.either types.str types.path); 59 default = [ ]; 60 example = [ 61 "/etc/secrets/initrd/ssh_host_rsa_key" 62 "/etc/secrets/initrd/ssh_host_ed25519_key" 63 ]; 64 description = '' 65 Specify SSH host keys to import into the initrd. 66 67 To generate keys, use 68 {manpage}`ssh-keygen(1)` 69 as root: 70 71 ``` 72 ssh-keygen -t rsa -N "" -f /etc/secrets/initrd/ssh_host_rsa_key 73 ssh-keygen -t ed25519 -N "" -f /etc/secrets/initrd/ssh_host_ed25519_key 74 ``` 75 76 ::: {.warning} 77 Unless your bootloader supports initrd secrets, these keys 78 are stored insecurely in the global Nix store. Do NOT use 79 your regular SSH host private keys for this purpose or 80 you'll expose them to regular users! 81 82 Additionally, even if your initrd supports secrets, if 83 you're using initrd SSH to unlock an encrypted disk then 84 using your regular host keys exposes the private keys on 85 your unencrypted boot partition. 86 ::: 87 ''; 88 }; 89 90 ignoreEmptyHostKeys = mkOption { 91 type = types.bool; 92 default = false; 93 description = '' 94 Allow leaving {option}`config.boot.initrd.network.ssh.hostKeys` empty, 95 to deploy ssh host keys out of band. 96 ''; 97 }; 98 99 authorizedKeys = mkOption { 100 type = types.listOf types.str; 101 default = config.users.users.root.openssh.authorizedKeys.keys; 102 defaultText = literalExpression "config.users.users.root.openssh.authorizedKeys.keys"; 103 description = '' 104 Authorized keys for the root user on initrd. 105 You can combine the `authorizedKeys` and `authorizedKeyFiles` options. 106 ''; 107 example = [ 108 "ssh-rsa AAAAB3NzaC1yc2etc/etc/etcjwrsh8e596z6J0l7 example@host" 109 "ssh-ed25519 AAAAC3NzaCetcetera/etceteraJZMfk3QPfQ foo@bar" 110 ]; 111 }; 112 113 authorizedKeyFiles = mkOption { 114 type = types.listOf types.path; 115 default = config.users.users.root.openssh.authorizedKeys.keyFiles; 116 defaultText = literalExpression "config.users.users.root.openssh.authorizedKeys.keyFiles"; 117 description = '' 118 Authorized keys taken from files for the root user on initrd. 119 You can combine the `authorizedKeyFiles` and `authorizedKeys` options. 120 ''; 121 }; 122 123 extraConfig = mkOption { 124 type = types.lines; 125 default = ""; 126 description = "Verbatim contents of {file}`sshd_config`."; 127 }; 128 }; 129 130 imports = 131 map 132 ( 133 opt: 134 mkRemovedOptionModule 135 ( 136 [ 137 "boot" 138 "initrd" 139 "network" 140 "ssh" 141 ] 142 ++ [ opt ] 143 ) 144 '' 145 The initrd SSH functionality now uses OpenSSH rather than Dropbear. 146 147 If you want to keep your existing initrd SSH host keys, convert them with 148 $ dropbearconvert dropbear openssh dropbear_host_$type_key ssh_host_$type_key 149 and then set options.boot.initrd.network.ssh.hostKeys. 150 '' 151 ) 152 [ 153 "hostRSAKey" 154 "hostDSSKey" 155 "hostECDSAKey" 156 ]; 157 158 config = 159 let 160 # Nix complains if you include a store hash in initrd path names, so 161 # as an awful hack we drop the first character of the hash. 162 initrdKeyPath = 163 path: 164 if isString path then 165 path 166 else 167 let 168 name = builtins.baseNameOf path; 169 in 170 builtins.unsafeDiscardStringContext ("/etc/ssh/" + substring 1 (stringLength name) name); 171 172 sshdCfg = config.services.openssh; 173 174 sshdConfig = 175 '' 176 UsePAM no 177 Port ${toString cfg.port} 178 179 PasswordAuthentication no 180 AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys2 /etc/ssh/authorized_keys.d/%u 181 ChallengeResponseAuthentication no 182 183 ${flip concatMapStrings cfg.hostKeys (path: '' 184 HostKey ${initrdKeyPath path} 185 '')} 186 187 '' 188 + lib.optionalString (sshdCfg.settings.KexAlgorithms != null) '' 189 KexAlgorithms ${concatStringsSep "," sshdCfg.settings.KexAlgorithms} 190 '' 191 + lib.optionalString (sshdCfg.settings.Ciphers != null) '' 192 Ciphers ${concatStringsSep "," sshdCfg.settings.Ciphers} 193 '' 194 + lib.optionalString (sshdCfg.settings.Macs != null) '' 195 MACs ${concatStringsSep "," sshdCfg.settings.Macs} 196 '' 197 + '' 198 199 LogLevel ${sshdCfg.settings.LogLevel} 200 201 ${ 202 if sshdCfg.settings.UseDns then 203 '' 204 UseDNS yes 205 '' 206 else 207 '' 208 UseDNS no 209 '' 210 } 211 212 ${optionalString (!config.boot.initrd.systemd.enable) '' 213 SshdAuthPath /bin/sshd-auth 214 SshdSessionPath /bin/sshd-session 215 ''} 216 217 ${cfg.extraConfig} 218 ''; 219 in 220 mkIf enabled { 221 assertions = [ 222 { 223 assertion = cfg.authorizedKeys != [ ] || cfg.authorizedKeyFiles != [ ]; 224 message = "You should specify at least one authorized key for initrd SSH"; 225 } 226 227 { 228 assertion = (cfg.hostKeys != [ ]) || cfg.ignoreEmptyHostKeys; 229 message = '' 230 You must now pre-generate the host keys for initrd SSH. 231 See the boot.initrd.network.ssh.hostKeys documentation 232 for instructions. 233 ''; 234 } 235 ]; 236 237 warnings = lib.optional (config.boot.initrd.systemd.enable && cfg.shell != null) '' 238 Please set 'boot.initrd.systemd.users.root.shell' instead of 'boot.initrd.network.ssh.shell' 239 ''; 240 241 boot.initrd.extraUtilsCommands = mkIf (!config.boot.initrd.systemd.enable) '' 242 copy_bin_and_libs ${package}/bin/sshd 243 copy_bin_and_libs ${package}/libexec/sshd-auth 244 copy_bin_and_libs ${package}/libexec/sshd-session 245 cp -pv ${pkgs.glibc.out}/lib/libnss_files.so.* $out/lib 246 ''; 247 248 boot.initrd.extraUtilsCommandsTest = mkIf (!config.boot.initrd.systemd.enable) '' 249 # sshd requires a host key to check config, so we pass in the test's 250 tmpkey="$(mktemp initrd-ssh-testkey.XXXXXXXXXX)" 251 cp "${../../../tests/initrd-network-ssh/ssh_host_ed25519_key}" "$tmpkey" 252 # keys from Nix store are world-readable, which sshd doesn't like 253 chmod 600 "$tmpkey" 254 echo -n ${escapeShellArg sshdConfig} | 255 $out/bin/sshd -t -f /dev/stdin \ 256 -h "$tmpkey" 257 rm "$tmpkey" 258 ''; 259 260 boot.initrd.network.postCommands = mkIf (!config.boot.initrd.systemd.enable) '' 261 echo '${shell}' > /etc/shells 262 echo 'root:x:0:0:root:/root:${shell}' > /etc/passwd 263 echo 'sshd:x:1:1:sshd:/var/empty:/bin/nologin' >> /etc/passwd 264 echo 'passwd: files' > /etc/nsswitch.conf 265 266 mkdir -p /var/log /var/empty 267 touch /var/log/lastlog 268 269 mkdir -p /etc/ssh 270 echo -n ${escapeShellArg sshdConfig} > /etc/ssh/sshd_config 271 272 echo "export PATH=$PATH" >> /etc/profile 273 echo "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH" >> /etc/profile 274 275 mkdir -p /root/.ssh 276 ${concatStrings ( 277 map (key: '' 278 echo ${escapeShellArg key} >> /root/.ssh/authorized_keys 279 '') cfg.authorizedKeys 280 )} 281 ${concatStrings ( 282 map (keyFile: '' 283 cat ${keyFile} >> /root/.ssh/authorized_keys 284 '') cfg.authorizedKeyFiles 285 )} 286 287 ${flip concatMapStrings cfg.hostKeys (path: '' 288 # keys from Nix store are world-readable, which sshd doesn't like 289 chmod 0600 "${initrdKeyPath path}" 290 '')} 291 292 /bin/sshd -e 293 ''; 294 295 boot.initrd.postMountCommands = mkIf (!config.boot.initrd.systemd.enable) '' 296 # Stop sshd cleanly before stage 2. 297 # 298 # If you want to keep it around to debug post-mount SSH issues, 299 # run `touch /.keep_sshd` (either from an SSH session or in 300 # another initrd hook like preDeviceCommands). 301 if ! [ -e /.keep_sshd ]; then 302 pkill -x sshd 303 fi 304 ''; 305 306 boot.initrd.secrets = listToAttrs ( 307 map (path: nameValuePair (initrdKeyPath path) path) cfg.hostKeys 308 ); 309 310 # Systemd initrd stuff 311 boot.initrd.systemd = mkIf config.boot.initrd.systemd.enable { 312 users.sshd = { 313 uid = 1; 314 group = "sshd"; 315 }; 316 groups.sshd = { 317 gid = 1; 318 }; 319 320 users.root.shell = mkIf ( 321 config.boot.initrd.network.ssh.shell != null 322 ) config.boot.initrd.network.ssh.shell; 323 324 contents = { 325 "/etc/ssh/sshd_config".text = sshdConfig; 326 "/etc/ssh/authorized_keys.d/root".text = concatStringsSep "\n" ( 327 config.boot.initrd.network.ssh.authorizedKeys 328 ++ (map (file: lib.fileContents file) config.boot.initrd.network.ssh.authorizedKeyFiles) 329 ); 330 }; 331 storePaths = [ 332 "${package}/bin/sshd" 333 "${package}/libexec/sshd-auth" 334 "${package}/libexec/sshd-session" 335 ]; 336 337 services.sshd = { 338 description = "SSH Daemon"; 339 wantedBy = [ "initrd.target" ]; 340 after = [ 341 "network.target" 342 "initrd-nixos-copy-secrets.service" 343 ]; 344 before = [ "shutdown.target" ]; 345 conflicts = [ "shutdown.target" ]; 346 347 # Keys from Nix store are world-readable, which sshd doesn't 348 # like. If this were a real nix store and not the initrd, we 349 # neither would nor could do this 350 preStart = flip concatMapStrings cfg.hostKeys (path: '' 351 /bin/chmod 0600 "${initrdKeyPath path}" 352 ''); 353 unitConfig.DefaultDependencies = false; 354 serviceConfig = { 355 ExecStart = "${package}/bin/sshd -D -f /etc/ssh/sshd_config"; 356 Type = "simple"; 357 KillMode = "process"; 358 Restart = "on-failure"; 359 }; 360 }; 361 }; 362 363 }; 364 365}