systemd-initrd: sshd

Changed files
+137 -10
nixos
+54 -10
nixos/modules/system/boot/initrd-ssh.nix
···
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
···
};
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.
'';
···
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: ''
···
${cfg.extraConfig}
'';
-
in mkIf (config.boot.initrd.network.enable && cfg.enable) {
+
in mkIf enabled {
assertions = [
{
assertion = cfg.authorizedKeys != [];
···
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"
···
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
···
/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,
···
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";
+
};
+
};
+
};
+
};
}
+1
nixos/tests/all-tests.nix
···
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 {};
+82
nixos/tests/systemd-initrd-networkd-ssh.nix
···
+
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 /'")
+
'';
+
})