nixos: add systemd-homed support
As a start, it's not very configurable, but works pretty well.
This commit is contained in:
@@ -1276,6 +1276,7 @@
|
|||||||
./system/boot/systemd/tmpfiles.nix
|
./system/boot/systemd/tmpfiles.nix
|
||||||
./system/boot/systemd/user.nix
|
./system/boot/systemd/user.nix
|
||||||
./system/boot/systemd/userdbd.nix
|
./system/boot/systemd/userdbd.nix
|
||||||
|
./system/boot/systemd/homed.nix
|
||||||
./system/boot/timesyncd.nix
|
./system/boot/timesyncd.nix
|
||||||
./system/boot/tmp.nix
|
./system/boot/tmp.nix
|
||||||
./system/boot/uvesafb.nix
|
./system/boot/uvesafb.nix
|
||||||
|
|||||||
@@ -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 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
|
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
|
# 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
|
# because otherwise, the account lookup will fail if the user does not exist
|
||||||
# locally, for example with MySQL- or LDAP-auth.
|
# locally, for example with MySQL- or LDAP-auth.
|
||||||
@@ -541,8 +544,10 @@ let
|
|||||||
# after it succeeds. Certain modules need to run after pam_unix
|
# 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
|
# 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.
|
# earlier point and it will run again with 'sufficient' further down.
|
||||||
# We use try_first_pass the second time to avoid prompting password twice
|
# We use try_first_pass the second time to avoid prompting password twice.
|
||||||
(optionalString (cfg.unixAuth &&
|
#
|
||||||
|
# The same principle applies to systemd-homed
|
||||||
|
(optionalString ((cfg.unixAuth || config.services.homed.enable) &&
|
||||||
(config.security.pam.enableEcryptfs
|
(config.security.pam.enableEcryptfs
|
||||||
|| config.security.pam.enableFscrypt
|
|| config.security.pam.enableFscrypt
|
||||||
|| cfg.pamMount
|
|| cfg.pamMount
|
||||||
@@ -553,7 +558,10 @@ let
|
|||||||
|| cfg.failDelay.enable
|
|| cfg.failDelay.enable
|
||||||
|| cfg.duoSecurity.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
|
auth optional pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} ${optionalString cfg.nodelay "nodelay"} likeauth
|
||||||
'' +
|
'' +
|
||||||
optionalString config.security.pam.enableEcryptfs ''
|
optionalString config.security.pam.enableEcryptfs ''
|
||||||
@@ -584,6 +592,9 @@ let
|
|||||||
auth required ${pkgs.duo-unix}/lib/security/pam_duo.so
|
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 ''
|
optionalString cfg.unixAuth ''
|
||||||
auth sufficient pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} ${optionalString cfg.nodelay "nodelay"} likeauth try_first_pass
|
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
|
auth required pam_deny.so
|
||||||
|
|
||||||
# Password management.
|
# 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
|
password sufficient pam_unix.so nullok sha512
|
||||||
'' +
|
'' +
|
||||||
optionalString config.security.pam.enableEcryptfs ''
|
optionalString config.security.pam.enableEcryptfs ''
|
||||||
@@ -650,6 +665,9 @@ let
|
|||||||
++ optional (cfg.ttyAudit.enablePattern != null) "enable=${cfg.ttyAudit.enablePattern}"
|
++ optional (cfg.ttyAudit.enablePattern != null) "enable=${cfg.ttyAudit.enablePattern}"
|
||||||
++ optional (cfg.ttyAudit.disablePattern != null) "disable=${cfg.ttyAudit.disablePattern}"
|
++ 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 ''
|
optionalString cfg.makeHomeDir ''
|
||||||
session required ${pkgs.pam}/lib/security/pam_mkhomedir.so silent skel=${config.security.pam.makeHomeDir.skelDirectory} umask=0077
|
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 ''
|
optionalString config.virtualisation.lxc.lxcfs.enable ''
|
||||||
mr ${pkgs.lxc}/lib/security/pam_cgfs.so
|
mr ${pkgs.lxc}/lib/security/pam_cgfs.so
|
||||||
|
'' +
|
||||||
|
optionalString config.services.homed.enable ''
|
||||||
|
mr ${config.systemd.package}/lib/security/pam_systemd_home.so
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -450,7 +450,7 @@ in
|
|||||||
(mkAfter [ "systemd" ])
|
(mkAfter [ "systemd" ])
|
||||||
]);
|
]);
|
||||||
group = (mkMerge [
|
group = (mkMerge [
|
||||||
(mkAfter [ "systemd" ])
|
(mkAfter [ "[success=merge] systemd" ]) # need merge so that NSS won't stop at file-based groups
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
43
nixos/modules/system/boot/systemd/homed.nix
Normal file
43
nixos/modules/system/boot/systemd/homed.nix
Normal 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" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -637,6 +637,7 @@ in {
|
|||||||
systemd-timesyncd = handleTest ./systemd-timesyncd.nix {};
|
systemd-timesyncd = handleTest ./systemd-timesyncd.nix {};
|
||||||
systemd-misc = handleTest ./systemd-misc.nix {};
|
systemd-misc = handleTest ./systemd-misc.nix {};
|
||||||
systemd-userdbd = handleTest ./systemd-userdbd.nix {};
|
systemd-userdbd = handleTest ./systemd-userdbd.nix {};
|
||||||
|
systemd-homed = handleTest ./systemd-homed.nix {};
|
||||||
tandoor-recipes = handleTest ./tandoor-recipes.nix {};
|
tandoor-recipes = handleTest ./tandoor-recipes.nix {};
|
||||||
taskserver = handleTest ./taskserver.nix {};
|
taskserver = handleTest ./taskserver.nix {};
|
||||||
tayga = handleTest ./tayga.nix {};
|
tayga = handleTest ./tayga.nix {};
|
||||||
|
|||||||
99
nixos/tests/systemd-homed.nix
Normal file
99
nixos/tests/systemd-homed.nix
Normal 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")
|
||||||
|
'';
|
||||||
|
})
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
, withDocumentation ? true
|
, withDocumentation ? true
|
||||||
, withEfi ? stdenv.hostPlatform.isEfi
|
, withEfi ? stdenv.hostPlatform.isEfi
|
||||||
, withFido2 ? true
|
, withFido2 ? true
|
||||||
, withHomed ? false
|
, withHomed ? true
|
||||||
, withHostnamed ? true
|
, withHostnamed ? true
|
||||||
, withHwdb ? true
|
, withHwdb ? true
|
||||||
, withImportd ? !stdenv.hostPlatform.isMusl
|
, withImportd ? !stdenv.hostPlatform.isMusl
|
||||||
|
|||||||
@@ -25973,6 +25973,7 @@ with pkgs;
|
|||||||
withEfi = false;
|
withEfi = false;
|
||||||
withFido2 = false;
|
withFido2 = false;
|
||||||
withHostnamed = false;
|
withHostnamed = false;
|
||||||
|
withHomed = false;
|
||||||
withHwdb = false;
|
withHwdb = false;
|
||||||
withImportd = false;
|
withImportd = false;
|
||||||
withLibBPF = false;
|
withLibBPF = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user