Loading nixos/modules/system/boot/initrd-ssh.nix +54 −10 Original line number Diff line number Diff line Loading @@ -5,6 +5,10 @@ with lib; let cfg = config.boot.initrd.network.ssh; shell = if cfg.shell == null then "/bin/ash" else cfg.shell; inherit (config.programs.ssh) package; enabled = let initrd = config.boot.initrd; in (initrd.network.enable || initrd.systemd.network.enable) && cfg.enable; in Loading Loading @@ -33,8 +37,9 @@ in }; shell = mkOption { type = types.str; default = "/bin/ash"; type = types.nullOr types.str; default = null; defaultText = ''"/bin/ash"''; description = lib.mdDoc '' Login shell of the remote user. Can be used to limit actions user can do. ''; Loading Loading @@ -119,9 +124,11 @@ in sshdCfg = config.services.openssh; sshdConfig = '' UsePAM no Port ${toString cfg.port} PasswordAuthentication no AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys2 /etc/ssh/authorized_keys.d/%u ChallengeResponseAuthentication no ${flip concatMapStrings cfg.hostKeys (path: '' Loading @@ -142,7 +149,7 @@ in ${cfg.extraConfig} ''; in mkIf (config.boot.initrd.network.enable && cfg.enable) { in mkIf enabled { assertions = [ { assertion = cfg.authorizedKeys != []; Loading @@ -157,14 +164,19 @@ in for instructions. ''; } { assertion = config.boot.initrd.systemd.enable -> cfg.shell == null; message = "systemd stage 1 does not support boot.initrd.network.ssh.shell"; } ]; boot.initrd.extraUtilsCommands = '' copy_bin_and_libs ${pkgs.openssh}/bin/sshd boot.initrd.extraUtilsCommands = mkIf (!config.boot.initrd.systemd.enable) '' copy_bin_and_libs ${package}/bin/sshd cp -pv ${pkgs.glibc.out}/lib/libnss_files.so.* $out/lib ''; boot.initrd.extraUtilsCommandsTest = '' boot.initrd.extraUtilsCommandsTest = mkIf (!config.boot.initrd.systemd.enable) '' # sshd requires a host key to check config, so we pass in the test's tmpkey="$(mktemp initrd-ssh-testkey.XXXXXXXXXX)" cp "${../../../tests/initrd-network-ssh/ssh_host_ed25519_key}" "$tmpkey" Loading @@ -176,9 +188,9 @@ in rm "$tmpkey" ''; boot.initrd.network.postCommands = '' echo '${cfg.shell}' > /etc/shells echo 'root:x:0:0:root:/root:${cfg.shell}' > /etc/passwd boot.initrd.network.postCommands = mkIf (!config.boot.initrd.systemd.enable) '' echo '${shell}' > /etc/shells echo 'root:x:0:0:root:/root:${shell}' > /etc/passwd echo 'sshd:x:1:1:sshd:/var/empty:/bin/nologin' >> /etc/passwd echo 'passwd: files' > /etc/nsswitch.conf Loading @@ -204,7 +216,7 @@ in /bin/sshd -e ''; boot.initrd.postMountCommands = '' boot.initrd.postMountCommands = mkIf (!config.boot.initrd.systemd.enable) '' # Stop sshd cleanly before stage 2. # # If you want to keep it around to debug post-mount SSH issues, Loading @@ -217,6 +229,38 @@ in boot.initrd.secrets = listToAttrs (map (path: nameValuePair (initrdKeyPath path) path) cfg.hostKeys); # Systemd initrd stuff boot.initrd.systemd = mkIf config.boot.initrd.systemd.enable { users.sshd = { uid = 1; group = "sshd"; }; groups.sshd = { gid = 1; }; contents."/etc/ssh/authorized_keys.d/root".text = concatStringsSep "\n" config.boot.initrd.network.ssh.authorizedKeys; contents."/etc/ssh/sshd_config".text = sshdConfig; storePaths = ["${package}/bin/sshd"]; services.sshd = { description = "SSH Daemon"; wantedBy = ["initrd.target"]; after = ["network.target" "initrd-nixos-copy-secrets.service"]; # Keys from Nix store are world-readable, which sshd doesn't # like. If this were a real nix store and not the initrd, we # neither would nor could do this preStart = flip concatMapStrings cfg.hostKeys (path: '' /bin/chmod 0600 "${initrdKeyPath path}" ''); unitConfig.DefaultDependencies = false; serviceConfig = { ExecStart = "${package}/bin/sshd -D -f /etc/ssh/sshd_config"; Type = "simple"; KillMode = "process"; Restart = "on-failure"; }; }; }; }; } nixos/tests/all-tests.nix +1 −0 Original line number Diff line number Diff line Loading @@ -678,6 +678,7 @@ in { systemd-initrd-swraid = handleTest ./systemd-initrd-swraid.nix {}; systemd-initrd-vconsole = handleTest ./systemd-initrd-vconsole.nix {}; systemd-initrd-networkd = handleTest ./systemd-initrd-networkd.nix {}; systemd-initrd-networkd-ssh = handleTest ./systemd-initrd-networkd-ssh.nix {}; systemd-journal = handleTest ./systemd-journal.nix {}; systemd-machinectl = handleTest ./systemd-machinectl.nix {}; systemd-networkd = handleTest ./systemd-networkd.nix {}; Loading nixos/tests/systemd-initrd-networkd-ssh.nix 0 → 100644 +82 −0 Original line number Diff line number Diff line import ./make-test-python.nix ({ lib, ... }: { name = "systemd-initrd-network-ssh"; meta.maintainers = [ lib.maintainers.elvishjerricco ]; nodes = with lib; { server = { config, pkgs, ... }: { environment.systemPackages = [pkgs.cryptsetup]; boot.loader.systemd-boot.enable = true; boot.loader.timeout = 0; virtualisation = { emptyDiskImages = [ 4096 ]; useBootLoader = true; useEFIBoot = true; }; specialisation.encrypted-root.configuration = { virtualisation.bootDevice = "/dev/mapper/root"; boot.initrd.luks.devices = lib.mkVMOverride { root.device = "/dev/vdc"; }; boot.initrd.systemd.enable = true; boot.initrd.network = { enable = true; ssh = { enable = true; authorizedKeys = [ (readFile ./initrd-network-ssh/id_ed25519.pub) ]; port = 22; # Terrible hack so it works with useBootLoader hostKeys = [ { outPath = "${./initrd-network-ssh/ssh_host_ed25519_key}"; } ]; }; }; }; }; client = { config, ... }: { environment.etc = { knownHosts = { text = concatStrings [ "server," "${ toString (head (splitString " " (toString (elemAt (splitString "\n" config.networking.extraHosts) 2)))) } " "${readFile ./initrd-network-ssh/ssh_host_ed25519_key.pub}" ]; }; sshKey = { source = ./initrd-network-ssh/id_ed25519; mode = "0600"; }; }; }; }; testScript = '' start_all() def ssh_is_up(_) -> bool: status, _ = client.execute("nc -z server 22") return status == 0 server.wait_for_unit("multi-user.target") server.succeed( "echo somepass | cryptsetup luksFormat --type=luks2 /dev/vdc", "bootctl set-default nixos-generation-1-specialisation-encrypted-root.conf", "sync", ) server.shutdown() server.start() client.wait_for_unit("network.target") with client.nested("waiting for SSH server to come up"): retry(ssh_is_up) client.succeed( "echo somepass | ssh -i /etc/sshKey -o UserKnownHostsFile=/etc/knownHosts server 'systemd-tty-ask-password-agent' & exit" ) server.wait_for_unit("multi-user.target") server.succeed("mount | grep '/dev/mapper/root on /'") ''; }) Loading
nixos/modules/system/boot/initrd-ssh.nix +54 −10 Original line number Diff line number Diff line Loading @@ -5,6 +5,10 @@ with lib; let cfg = config.boot.initrd.network.ssh; shell = if cfg.shell == null then "/bin/ash" else cfg.shell; inherit (config.programs.ssh) package; enabled = let initrd = config.boot.initrd; in (initrd.network.enable || initrd.systemd.network.enable) && cfg.enable; in Loading Loading @@ -33,8 +37,9 @@ in }; shell = mkOption { type = types.str; default = "/bin/ash"; type = types.nullOr types.str; default = null; defaultText = ''"/bin/ash"''; description = lib.mdDoc '' Login shell of the remote user. Can be used to limit actions user can do. ''; Loading Loading @@ -119,9 +124,11 @@ in sshdCfg = config.services.openssh; sshdConfig = '' UsePAM no Port ${toString cfg.port} PasswordAuthentication no AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys2 /etc/ssh/authorized_keys.d/%u ChallengeResponseAuthentication no ${flip concatMapStrings cfg.hostKeys (path: '' Loading @@ -142,7 +149,7 @@ in ${cfg.extraConfig} ''; in mkIf (config.boot.initrd.network.enable && cfg.enable) { in mkIf enabled { assertions = [ { assertion = cfg.authorizedKeys != []; Loading @@ -157,14 +164,19 @@ in for instructions. ''; } { assertion = config.boot.initrd.systemd.enable -> cfg.shell == null; message = "systemd stage 1 does not support boot.initrd.network.ssh.shell"; } ]; boot.initrd.extraUtilsCommands = '' copy_bin_and_libs ${pkgs.openssh}/bin/sshd boot.initrd.extraUtilsCommands = mkIf (!config.boot.initrd.systemd.enable) '' copy_bin_and_libs ${package}/bin/sshd cp -pv ${pkgs.glibc.out}/lib/libnss_files.so.* $out/lib ''; boot.initrd.extraUtilsCommandsTest = '' boot.initrd.extraUtilsCommandsTest = mkIf (!config.boot.initrd.systemd.enable) '' # sshd requires a host key to check config, so we pass in the test's tmpkey="$(mktemp initrd-ssh-testkey.XXXXXXXXXX)" cp "${../../../tests/initrd-network-ssh/ssh_host_ed25519_key}" "$tmpkey" Loading @@ -176,9 +188,9 @@ in rm "$tmpkey" ''; boot.initrd.network.postCommands = '' echo '${cfg.shell}' > /etc/shells echo 'root:x:0:0:root:/root:${cfg.shell}' > /etc/passwd boot.initrd.network.postCommands = mkIf (!config.boot.initrd.systemd.enable) '' echo '${shell}' > /etc/shells echo 'root:x:0:0:root:/root:${shell}' > /etc/passwd echo 'sshd:x:1:1:sshd:/var/empty:/bin/nologin' >> /etc/passwd echo 'passwd: files' > /etc/nsswitch.conf Loading @@ -204,7 +216,7 @@ in /bin/sshd -e ''; boot.initrd.postMountCommands = '' boot.initrd.postMountCommands = mkIf (!config.boot.initrd.systemd.enable) '' # Stop sshd cleanly before stage 2. # # If you want to keep it around to debug post-mount SSH issues, Loading @@ -217,6 +229,38 @@ in boot.initrd.secrets = listToAttrs (map (path: nameValuePair (initrdKeyPath path) path) cfg.hostKeys); # Systemd initrd stuff boot.initrd.systemd = mkIf config.boot.initrd.systemd.enable { users.sshd = { uid = 1; group = "sshd"; }; groups.sshd = { gid = 1; }; contents."/etc/ssh/authorized_keys.d/root".text = concatStringsSep "\n" config.boot.initrd.network.ssh.authorizedKeys; contents."/etc/ssh/sshd_config".text = sshdConfig; storePaths = ["${package}/bin/sshd"]; services.sshd = { description = "SSH Daemon"; wantedBy = ["initrd.target"]; after = ["network.target" "initrd-nixos-copy-secrets.service"]; # Keys from Nix store are world-readable, which sshd doesn't # like. If this were a real nix store and not the initrd, we # neither would nor could do this preStart = flip concatMapStrings cfg.hostKeys (path: '' /bin/chmod 0600 "${initrdKeyPath path}" ''); unitConfig.DefaultDependencies = false; serviceConfig = { ExecStart = "${package}/bin/sshd -D -f /etc/ssh/sshd_config"; Type = "simple"; KillMode = "process"; Restart = "on-failure"; }; }; }; }; }
nixos/tests/all-tests.nix +1 −0 Original line number Diff line number Diff line Loading @@ -678,6 +678,7 @@ in { systemd-initrd-swraid = handleTest ./systemd-initrd-swraid.nix {}; systemd-initrd-vconsole = handleTest ./systemd-initrd-vconsole.nix {}; systemd-initrd-networkd = handleTest ./systemd-initrd-networkd.nix {}; systemd-initrd-networkd-ssh = handleTest ./systemd-initrd-networkd-ssh.nix {}; systemd-journal = handleTest ./systemd-journal.nix {}; systemd-machinectl = handleTest ./systemd-machinectl.nix {}; systemd-networkd = handleTest ./systemd-networkd.nix {}; Loading
nixos/tests/systemd-initrd-networkd-ssh.nix 0 → 100644 +82 −0 Original line number Diff line number Diff line import ./make-test-python.nix ({ lib, ... }: { name = "systemd-initrd-network-ssh"; meta.maintainers = [ lib.maintainers.elvishjerricco ]; nodes = with lib; { server = { config, pkgs, ... }: { environment.systemPackages = [pkgs.cryptsetup]; boot.loader.systemd-boot.enable = true; boot.loader.timeout = 0; virtualisation = { emptyDiskImages = [ 4096 ]; useBootLoader = true; useEFIBoot = true; }; specialisation.encrypted-root.configuration = { virtualisation.bootDevice = "/dev/mapper/root"; boot.initrd.luks.devices = lib.mkVMOverride { root.device = "/dev/vdc"; }; boot.initrd.systemd.enable = true; boot.initrd.network = { enable = true; ssh = { enable = true; authorizedKeys = [ (readFile ./initrd-network-ssh/id_ed25519.pub) ]; port = 22; # Terrible hack so it works with useBootLoader hostKeys = [ { outPath = "${./initrd-network-ssh/ssh_host_ed25519_key}"; } ]; }; }; }; }; client = { config, ... }: { environment.etc = { knownHosts = { text = concatStrings [ "server," "${ toString (head (splitString " " (toString (elemAt (splitString "\n" config.networking.extraHosts) 2)))) } " "${readFile ./initrd-network-ssh/ssh_host_ed25519_key.pub}" ]; }; sshKey = { source = ./initrd-network-ssh/id_ed25519; mode = "0600"; }; }; }; }; testScript = '' start_all() def ssh_is_up(_) -> bool: status, _ = client.execute("nc -z server 22") return status == 0 server.wait_for_unit("multi-user.target") server.succeed( "echo somepass | cryptsetup luksFormat --type=luks2 /dev/vdc", "bootctl set-default nixos-generation-1-specialisation-encrypted-root.conf", "sync", ) server.shutdown() server.start() client.wait_for_unit("network.target") with client.nested("waiting for SSH server to come up"): retry(ssh_is_up) client.succeed( "echo somepass | ssh -i /etc/sshKey -o UserKnownHostsFile=/etc/knownHosts server 'systemd-tty-ask-password-agent' & exit" ) server.wait_for_unit("multi-user.target") server.succeed("mount | grep '/dev/mapper/root on /'") ''; })