nixos/user-groups: add a toggle for user account creation (#358646)

Sandro d1c535f6 622ad6b3

Changed files
+123 -12
nixos
doc
manual
release-notes
modules
config
services
system
tests
+2
nixos/doc/manual/release-notes/rl-2505.section.md
···
- `gerbera` now has wavpack support.
+
- A toggle has been added under `users.users.<name>.enable` to allow toggling individual users conditionally. If set to false, the user account will not be created.
+
- `ddclient` was updated from 3.11.2 to 4.0.0 [Release notes](https://github.com/ddclient/ddclient/releases/tag/v4.0.0)
<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
+11 -1
nixos/modules/config/users-groups.nix
···
options = {
+
enable = mkOption {
+
type = types.bool;
+
default = true;
+
example = false;
+
description = ''
+
If set to false, the user account will not be created. This is useful for when you wish to conditionally
+
disable user accounts.
+
'';
+
};
+
name = mkOption {
type = types.passwdEntry types.str;
apply = x: assert (stringLength x < 32 || abort "Username '${x}' is longer than 31 characters which is not allowed!"); x;
···
autoSubUidGidRange subUidRanges subGidRanges
initialPassword initialHashedPassword expires;
shell = utils.toShellPath u.shell;
-
}) cfg.users;
+
}) (filterAttrs (_: u: u.enable) cfg.users);
groups = attrValues cfg.groups;
});
+18 -11
nixos/modules/services/system/userborn.nix
···
;
isNormal = opts.isNormalUser;
shell = utils.toShellPath opts.shell;
-
}) config.users.users;
+
}) (lib.filterAttrs (_: u: u.enable) config.users.users);
};
userbornConfigJson = pkgs.writeText "userborn.json" (builtins.toJSON userbornConfig);
···
# 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 (toString opts.home) {
-
d = {
-
mode = opts.homeMode;
-
user = opts.name;
-
inherit (opts) group;
-
};
-
}
-
) (lib.filterAttrs (_username: opts: opts.createHome && opts.home != "/var/empty") userCfg.users);
+
tmpfiles.settings.home-directories =
+
lib.mapAttrs'
+
(
+
username: opts:
+
lib.nameValuePair (toString opts.home) {
+
d = {
+
mode = opts.homeMode;
+
user = opts.name;
+
inherit (opts) group;
+
};
+
}
+
)
+
(
+
lib.filterAttrs (
+
_username: opts: opts.enable && opts.createHome && opts.home != "/var/empty"
+
) userCfg.users
+
);
services.userborn = {
wantedBy = [ "sysinit.target" ];
+1
nixos/tests/all-tests.nix
···
userborn-mutable-etc = runTest ./userborn-mutable-etc.nix;
userborn-immutable-etc = runTest ./userborn-immutable-etc.nix;
user-activation-scripts = handleTest ./user-activation-scripts.nix {};
+
user-enable-option = runTest ./user-enable-option.nix;
user-expiry = runTest ./user-expiry.nix;
user-home-mode = handleTest ./user-home-mode.nix {};
ustreamer = handleTest ./ustreamer.nix {};
+82
nixos/tests/user-enable-option.nix
···
+
let
+
normal-enabled = "username-normal-enabled";
+
normal-disabled = "username-normal-disabled";
+
system-enabled = "username-system-enabled";
+
system-disabled = "username-system-disabled";
+
passwd = "enableOptionPasswd";
+
in
+
{
+
name = "user-enable-option";
+
+
nodes.machine = {
+
users = {
+
groups.test-group = { };
+
users = {
+
# User is enabled (default behaviour).
+
${normal-enabled} = {
+
enable = true;
+
isNormalUser = true;
+
initialPassword = passwd;
+
};
+
+
# User is disabled.
+
${normal-disabled} = {
+
enable = false;
+
isNormalUser = true;
+
initialPassword = passwd;
+
};
+
+
# User is a system user, and is enabled.
+
${system-enabled} = {
+
enable = true;
+
isSystemUser = true;
+
initialPassword = passwd;
+
group = "test-group";
+
};
+
+
# User is a system user, and is disabled.
+
${system-disabled} = {
+
enable = false;
+
isSystemUser = true;
+
initialPassword = passwd;
+
group = "test-group";
+
};
+
};
+
};
+
};
+
+
testScript = ''
+
def switch_to_tty(tty_number):
+
machine.fail(f"pgrep -f 'agetty.*tty{tty_number}'")
+
machine.send_key(f"alt-f{tty_number}")
+
machine.wait_until_succeeds(f"[ $(fgconsole) = {tty_number} ]")
+
machine.wait_for_unit(f"getty@tty{tty_number}.service")
+
machine.wait_until_succeeds(f"pgrep -f 'agetty.*tty{tty_number}'")
+
+
machine.wait_for_unit("multi-user.target")
+
machine.wait_for_unit("getty@tty1.service")
+
+
with subtest("${normal-enabled} exists"):
+
check_fn = f"id ${normal-enabled}"
+
machine.succeed(check_fn)
+
machine.wait_until_tty_matches("1", "login: ")
+
machine.send_chars("${normal-enabled}\n")
+
machine.wait_until_tty_matches("1", "Password: ")
+
machine.send_chars("${passwd}\n")
+
+
with subtest("${normal-disabled} does not exist"):
+
switch_to_tty(2)
+
check_fn = f"id ${normal-disabled}"
+
machine.fail(check_fn)
+
+
with subtest("${system-enabled} exists"):
+
switch_to_tty(3)
+
check_fn = f"id ${system-enabled}"
+
machine.succeed(check_fn)
+
+
with subtest("${system-disabled} does not exist"):
+
switch_to_tty(4)
+
check_fn = f"id ${system-disabled}"
+
machine.fail(check_fn)
+
'';
+
}
+9
nixos/tests/userborn.nix
···
isNormalUser = true;
hashedPassword = newNormaloHashedPassword;
};
+
normalo-disabled = {
+
enable = false;
+
isNormalUser = true;
+
};
};
groups = {
new-group = { };
···
print(machine.succeed("getent passwd sysuser"))
assert 1000 > int(machine.succeed("id --user sysuser")), "sysuser user doesn't have a system UID"
assert "${sysuserInitialHashedPassword}" in machine.succeed("getent shadow sysuser"), "system user password is not correct"
+
+
with subtest("normalo-disabled is NOT created"):
+
machine.fail("id normalo-disabled")
+
# Check if user's home has been created
+
machine.fail("[ -d '/home/normalo-disabled' ]")
with subtest("sysusers group is created"):
print(machine.succeed("getent group sysusers"))