From dffdcfd44403ded110b3fd8cad4efa14ae5c25b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Forsman?= Date: Fri, 28 Oct 2022 17:58:31 +0200 Subject: [PATCH 1/2] nixos: add per-user groups option Add a new option, users.solitaryByDefault, that defaults to true for stateVersion >= 24.11. It can be opt-in before that (and opt-out after). When users.solitaryByDefault is true, each user defaults to primary group ``, whose group id equals the user id. Before it was `users` (with group id 100) for normal users and system users required explicit setting the group -- because the default was "" (empty) which triggered an assert saying the group must be set. This change allows finer grained control over file access, and aligns NixOS with other distros. Here, the result of `useradd -m user1 && ls -ld /home/user1`: Arch Linux : drwx------ 2 user1 user1 4096 Oct 28 15:17 /home/user1 Debian 11 : drwxr-xr-x 2 user1 user1 4096 Oct 28 15:17 /home/user1 Fedora 36 : drwx------ 2 user1 user1 4096 Oct 28 15:17 /home/user1 Ubuntu 22.04 : drwxr-x--- 2 user1 user1 4096 Oct 28 15:17 /home/user1 Fixes https://github.com/NixOS/nixpkgs/issues/198296. --- nixos/modules/config/users-groups.nix | 76 +++++++++++++++--------- nixos/modules/profiles/hardened.nix | 9 ++- nixos/modules/profiles/macos-builder.nix | 1 + nixos/tests/user-group-membership.nix | 39 ++++++++++++ 4 files changed, 97 insertions(+), 28 deletions(-) create mode 100644 nixos/tests/user-group-membership.nix diff --git a/nixos/modules/config/users-groups.nix b/nixos/modules/config/users-groups.nix index f9750b7263cac..3cb7bc2c1b403 100644 --- a/nixos/modules/config/users-groups.nix +++ b/nixos/modules/config/users-groups.nix @@ -3,6 +3,7 @@ with lib; let + nixosConfig = config; ids = config.ids; cfg = config.users; @@ -114,7 +115,11 @@ let group = mkOption { type = types.str; apply = x: assert (builtins.stringLength x < 32 || abort "Group name '${x}' is longer than 31 characters which is not allowed!"); x; - default = ""; + default = name; + defaultText = lib.literalMD '' + For normal users: if users.solitaryByDefault then else "users" + For system users: + ''; description = lib.mdDoc "The user's primary group."; }; @@ -362,13 +367,15 @@ let shell = mkIf config.useDefaultShell (mkDefault cfg.defaultUserShell); } (mkIf config.isNormalUser { - group = mkDefault "users"; createHome = mkDefault true; home = mkDefault "/home/${config.name}"; homeMode = mkDefault "700"; useDefaultShell = mkDefault true; isSystemUser = mkDefault false; }) + (mkIf (config.isNormalUser && !nixosConfig.users.solitaryByDefault) { + group = mkDefault "users"; + }) # If !mutableUsers, setting ‘initialPassword’ is equivalent to # setting ‘password’ (and similarly for hashed passwords). (mkIf (!cfg.mutableUsers && config.initialPassword != null) { @@ -584,6 +591,16 @@ in { ''; }; + users.solitaryByDefault = mkOption { + type = types.bool; + default = lib.versionAtLeast nixosConfig.system.stateVersion "24.11"; + description = lib.mdDoc '' + Whether normal users should default to a primary group named after + them. When disabled, normal users default to the "users" group. System + users always default to a group named after them. + ''; + }; + # systemd initrd boot.initrd.systemd.users = mkOption { description = '' @@ -659,31 +676,36 @@ in { }; }; - users.groups = { - root.gid = ids.gids.root; - wheel.gid = ids.gids.wheel; - disk.gid = ids.gids.disk; - kmem.gid = ids.gids.kmem; - tty.gid = ids.gids.tty; - floppy.gid = ids.gids.floppy; - uucp.gid = ids.gids.uucp; - lp.gid = ids.gids.lp; - cdrom.gid = ids.gids.cdrom; - tape.gid = ids.gids.tape; - audio.gid = ids.gids.audio; - video.gid = ids.gids.video; - dialout.gid = ids.gids.dialout; - nogroup.gid = ids.gids.nogroup; - users.gid = ids.gids.users; - nixbld.gid = ids.gids.nixbld; - utmp.gid = ids.gids.utmp; - adm.gid = ids.gids.adm; - input.gid = ids.gids.input; - kvm.gid = ids.gids.kvm; - render.gid = ids.gids.render; - sgx.gid = ids.gids.sgx; - shadow.gid = ids.gids.shadow; - }; + users.groups = mkMerge [ + { + root.gid = ids.gids.root; + wheel.gid = ids.gids.wheel; + disk.gid = ids.gids.disk; + kmem.gid = ids.gids.kmem; + tty.gid = ids.gids.tty; + floppy.gid = ids.gids.floppy; + uucp.gid = ids.gids.uucp; + lp.gid = ids.gids.lp; + cdrom.gid = ids.gids.cdrom; + tape.gid = ids.gids.tape; + audio.gid = ids.gids.audio; + video.gid = ids.gids.video; + dialout.gid = ids.gids.dialout; + nogroup.gid = ids.gids.nogroup; + users.gid = ids.gids.users; + nixbld.gid = ids.gids.nixbld; + utmp.gid = ids.gids.utmp; + adm.gid = ids.gids.adm; + input.gid = ids.gids.input; + kvm.gid = ids.gids.kvm; + render.gid = ids.gids.render; + sgx.gid = ids.gids.sgx; + shadow.gid = ids.gids.shadow; + } + # Create per-user primary groups + (lib.optionalAttrs nixosConfig.users.solitaryByDefault + (lib.mapAttrs' (_: user: { name = user.group; value = { gid = lib.mkDefault user.uid; }; }) nixosConfig.users.users)) + ]; system.activationScripts.users = if !config.systemd.sysusers.enable then { supportsDryActivation = true; diff --git a/nixos/modules/profiles/hardened.nix b/nixos/modules/profiles/hardened.nix index b85a2ac7e69d2..e844cfbd5a203 100644 --- a/nixos/modules/profiles/hardened.nix +++ b/nixos/modules/profiles/hardened.nix @@ -10,6 +10,9 @@ with lib; +let + normalUsers = lib.filterAttrs (n: v: v.isNormalUser) config.users.users; +in { meta = { maintainers = [ maintainers.joachifm maintainers.emily ]; @@ -17,7 +20,11 @@ with lib; boot.kernelPackages = mkDefault pkgs.linuxPackages_hardened; - nix.settings.allowed-users = mkDefault [ "@users" ]; + nix.settings.allowed-users = mkDefault ( + if config.users.solitaryByDefault then + lib.mapAttrsToList (n: v: v.name) normalUsers + else [ "@users" ] + ); environment.memoryAllocator.provider = mkDefault "scudo"; environment.variables.SCUDO_OPTIONS = mkDefault "ZeroContents=1"; diff --git a/nixos/modules/profiles/macos-builder.nix b/nixos/modules/profiles/macos-builder.nix index 786e26cf98f7f..917be5f207524 100644 --- a/nixos/modules/profiles/macos-builder.nix +++ b/nixos/modules/profiles/macos-builder.nix @@ -23,6 +23,7 @@ in ]; # swraid's default depends on stateVersion config.boot.swraid.enable = false; + config.users.solitaryByDefault = true; options.boot.isContainer = lib.mkOption { default = false; internal = true; }; } ]; diff --git a/nixos/tests/user-group-membership.nix b/nixos/tests/user-group-membership.nix new file mode 100644 index 0000000000000..3d6d11b2572f9 --- /dev/null +++ b/nixos/tests/user-group-membership.nix @@ -0,0 +1,39 @@ +import ./make-test-python.nix ({ lib, ... }: { + name = "user-group-membership"; + meta = with lib.maintainers; { maintainers = [ bjornfor ]; }; + + nodes = + let + commonConfig = { + users.users.alice = { + isNormalUser = true; + }; + users.users.bob = { + isNormalUser = true; + group = "users"; + }; + }; + in + { + shared = { + imports = [ commonConfig ]; + users.solitaryByDefault = false; + }; + solitary = { + imports = [ commonConfig ]; + users.solitaryByDefault = true; + }; + }; + + testScript = '' + start_all() + + with subtest("solitaryByDefault = false"): + shared.succeed('[ "$(stat -c %G /home/alice)" == "users" ]') + shared.succeed('[ "$(stat -c %G /home/bob)" == "users" ]') + + with subtest("solitaryByDefault = true"): + solitary.succeed('[ "$(stat -c %G /home/alice)" == "alice" ]') + solitary.succeed('[ "$(stat -c %G /home/bob)" == "users" ]') + ''; +}) From 88d592952b23c2d54ece2700d7a74f5c6b444098 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Forsman?= Date: Fri, 30 Dec 2022 14:08:41 +0100 Subject: [PATCH 2/2] nixos/doc: add per-user groups info to release notes --- nixos/doc/manual/release-notes/rl-2405.section.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nixos/doc/manual/release-notes/rl-2405.section.md b/nixos/doc/manual/release-notes/rl-2405.section.md index dddb2b9241ad7..66407d0321777 100644 --- a/nixos/doc/manual/release-notes/rl-2405.section.md +++ b/nixos/doc/manual/release-notes/rl-2405.section.md @@ -12,6 +12,11 @@ In addition to numerous new and upgraded packages, this release has the followin Take the time to review [the release notes](https://gitlab.com/cryptsetup/cryptsetup/-/raw/v2.7.0/docs/v2.7.0-ReleaseNotes). One of the highlight is that it is now possible to use hardware OPAL-based encryption of your disk with `cryptsetup`, it has a lot of caveats, see the above notes for the full details. +- A new option, `users.solitaryByDefault`, controls whether normal users default to a primary group named the same as the user, and with group id same as user id, or use the shared group "users" with group id 100. + System users always default to a group named after them, with GID = UID. + Per-user groups gives better control over permissions/file sharing and aligns NixOS with other distros like Arch Linux/Debian/Fedora/Ubuntu. + The option defaults to `system.stateVersion >= 24.11`, which means it's opt-in (to per-user groups) up to and including NixOS 24.05, but after that the option must be set to `false` to keep the current/old behaviour. + - `screen`'s module has been cleaned, and will now require you to set `programs.screen.enable` in order to populate `screenrc` and add the program to the environment. - `linuxPackages_testing_bcachefs` is now fully deprecated by `linuxPackages_latest`, and is therefore no longer available.