nixos: add systemd-homed support

As a start, it's not very configurable, but works pretty well.
This commit is contained in:
Leorize 2022-12-08 02:49:12 -06:00
parent 0cc87ab901
commit 05420f34cf
8 changed files with 171 additions and 5 deletions

View File

@ -1276,6 +1276,7 @@
./system/boot/systemd/tmpfiles.nix
./system/boot/systemd/user.nix
./system/boot/systemd/userdbd.nix
./system/boot/systemd/homed.nix
./system/boot/timesyncd.nix
./system/boot/tmp.nix
./system/boot/uvesafb.nix

View File

@ -488,6 +488,9 @@ let
account [success=ok ignore=ignore default=die] ${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_login.so
account [success=ok default=ignore] ${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_admin.so
'' +
optionalString config.services.homed.enable ''
account sufficient ${config.systemd.package}/lib/security/pam_systemd_home.so
'' +
# The required pam_unix.so module has to come after all the sufficient modules
# because otherwise, the account lookup will fail if the user does not exist
# locally, for example with MySQL- or LDAP-auth.
@ -541,8 +544,10 @@ let
# after it succeeds. Certain modules need to run after pam_unix
# prompts the user for password so we run it once with 'optional' at an
# earlier point and it will run again with 'sufficient' further down.
# We use try_first_pass the second time to avoid prompting password twice
(optionalString (cfg.unixAuth &&
# We use try_first_pass the second time to avoid prompting password twice.
#
# The same principle applies to systemd-homed
(optionalString ((cfg.unixAuth || config.services.homed.enable) &&
(config.security.pam.enableEcryptfs
|| config.security.pam.enableFscrypt
|| cfg.pamMount
@ -553,7 +558,10 @@ let
|| cfg.failDelay.enable
|| cfg.duoSecurity.enable))
(
''
optionalString config.services.homed.enable ''
auth optional ${config.systemd.package}/lib/security/pam_systemd_home.so
'' +
optionalString cfg.unixAuth ''
auth optional pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} ${optionalString cfg.nodelay "nodelay"} likeauth
'' +
optionalString config.security.pam.enableEcryptfs ''
@ -584,6 +592,9 @@ let
auth required ${pkgs.duo-unix}/lib/security/pam_duo.so
''
)) +
optionalString config.services.homed.enable ''
auth sufficient ${config.systemd.package}/lib/security/pam_systemd_home.so
'' +
optionalString cfg.unixAuth ''
auth sufficient pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} ${optionalString cfg.nodelay "nodelay"} likeauth try_first_pass
'' +
@ -605,6 +616,10 @@ let
auth required pam_deny.so
# Password management.
'' +
optionalString config.services.homed.enable ''
password sufficient ${config.systemd.package}/lib/security/pam_systemd_home.so
'' + ''
password sufficient pam_unix.so nullok sha512
'' +
optionalString config.security.pam.enableEcryptfs ''
@ -650,6 +665,9 @@ let
++ optional (cfg.ttyAudit.enablePattern != null) "enable=${cfg.ttyAudit.enablePattern}"
++ optional (cfg.ttyAudit.disablePattern != null) "disable=${cfg.ttyAudit.disablePattern}"
)) +
optionalString config.services.homed.enable ''
session required ${config.systemd.package}/lib/security/pam_systemd_home.so
'' +
optionalString cfg.makeHomeDir ''
session required ${pkgs.pam}/lib/security/pam_mkhomedir.so silent skel=${config.security.pam.makeHomeDir.skelDirectory} umask=0077
'' +
@ -1345,6 +1363,9 @@ in
'' +
optionalString config.virtualisation.lxc.lxcfs.enable ''
mr ${pkgs.lxc}/lib/security/pam_cgfs.so
'' +
optionalString config.services.homed.enable ''
mr ${config.systemd.package}/lib/security/pam_systemd_home.so
'';
};

View File

@ -450,7 +450,7 @@ in
(mkAfter [ "systemd" ])
]);
group = (mkMerge [
(mkAfter [ "systemd" ])
(mkAfter [ "[success=merge] systemd" ]) # need merge so that NSS won't stop at file-based groups
]);
};

View File

@ -0,0 +1,43 @@
{ config, lib, pkgs, ... }:
let
cfg = config.services.homed;
in
{
options.services.homed.enable = lib.mkEnableOption (lib.mdDoc ''
Enable systemd home area/user account manager
'');
config = lib.mkIf cfg.enable {
assertions = [
{
assertion = config.services.nscd.enable;
message = "systemd-homed requires the use of systemd nss module. services.nscd.enable must be set to true,";
}
];
systemd.additionalUpstreamSystemUnits = [
"systemd-homed.service"
"systemd-homed-activate.service"
];
# This is mentioned in homed's [Install] section.
#
# While homed appears to work without it, it's probably better
# to follow upstream recommendations.
services.userdbd.enable = lib.mkDefault true;
systemd.services = {
systemd-homed = {
# These packages are required to manage encrypted volumes
path = config.system.fsPackages;
aliases = [ "dbus-org.freedesktop.home1.service" ];
wantedBy = [ "multi-user.target" ];
};
systemd-homed-activate = {
wantedBy = [ "systemd-homed.service" ];
};
};
};
}

View File

@ -637,6 +637,7 @@ in {
systemd-timesyncd = handleTest ./systemd-timesyncd.nix {};
systemd-misc = handleTest ./systemd-misc.nix {};
systemd-userdbd = handleTest ./systemd-userdbd.nix {};
systemd-homed = handleTest ./systemd-homed.nix {};
tandoor-recipes = handleTest ./tandoor-recipes.nix {};
taskserver = handleTest ./taskserver.nix {};
tayga = handleTest ./tayga.nix {};

View File

@ -0,0 +1,99 @@
import ./make-test-python.nix ({ pkgs, lib, ... }:
let
password = "foobar";
newPass = "barfoo";
in
{
name = "systemd-homed";
nodes.machine = { config, pkgs, ... }: {
services.homed.enable = true;
users.users.test-normal-user = {
extraGroups = [ "wheel" ];
isNormalUser = true;
initialPassword = password;
};
};
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}'")
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("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.send_chars("whoami > /tmp/2\n")
machine.wait_for_file("/tmp/2")
assert "test-homed-user" in machine.succeed("cat /tmp/2")
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")
# 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.sleep(4)
machine.send_chars("${password}\n")
machine.wait_until_fails("pgrep -t tty4 passwd")
@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")
machine.wait_for_file("/tmp/4")
assert "test-homed-user" in machine.succeed("cat /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")
'';
})

View File

@ -81,7 +81,7 @@
, withDocumentation ? true
, withEfi ? stdenv.hostPlatform.isEfi
, withFido2 ? true
, withHomed ? false
, withHomed ? true
, withHostnamed ? true
, withHwdb ? true
, withImportd ? !stdenv.hostPlatform.isMusl

View File

@ -25973,6 +25973,7 @@ with pkgs;
withEfi = false;
withFido2 = false;
withHostnamed = false;
withHomed = false;
withHwdb = false;
withImportd = false;
withLibBPF = false;