Merge pull request #205121 from alaviss/homed
nixos: systemd-homed support
This commit is contained in:
commit
6b1a896570
10 changed files with 223 additions and 5 deletions
|
@ -1288,6 +1288,8 @@
|
|||
./system/boot/systemd/shutdown.nix
|
||||
./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
|
||||
|
|
|
@ -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
|
||||
'' +
|
||||
|
@ -1361,6 +1379,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
|
||||
'';
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
]);
|
||||
};
|
||||
|
||||
|
|
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" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
18
nixos/modules/system/boot/systemd/userdbd.nix
Normal file
18
nixos/modules/system/boot/systemd/userdbd.nix
Normal file
|
@ -0,0 +1,18 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
cfg = config.services.userdbd;
|
||||
in
|
||||
{
|
||||
options.services.userdbd.enable = lib.mkEnableOption (lib.mdDoc ''
|
||||
Enables the systemd JSON user/group record lookup service
|
||||
'');
|
||||
config = lib.mkIf cfg.enable {
|
||||
systemd.additionalUpstreamSystemUnits = [
|
||||
"systemd-userdbd.socket"
|
||||
"systemd-userdbd.service"
|
||||
];
|
||||
|
||||
systemd.sockets.systemd-userdbd.wantedBy = [ "sockets.target" ];
|
||||
};
|
||||
}
|
|
@ -642,6 +642,8 @@ in {
|
|||
systemd-shutdown = handleTest ./systemd-shutdown.nix {};
|
||||
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 {};
|
||||
|
|
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")
|
||||
'';
|
||||
})
|
32
nixos/tests/systemd-userdbd.nix
Normal file
32
nixos/tests/systemd-userdbd.nix
Normal file
|
@ -0,0 +1,32 @@
|
|||
import ./make-test-python.nix ({ pkgs, lib, ... }: {
|
||||
name = "systemd-userdbd";
|
||||
nodes.machine = { config, pkgs, ... }: {
|
||||
services.userdbd.enable = true;
|
||||
|
||||
users.users.test-user-nss = {
|
||||
isNormalUser = true;
|
||||
};
|
||||
|
||||
environment.etc."userdb/test-user-dropin.user".text = builtins.toJSON {
|
||||
userName = "test-user-dropin";
|
||||
};
|
||||
|
||||
environment.systemPackages = with pkgs; [ libvarlink ];
|
||||
};
|
||||
testScript = ''
|
||||
import json
|
||||
from shlex import quote
|
||||
|
||||
def getUserRecord(name):
|
||||
Interface = "unix:/run/systemd/userdb/io.systemd.Multiplexer/io.systemd.UserDatabase"
|
||||
payload = json.dumps({
|
||||
"service": "io.systemd.Multiplexer",
|
||||
"userName": name
|
||||
})
|
||||
return json.loads(machine.succeed(f"varlink call {Interface}.GetUserRecord {quote(payload)}"))
|
||||
|
||||
machine.wait_for_unit("systemd-userdbd.socket")
|
||||
getUserRecord("test-user-nss")
|
||||
getUserRecord("test-user-dropin")
|
||||
'';
|
||||
})
|
|
@ -82,7 +82,7 @@
|
|||
, withDocumentation ? true
|
||||
, withEfi ? stdenv.hostPlatform.isEfi && !stdenv.hostPlatform.isMusl
|
||||
, withFido2 ? true
|
||||
, withHomed ? false
|
||||
, withHomed ? true
|
||||
, withHostnamed ? true
|
||||
, withHwdb ? true
|
||||
, withImportd ? !stdenv.hostPlatform.isMusl
|
||||
|
|
|
@ -26152,6 +26152,7 @@ with pkgs;
|
|||
withEfi = false;
|
||||
withFido2 = false;
|
||||
withHostnamed = false;
|
||||
withHomed = false;
|
||||
withHwdb = false;
|
||||
withImportd = false;
|
||||
withLibBPF = false;
|
||||
|
|
Loading…
Reference in a new issue