systemd-initrd: sshd

This commit is contained in:
Will Fancher 2022-08-03 06:36:11 -04:00
parent 748f1329fc
commit 0698a1cf04
3 changed files with 137 additions and 10 deletions

View file

@ -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
@ -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.
'';
@ -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: ''
@ -142,7 +149,7 @@ in
${cfg.extraConfig}
'';
in mkIf (config.boot.initrd.network.enable && cfg.enable) {
in mkIf enabled {
assertions = [
{
assertion = cfg.authorizedKeys != [];
@ -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"
@ -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
@ -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,
@ -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";
};
};
};
};
}

View file

@ -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 {};

View file

@ -0,0 +1,82 @@
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 /'")
'';
})