at master 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 UsePAM no 176 Port ${toString cfg.port} 177 178 PasswordAuthentication no 179 AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys2 /etc/ssh/authorized_keys.d/%u 180 ChallengeResponseAuthentication no 181 182 ${flip concatMapStrings cfg.hostKeys (path: '' 183 HostKey ${initrdKeyPath path} 184 '')} 185 186 '' 187 + lib.optionalString (sshdCfg.settings.KexAlgorithms != null) '' 188 KexAlgorithms ${concatStringsSep "," sshdCfg.settings.KexAlgorithms} 189 '' 190 + lib.optionalString (sshdCfg.settings.Ciphers != null) '' 191 Ciphers ${concatStringsSep "," sshdCfg.settings.Ciphers} 192 '' 193 + lib.optionalString (sshdCfg.settings.Macs != null) '' 194 MACs ${concatStringsSep "," sshdCfg.settings.Macs} 195 '' 196 + '' 197 198 LogLevel ${sshdCfg.settings.LogLevel} 199 200 ${ 201 if sshdCfg.settings.UseDns then 202 '' 203 UseDNS yes 204 '' 205 else 206 '' 207 UseDNS no 208 '' 209 } 210 211 ${optionalString (!config.boot.initrd.systemd.enable) '' 212 SshdAuthPath /bin/sshd-auth 213 SshdSessionPath /bin/sshd-session 214 ''} 215 216 ${cfg.extraConfig} 217 ''; 218 in 219 mkIf enabled { 220 assertions = [ 221 { 222 assertion = cfg.authorizedKeys != [ ] || cfg.authorizedKeyFiles != [ ]; 223 message = "You should specify at least one authorized key for initrd SSH"; 224 } 225 226 { 227 assertion = (cfg.hostKeys != [ ]) || cfg.ignoreEmptyHostKeys; 228 message = '' 229 You must now pre-generate the host keys for initrd SSH. 230 See the boot.initrd.network.ssh.hostKeys documentation 231 for instructions. 232 ''; 233 } 234 ]; 235 236 warnings = lib.optional (config.boot.initrd.systemd.enable && cfg.shell != null) '' 237 Please set 'boot.initrd.systemd.users.root.shell' instead of 'boot.initrd.network.ssh.shell' 238 ''; 239 240 boot.initrd.extraUtilsCommands = mkIf (!config.boot.initrd.systemd.enable) '' 241 copy_bin_and_libs ${package}/bin/sshd 242 copy_bin_and_libs ${package}/libexec/sshd-auth 243 copy_bin_and_libs ${package}/libexec/sshd-session 244 cp -pv ${pkgs.glibc.out}/lib/libnss_files.so.* $out/lib 245 ''; 246 247 boot.initrd.extraUtilsCommandsTest = mkIf (!config.boot.initrd.systemd.enable) '' 248 # sshd requires a host key to check config, so we pass in the test's 249 tmpkey="$(mktemp initrd-ssh-testkey.XXXXXXXXXX)" 250 cp "${../../../tests/initrd-network-ssh/ssh_host_ed25519_key}" "$tmpkey" 251 # keys from Nix store are world-readable, which sshd doesn't like 252 chmod 600 "$tmpkey" 253 echo -n ${escapeShellArg sshdConfig} | 254 $out/bin/sshd -t -f /dev/stdin \ 255 -h "$tmpkey" 256 rm "$tmpkey" 257 ''; 258 259 boot.initrd.network.postCommands = mkIf (!config.boot.initrd.systemd.enable) '' 260 echo '${shell}' > /etc/shells 261 echo 'root:x:0:0:root:/root:${shell}' > /etc/passwd 262 echo 'sshd:x:1:1:sshd:/var/empty:/bin/nologin' >> /etc/passwd 263 echo 'passwd: files' > /etc/nsswitch.conf 264 265 mkdir -p /var/log /var/empty 266 touch /var/log/lastlog 267 268 mkdir -p /etc/ssh 269 echo -n ${escapeShellArg sshdConfig} > /etc/ssh/sshd_config 270 271 echo "export PATH=$PATH" >> /etc/profile 272 echo "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH" >> /etc/profile 273 274 mkdir -p /root/.ssh 275 ${concatStrings ( 276 map (key: '' 277 echo ${escapeShellArg key} >> /root/.ssh/authorized_keys 278 '') cfg.authorizedKeys 279 )} 280 ${concatStrings ( 281 map (keyFile: '' 282 cat ${keyFile} >> /root/.ssh/authorized_keys 283 '') cfg.authorizedKeyFiles 284 )} 285 286 ${flip concatMapStrings cfg.hostKeys (path: '' 287 # keys from Nix store are world-readable, which sshd doesn't like 288 chmod 0600 "${initrdKeyPath path}" 289 '')} 290 291 /bin/sshd -e 292 ''; 293 294 boot.initrd.postMountCommands = mkIf (!config.boot.initrd.systemd.enable) '' 295 # Stop sshd cleanly before stage 2. 296 # 297 # If you want to keep it around to debug post-mount SSH issues, 298 # run `touch /.keep_sshd` (either from an SSH session or in 299 # another initrd hook like preDeviceCommands). 300 if ! [ -e /.keep_sshd ]; then 301 pkill -x sshd 302 fi 303 ''; 304 305 boot.initrd.secrets = listToAttrs ( 306 map (path: nameValuePair (initrdKeyPath path) path) cfg.hostKeys 307 ); 308 309 # Systemd initrd stuff 310 boot.initrd.systemd = mkIf config.boot.initrd.systemd.enable { 311 users.sshd = { 312 uid = 1; 313 group = "sshd"; 314 }; 315 groups.sshd = { 316 gid = 1; 317 }; 318 319 users.root.shell = mkIf ( 320 config.boot.initrd.network.ssh.shell != null 321 ) config.boot.initrd.network.ssh.shell; 322 323 contents = { 324 "/etc/ssh/sshd_config".text = sshdConfig; 325 "/etc/ssh/authorized_keys.d/root".text = concatStringsSep "\n" ( 326 config.boot.initrd.network.ssh.authorizedKeys 327 ++ (map (file: lib.fileContents file) config.boot.initrd.network.ssh.authorizedKeyFiles) 328 ); 329 }; 330 storePaths = [ 331 "${package}/bin/sshd" 332 "${package}/libexec/sshd-auth" 333 "${package}/libexec/sshd-session" 334 ]; 335 336 services.sshd = { 337 description = "SSH Daemon"; 338 wantedBy = [ "initrd.target" ]; 339 after = [ 340 "network.target" 341 "initrd-nixos-copy-secrets.service" 342 ]; 343 before = [ "shutdown.target" ]; 344 conflicts = [ "shutdown.target" ]; 345 346 # Keys from Nix store are world-readable, which sshd doesn't 347 # like. If this were a real nix store and not the initrd, we 348 # neither would nor could do this 349 preStart = flip concatMapStrings cfg.hostKeys (path: '' 350 /bin/chmod 0600 "${initrdKeyPath path}" 351 ''); 352 unitConfig.DefaultDependencies = false; 353 serviceConfig = { 354 ExecStart = "${package}/bin/sshd -D -f /etc/ssh/sshd_config"; 355 Type = "simple"; 356 KillMode = "process"; 357 Restart = "on-failure"; 358 }; 359 }; 360 }; 361 362 }; 363 364}