nixos/systemd-sysusers: init
This commit is contained in:
parent
be2a4f37af
commit
eec1845744
6 changed files with 313 additions and 4 deletions
|
@ -685,7 +685,7 @@ in {
|
|||
shadow.gid = ids.gids.shadow;
|
||||
};
|
||||
|
||||
system.activationScripts.users = {
|
||||
system.activationScripts.users = if !config.systemd.sysusers.enable then {
|
||||
supportsDryActivation = true;
|
||||
text = ''
|
||||
install -m 0700 -d /root
|
||||
|
@ -694,7 +694,7 @@ in {
|
|||
${pkgs.perl.withPackages (p: [ p.FileSlurp p.JSON ])}/bin/perl \
|
||||
-w ${./update-users-groups.pl} ${spec}
|
||||
'';
|
||||
};
|
||||
} else ""; # keep around for backwards compatibility
|
||||
|
||||
system.activationScripts.update-lingering = let
|
||||
lingerDir = "/var/lib/systemd/linger";
|
||||
|
@ -711,7 +711,9 @@ in {
|
|||
'';
|
||||
|
||||
# Warn about user accounts with deprecated password hashing schemes
|
||||
system.activationScripts.hashes = {
|
||||
# This does not work when the users and groups are created by
|
||||
# systemd-sysusers because the users are created too late then.
|
||||
system.activationScripts.hashes = if !config.systemd.sysusers.enable then {
|
||||
deps = [ "users" ];
|
||||
text = ''
|
||||
users=()
|
||||
|
@ -729,7 +731,7 @@ in {
|
|||
printf ' - %s\n' "''${users[@]}"
|
||||
fi
|
||||
'';
|
||||
};
|
||||
} else ""; # keep around for backwards compatibility
|
||||
|
||||
# for backwards compatibility
|
||||
system.activationScripts.groups = stringAfter [ "users" ] "";
|
||||
|
|
|
@ -1486,6 +1486,7 @@
|
|||
./system/boot/systemd/repart.nix
|
||||
./system/boot/systemd/shutdown.nix
|
||||
./system/boot/systemd/sysupdate.nix
|
||||
./system/boot/systemd/sysusers.nix
|
||||
./system/boot/systemd/tmpfiles.nix
|
||||
./system/boot/systemd/user.nix
|
||||
./system/boot/systemd/userdbd.nix
|
||||
|
|
169
nixos/modules/system/boot/systemd/sysusers.nix
Normal file
169
nixos/modules/system/boot/systemd/sysusers.nix
Normal file
|
@ -0,0 +1,169 @@
|
|||
{ config, lib, pkgs, utils, ... }:
|
||||
|
||||
let
|
||||
|
||||
cfg = config.systemd.sysusers;
|
||||
userCfg = config.users;
|
||||
|
||||
sysusersConfig = pkgs.writeTextDir "00-nixos.conf" ''
|
||||
# Type Name ID GECOS Home directory Shell
|
||||
|
||||
# Users
|
||||
${lib.concatLines (lib.mapAttrsToList
|
||||
(username: opts:
|
||||
let
|
||||
uid = if opts.uid == null then "-" else toString opts.uid;
|
||||
in
|
||||
''u ${username} ${uid}:${opts.group} "${opts.description}" ${opts.home} ${utils.toShellPath opts.shell}''
|
||||
)
|
||||
userCfg.users)
|
||||
}
|
||||
|
||||
# Groups
|
||||
${lib.concatLines (lib.mapAttrsToList
|
||||
(groupname: opts: ''g ${groupname} ${if opts.gid == null then "-" else toString opts.gid}'') userCfg.groups)
|
||||
}
|
||||
|
||||
# Group membership
|
||||
${lib.concatStrings (lib.mapAttrsToList
|
||||
(groupname: opts: (lib.concatMapStrings (username: "m ${username} ${groupname}\n")) opts.members ) userCfg.groups)
|
||||
}
|
||||
'';
|
||||
|
||||
staticSysusersCredentials = pkgs.runCommand "static-sysusers-credentials" { } ''
|
||||
mkdir $out; cd $out
|
||||
${lib.concatLines (
|
||||
(lib.mapAttrsToList
|
||||
(username: opts: "echo -n '${opts.initialHashedPassword}' > 'passwd.hashed-password.${username}'")
|
||||
(lib.filterAttrs (_username: opts: opts.initialHashedPassword != null) userCfg.users))
|
||||
++
|
||||
(lib.mapAttrsToList
|
||||
(username: opts: "echo -n '${opts.initialPassword}' > 'passwd.plaintext-password.${username}'")
|
||||
(lib.filterAttrs (_username: opts: opts.initialPassword != null) userCfg.users))
|
||||
++
|
||||
(lib.mapAttrsToList
|
||||
(username: opts: "cat '${opts.hashedPasswordFile}' > 'passwd.hashed-password.${username}'")
|
||||
(lib.filterAttrs (_username: opts: opts.hashedPasswordFile != null) userCfg.users))
|
||||
)
|
||||
}
|
||||
'';
|
||||
|
||||
staticSysusers = pkgs.runCommand "static-sysusers"
|
||||
{
|
||||
nativeBuildInputs = [ pkgs.systemd ];
|
||||
} ''
|
||||
mkdir $out
|
||||
export CREDENTIALS_DIRECTORY=${staticSysusersCredentials}
|
||||
systemd-sysusers --root $out ${sysusersConfig}/00-nixos.conf
|
||||
'';
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
options = {
|
||||
|
||||
# This module doesn't set it's own user options but reuses the ones from
|
||||
# users-groups.nix
|
||||
|
||||
systemd.sysusers = {
|
||||
enable = lib.mkEnableOption (lib.mdDoc "systemd-sysusers") // {
|
||||
description = lib.mdDoc ''
|
||||
If enabled, users are created with systemd-sysusers instead of with
|
||||
the custom `update-users-groups.pl` script.
|
||||
|
||||
Note: This is experimental.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
|
||||
assertions = [
|
||||
{
|
||||
assertion = config.system.activationScripts.users == "";
|
||||
message = "system.activationScripts.users has to be empty to use systemd-sysusers";
|
||||
}
|
||||
{
|
||||
assertion = config.users.mutableUsers -> config.system.etc.overlay.enable;
|
||||
message = "config.users.mutableUsers requires config.system.etc.overlay.enable.";
|
||||
}
|
||||
];
|
||||
|
||||
systemd = lib.mkMerge [
|
||||
({
|
||||
|
||||
# Create home directories, do not create /var/empty even if that's a user's
|
||||
# home.
|
||||
tmpfiles.settings.home-directories = lib.mapAttrs'
|
||||
(username: opts: lib.nameValuePair opts.home {
|
||||
d = {
|
||||
mode = opts.homeMode;
|
||||
user = username;
|
||||
group = opts.group;
|
||||
};
|
||||
})
|
||||
(lib.filterAttrs (_username: opts: opts.home != "/var/empty") userCfg.users);
|
||||
})
|
||||
|
||||
(lib.mkIf config.users.mutableUsers {
|
||||
additionalUpstreamSystemUnits = [
|
||||
"systemd-sysusers.service"
|
||||
];
|
||||
|
||||
services.systemd-sysusers = {
|
||||
# Enable switch-to-configuration to restart the service.
|
||||
unitConfig.ConditionNeedsUpdate = [ "" ];
|
||||
requiredBy = [ "sysinit-reactivation.target" ];
|
||||
before = [ "sysinit-reactivation.target" ];
|
||||
restartTriggers = [ "${config.environment.etc."sysusers.d".source}" ];
|
||||
|
||||
serviceConfig = {
|
||||
LoadCredential = lib.mapAttrsToList
|
||||
(username: opts: "passwd.hashed-password.${username}:${opts.hashedPasswordFile}")
|
||||
(lib.filterAttrs (_username: opts: opts.hashedPasswordFile != null) userCfg.users);
|
||||
SetCredential = (lib.mapAttrsToList
|
||||
(username: opts: "passwd.hashed-password.${username}:${opts.initialHashedPassword}")
|
||||
(lib.filterAttrs (_username: opts: opts.initialHashedPassword != null) userCfg.users))
|
||||
++
|
||||
(lib.mapAttrsToList
|
||||
(username: opts: "passwd.plaintext-password.${username}:${opts.initialPassword}")
|
||||
(lib.filterAttrs (_username: opts: opts.initialPassword != null) userCfg.users))
|
||||
;
|
||||
};
|
||||
};
|
||||
})
|
||||
];
|
||||
|
||||
environment.etc = lib.mkMerge [
|
||||
(lib.mkIf (!userCfg.mutableUsers) {
|
||||
"passwd" = {
|
||||
source = "${staticSysusers}/etc/passwd";
|
||||
mode = "0644";
|
||||
};
|
||||
"group" = {
|
||||
source = "${staticSysusers}/etc/group";
|
||||
mode = "0644";
|
||||
};
|
||||
"shadow" = {
|
||||
source = "${staticSysusers}/etc/shadow";
|
||||
mode = "0000";
|
||||
};
|
||||
"gshadow" = {
|
||||
source = "${staticSysusers}/etc/gshadow";
|
||||
mode = "0000";
|
||||
};
|
||||
})
|
||||
|
||||
(lib.mkIf userCfg.mutableUsers {
|
||||
"sysusers.d".source = sysusersConfig;
|
||||
})
|
||||
];
|
||||
|
||||
};
|
||||
|
||||
meta.maintainers = with lib.maintainers; [ nikstur ];
|
||||
|
||||
}
|
|
@ -864,6 +864,8 @@ in {
|
|||
systemd-repart = handleTest ./systemd-repart.nix {};
|
||||
systemd-shutdown = handleTest ./systemd-shutdown.nix {};
|
||||
systemd-sysupdate = runTest ./systemd-sysupdate.nix;
|
||||
systemd-sysusers-mutable = runTest ./systemd-sysusers-mutable.nix;
|
||||
systemd-sysusers-immutable = runTest ./systemd-sysusers-immutable.nix;
|
||||
systemd-timesyncd = handleTest ./systemd-timesyncd.nix {};
|
||||
systemd-timesyncd-nscd-dnssec = handleTest ./systemd-timesyncd-nscd-dnssec.nix {};
|
||||
systemd-user-tmpfiles-rules = handleTest ./systemd-user-tmpfiles-rules.nix {};
|
||||
|
|
64
nixos/tests/systemd-sysusers-immutable.nix
Normal file
64
nixos/tests/systemd-sysusers-immutable.nix
Normal file
|
@ -0,0 +1,64 @@
|
|||
{ lib, ... }:
|
||||
|
||||
let
|
||||
rootPassword = "$y$j9T$p6OI0WN7.rSfZBOijjRdR.$xUOA2MTcB48ac.9Oc5fz8cxwLv1mMqabnn333iOzSA6";
|
||||
normaloPassword = "$y$j9T$3aiOV/8CADAK22OK2QT3/0$67OKd50Z4qTaZ8c/eRWHLIM.o3ujtC1.n9ysmJfv639";
|
||||
newNormaloPassword = "mellow";
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
name = "activation-sysusers-immutable";
|
||||
|
||||
meta.maintainers = with lib.maintainers; [ nikstur ];
|
||||
|
||||
nodes.machine = {
|
||||
systemd.sysusers.enable = true;
|
||||
users.mutableUsers = false;
|
||||
|
||||
# Override the empty root password set by the test instrumentation
|
||||
users.users.root.hashedPasswordFile = lib.mkForce null;
|
||||
users.users.root.initialHashedPassword = rootPassword;
|
||||
users.users.normalo = {
|
||||
isNormalUser = true;
|
||||
initialHashedPassword = normaloPassword;
|
||||
};
|
||||
|
||||
specialisation.new-generation.configuration = {
|
||||
users.users.new-normalo = {
|
||||
isNormalUser = true;
|
||||
initialPassword = newNormaloPassword;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
with subtest("Users are not created with systemd-sysusers"):
|
||||
machine.fail("systemctl status systemd-sysusers.service")
|
||||
machine.fail("ls /etc/sysusers.d")
|
||||
|
||||
with subtest("Correct mode on the password files"):
|
||||
assert machine.succeed("stat -c '%a' /etc/passwd") == "644\n"
|
||||
assert machine.succeed("stat -c '%a' /etc/group") == "644\n"
|
||||
assert machine.succeed("stat -c '%a' /etc/shadow") == "0\n"
|
||||
assert machine.succeed("stat -c '%a' /etc/gshadow") == "0\n"
|
||||
|
||||
with subtest("root user has correct password"):
|
||||
print(machine.succeed("getent passwd root"))
|
||||
assert "${rootPassword}" in machine.succeed("getent shadow root"), "root user password is not correct"
|
||||
|
||||
with subtest("normalo user is created"):
|
||||
print(machine.succeed("getent passwd normalo"))
|
||||
assert machine.succeed("stat -c '%U' /home/normalo") == "normalo\n"
|
||||
assert "${normaloPassword}" in machine.succeed("getent shadow normalo"), "normalo user password is not correct"
|
||||
|
||||
|
||||
machine.succeed("/run/current-system/specialisation/new-generation/bin/switch-to-configuration switch")
|
||||
|
||||
|
||||
with subtest("new-normalo user is created after switching to new generation"):
|
||||
print(machine.succeed("getent passwd new-normalo"))
|
||||
print(machine.succeed("getent shadow new-normalo"))
|
||||
assert machine.succeed("stat -c '%U' /home/new-normalo") == "new-normalo\n"
|
||||
'';
|
||||
}
|
71
nixos/tests/systemd-sysusers-mutable.nix
Normal file
71
nixos/tests/systemd-sysusers-mutable.nix
Normal file
|
@ -0,0 +1,71 @@
|
|||
{ lib, ... }:
|
||||
|
||||
let
|
||||
rootPassword = "$y$j9T$p6OI0WN7.rSfZBOijjRdR.$xUOA2MTcB48ac.9Oc5fz8cxwLv1mMqabnn333iOzSA6";
|
||||
normaloPassword = "hello";
|
||||
newNormaloPassword = "$y$j9T$p6OI0WN7.rSfZBOijjRdR.$xUOA2MTcB48ac.9Oc5fz8cxwLv1mMqabnn333iOzSA6";
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
name = "activation-sysusers-mutable";
|
||||
|
||||
meta.maintainers = with lib.maintainers; [ nikstur ];
|
||||
|
||||
nodes.machine = { pkgs, ... }: {
|
||||
systemd.sysusers.enable = true;
|
||||
users.mutableUsers = true;
|
||||
|
||||
# Prerequisites
|
||||
system.etc.overlay.enable = true;
|
||||
boot.initrd.systemd.enable = true;
|
||||
boot.kernelPackages = pkgs.linuxPackages_latest;
|
||||
|
||||
# Override the empty root password set by the test instrumentation
|
||||
users.users.root.hashedPasswordFile = lib.mkForce null;
|
||||
users.users.root.initialHashedPassword = rootPassword;
|
||||
users.users.normalo = {
|
||||
isNormalUser = true;
|
||||
initialPassword = normaloPassword;
|
||||
};
|
||||
|
||||
specialisation.new-generation.configuration = {
|
||||
users.users.new-normalo = {
|
||||
isNormalUser = true;
|
||||
initialHashedPassword = newNormaloPassword;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
machine.wait_for_unit("systemd-sysusers.service")
|
||||
|
||||
with subtest("systemd-sysusers.service contains the credentials"):
|
||||
sysusers_service = machine.succeed("systemctl cat systemd-sysusers.service")
|
||||
print(sysusers_service)
|
||||
assert "SetCredential=passwd.plaintext-password.normalo:${normaloPassword}" in sysusers_service
|
||||
|
||||
with subtest("Correct mode on the password files"):
|
||||
assert machine.succeed("stat -c '%a' /etc/passwd") == "644\n"
|
||||
assert machine.succeed("stat -c '%a' /etc/group") == "644\n"
|
||||
assert machine.succeed("stat -c '%a' /etc/shadow") == "0\n"
|
||||
assert machine.succeed("stat -c '%a' /etc/gshadow") == "0\n"
|
||||
|
||||
with subtest("root user has correct password"):
|
||||
print(machine.succeed("getent passwd root"))
|
||||
assert "${rootPassword}" in machine.succeed("getent shadow root"), "root user password is not correct"
|
||||
|
||||
with subtest("normalo user is created"):
|
||||
print(machine.succeed("getent passwd normalo"))
|
||||
assert machine.succeed("stat -c '%U' /home/normalo") == "normalo\n"
|
||||
|
||||
|
||||
machine.succeed("/run/current-system/specialisation/new-generation/bin/switch-to-configuration switch")
|
||||
|
||||
|
||||
with subtest("new-normalo user is created after switching to new generation"):
|
||||
print(machine.succeed("getent passwd new-normalo"))
|
||||
assert machine.succeed("stat -c '%U' /home/new-normalo") == "new-normalo\n"
|
||||
assert "${newNormaloPassword}" in machine.succeed("getent shadow new-normalo"), "new-normalo user password is not correct"
|
||||
'';
|
||||
}
|
Loading…
Reference in a new issue