From 014accdc9cb0c4ddce5b24c239f09d1d53b6ecf1 Mon Sep 17 00:00:00 2001 From: andre4ik3 Date: Mon, 11 Aug 2025 04:46:21 +0000 Subject: [PATCH] nixos/tests/systemd-homed: add first boot, sudo, deactivation, and SSH tests --- nixos/tests/systemd-homed.nix | 189 ++++++++++++++++++++-------------- 1 file changed, 110 insertions(+), 79 deletions(-) diff --git a/nixos/tests/systemd-homed.nix b/nixos/tests/systemd-homed.nix index 766fd4f2cc19..41aff0ecedba 100644 --- a/nixos/tests/systemd-homed.nix +++ b/nixos/tests/systemd-homed.nix @@ -1,101 +1,132 @@ -{ pkgs, lib, ... }: let - password = "foobarfoo"; - newPass = "barfoobar"; + username = "test-homed-user"; + initialPassword = "foobarfoo"; + newPassword = "barfoobar"; in + { name = "systemd-homed"; - nodes.machine = - { config, pkgs, ... }: - { - services.homed.enable = true; - users.users.test-normal-user = { - extraGroups = [ "wheel" ]; - isNormalUser = true; - initialPassword = password; + nodes = { + machine = + { ... }: + { + services = { + homed.enable = true; + openssh.enable = true; + }; + + # Prevent nixbld users from showing up as regular users, required for + # first boot prompt + nix.settings = { + experimental-features = [ "auto-allocate-uids" ]; + auto-allocate-uids = true; + }; }; - }; + + sshClient = + { pkgs, ... }: + { + services = { + homed.enable = true; + userdbd.silenceHighSystemUsers = true; + }; + + # Regular user, should prevent first boot prompt + users.users.test-normal-user = { + extraGroups = [ "wheel" ]; + isNormalUser = true; + inherit initialPassword; + }; + }; + }; + testScript = '' - def switchTTY(number): - machine.send_key(f"alt-f{number}") - machine.wait_until_succeeds(f"[ $(fgconsole) = {number} ]") - machine.wait_for_unit(f"getty@tty{number}.service") - machine.wait_until_succeeds(f"pgrep -f 'agetty.*tty{number}'") + start_all() - machine.wait_for_unit("multi-user.target") - - # Smoke test to make sure the pam changes didn't break regular users. - machine.wait_until_succeeds("pgrep -f 'agetty.*tty1'") - with subtest("login as regular user"): - switchTTY(2) - machine.wait_until_tty_matches("2", "login: ") - machine.send_chars("test-normal-user\n") - machine.wait_until_tty_matches("2", "login: test-normal-user") - machine.wait_until_tty_matches("2", "Password: ") - machine.send_chars("${password}\n") - machine.wait_until_succeeds("pgrep -u test-normal-user bash") - machine.send_chars("whoami > /tmp/1\n") - machine.wait_for_file("/tmp/1") - assert "test-normal-user" in machine.succeed("cat /tmp/1") - - with subtest("create homed encrypted user"): - # TODO: Figure out how to pass password manually. - # - # This environment variable is used for homed internal testing - # and is not documented. - machine.succeed("NEWPASSWORD=${password} homectl create --shell=/run/current-system/sw/bin/bash --storage=luks -G wheel test-homed-user") + with subtest("create systemd-homed user on first boot prompt"): + machine.wait_for_unit("systemd-homed.service") + machine.wait_until_tty_matches("1", "-- Press any key to proceed --") + machine.send_chars(" ") + machine.wait_until_tty_matches("1", "Please enter user name") + machine.send_chars("${username}\n") + machine.wait_until_tty_matches("1", "Please enter an auxiliary group") + machine.send_chars("wheel\n") + machine.wait_until_tty_matches("1", "Please enter an auxiliary group") + machine.send_chars("\n") + machine.wait_until_tty_matches("1", "Please enter the shell to use") + machine.send_chars("/bin/sh\n") + machine.wait_until_tty_matches("1", "Please enter new password") + machine.send_chars("${initialPassword}\n") + machine.wait_until_tty_matches("1", "(repeat)") + machine.send_chars("${initialPassword}\n") with subtest("login as homed user"): - switchTTY(3) - machine.wait_until_tty_matches("3", "login: ") - machine.send_chars("test-homed-user\n") - machine.wait_until_tty_matches("3", "login: test-homed-user") - machine.wait_until_tty_matches("3", "Password: ") - machine.send_chars("${password}\n") - machine.wait_until_succeeds("pgrep -t tty3 -u test-homed-user bash") + machine.wait_until_tty_matches("1", "login: ") + machine.send_chars("${username}\n") + machine.wait_until_tty_matches("1", "Password: ") + machine.send_chars("${initialPassword}\n") + machine.wait_until_succeeds("pgrep -u ${username} -t tty1 sh") machine.send_chars("whoami > /tmp/2\n") machine.wait_for_file("/tmp/2") - assert "test-homed-user" in machine.succeed("cat /tmp/2") + assert "${username}" in machine.succeed("cat /tmp/2") + + # Smoke test to make sure the pam changes didn't break regular users. + # Since homed is also enabled in the sshClient, it also tests the first + # boot prompt did not occur. + with subtest("login as regular user"): + sshClient.wait_until_tty_matches("1", "login: ") + sshClient.send_chars("test-normal-user\n") + sshClient.wait_until_tty_matches("1", "Password: ") + sshClient.send_chars("${initialPassword}\n") + sshClient.wait_until_succeeds("pgrep -u test-normal-user bash") + sshClient.send_chars("whoami > /tmp/1\n") + sshClient.wait_for_file("/tmp/1") + assert "test-normal-user" in sshClient.succeed("cat /tmp/1") + + with subtest("add homed ssh authorized key"): + sshClient.send_chars('ssh-keygen -t ed25519 -f /tmp/id_ed25519 -N ""\n') + sshClient.wait_for_file("/tmp/id_ed25519.pub") + public_key = sshClient.succeed('cat /tmp/id_ed25519.pub') + public_key = public_key.strip() + machine.succeed(f"homectl update ${username} --offline --ssh-authorized-keys '{public_key}'") + machine.succeed("userdbctl ssh-authorized-keys ${username} | grep ed25519") with subtest("change homed user password"): - switchTTY(4) - machine.wait_until_tty_matches("4", "login: ") - machine.send_chars("test-homed-user\n") - machine.wait_until_tty_matches("4", "login: test-homed-user") - machine.wait_until_tty_matches("4", "Password: ") - machine.send_chars("${password}\n") - machine.wait_until_succeeds("pgrep -t tty4 -u test-homed-user bash") - machine.send_chars("passwd\n") + machine.send_chars("passwd; echo $? > /tmp/3\n") # homed does it in a weird order, it asks for new passes, then it asks # for the old one. - machine.sleep(2) - machine.send_chars("${newPass}\n") - machine.sleep(2) - machine.send_chars("${newPass}\n") + machine.wait_until_tty_matches("1", "New password: ") + machine.send_chars("${newPassword}\n") + machine.wait_until_tty_matches("1", "Retype new password: ") + machine.send_chars("${newPassword}\n") + #machine.wait_until_tty_matches("1", "Password: ") machine.sleep(4) - machine.send_chars("${password}\n") - machine.wait_until_fails("pgrep -t tty4 passwd") + machine.send_chars("${initialPassword}\n") + machine.wait_for_file("/tmp/3") + assert "0\n" == machine.succeed("cat /tmp/3") - @polling_condition - def not_logged_in_tty5(): - machine.fail("pgrep -t tty5 bash") - - switchTTY(5) - with not_logged_in_tty5: # type: ignore[union-attr] - machine.wait_until_tty_matches("5", "login: ") - machine.send_chars("test-homed-user\n") - machine.wait_until_tty_matches("5", "login: test-homed-user") - machine.wait_until_tty_matches("5", "Password: ") - machine.send_chars("${password}\n") - machine.wait_until_tty_matches("5", "Password incorrect or not sufficient for authentication of user test-homed-user.") - machine.wait_until_tty_matches("5", "Sorry, try again: ") - machine.send_chars("${newPass}\n") - machine.send_chars("whoami > /tmp/4\n") + with subtest("escalate to root from homed user"): + # Also tests the user is in wheel. + machine.send_chars("sudo id | tee /tmp/4\n") + machine.wait_until_tty_matches("1", "password for ${username}") + machine.send_chars("${newPassword}\n") machine.wait_for_file("/tmp/4") - assert "test-homed-user" in machine.succeed("cat /tmp/4") + machine.wait_until_succeeds("grep uid=0 /tmp/4") - with subtest("homed user should be in wheel according to NSS"): - machine.succeed("userdbctl group wheel -s io.systemd.NameServiceSwitch | grep test-homed-user") + with subtest("log out and deactivate homed user's home area"): + machine.send_chars("exit\n") + machine.wait_until_succeeds("homectl inspect ${username} | grep 'State: inactive'") + + with subtest("ssh as homed user"): + sshClient.send_chars("ssh -o StrictHostKeyChecking=no -i /tmp/id_ed25519 ${username}@machine\n") + sshClient.wait_until_tty_matches("1", "Please enter password for user") + sshClient.send_chars("${newPassword}\n") + machine.wait_until_succeeds("pgrep -u ${username} sh") + sshClient.send_chars("whoami > /tmp/5\n") + machine.wait_for_file("/tmp/5") + assert "${username}" in machine.succeed("cat /tmp/5") + sshClient.send_chars("exit\n") # ssh + sshClient.send_chars("exit\n") # sh ''; }