nixos/users: require one of users.users.name.{isSystemUser,isNormalUser}

As the only consequence of isSystemUser is that if the uid is null then
it's allocated below 500, if a user has uid = something below 500 then
we don't require isSystemUser to be set.

Motivation: https://github.com/NixOS/nixpkgs/issues/112647
This commit is contained in:
Symphorien Gibol 2021-03-07 14:54:00 +01:00
parent 311ceed827
commit 7a87973b4c
23 changed files with 80 additions and 32 deletions

View file

@ -167,7 +167,7 @@ in {
# We know that the `user` attribute exists because we set a default value # We know that the `user` attribute exists because we set a default value
# for it above, allowing us to use it without worries here # for it above, allowing us to use it without worries here
users.users.${cfg.settings.user} = {}; users.users.${cfg.settings.user} = { isSystemUser = true; };
# ... # ...
}; };

View file

@ -839,6 +839,13 @@ environment.systemPackages = [
The option's description was incorrect regarding ownership management and has been simplified greatly. The option's description was incorrect regarding ownership management and has been simplified greatly.
</para> </para>
</listitem> </listitem>
<listitem>
<para>
When defining a new user, one of <xref linkend="opt-users.users._name_.isNormalUser" /> and <xref linkend="opt-users.users._name_.isSystemUser" /> is now required.
This is to prevent accidentally giving a UID above 1000 to system users, which could have unexpected consequences, like running user activation scripts for system users.
Note that users defined with an explicit UID below 500 are exempted from this check, as <xref linkend="opt-users.users._name_.isSystemUser" /> has no effect for those.
</para>
</listitem>
<listitem> <listitem>
<para> <para>
The GNOME desktop manager once again installs <package>gnome3.epiphany</package> by default. The GNOME desktop manager once again installs <package>gnome3.epiphany</package> by default.

View file

@ -306,6 +306,7 @@ in {
description = "PulseAudio system service user"; description = "PulseAudio system service user";
home = stateDir; home = stateDir;
createHome = true; createHome = true;
isSystemUser = true;
}; };
users.groups.pulse.gid = gid; users.groups.pulse.gid = gid;

View file

@ -92,6 +92,8 @@ let
the user's UID is allocated in the range for system users the user's UID is allocated in the range for system users
(below 500) or in the range for normal users (starting at (below 500) or in the range for normal users (starting at
1000). 1000).
Exactly one of <literal>isNormalUser</literal> and
<literal>isSystemUser</literal> must be true.
''; '';
}; };
@ -107,6 +109,8 @@ let
<option>useDefaultShell</option> to <literal>true</literal>, <option>useDefaultShell</option> to <literal>true</literal>,
and <option>isSystemUser</option> to and <option>isSystemUser</option> to
<literal>false</literal>. <literal>false</literal>.
Exactly one of <literal>isNormalUser</literal> and
<literal>isSystemUser</literal> must be true.
''; '';
}; };
@ -521,6 +525,7 @@ in {
}; };
nobody = { nobody = {
uid = ids.uids.nobody; uid = ids.uids.nobody;
isSystemUser = true;
description = "Unprivileged account (don't use!)"; description = "Unprivileged account (don't use!)";
group = "nogroup"; group = "nogroup";
}; };
@ -608,17 +613,28 @@ in {
Neither the root account nor any wheel user has a password or SSH authorized key. Neither the root account nor any wheel user has a password or SSH authorized key.
You must set one to prevent being locked out of your system.''; You must set one to prevent being locked out of your system.'';
} }
] ++ flip mapAttrsToList cfg.users (name: user: ] ++ flatten (flip mapAttrsToList cfg.users (name: user:
{ [
{
assertion = (user.hashedPassword != null) assertion = (user.hashedPassword != null)
-> (builtins.match ".*:.*" user.hashedPassword == null); -> (builtins.match ".*:.*" user.hashedPassword == null);
message = '' message = ''
The password hash of user "${user.name}" contains a ":" character. The password hash of user "${user.name}" contains a ":" character.
This is invalid and would break the login system because the fields This is invalid and would break the login system because the fields
of /etc/shadow (file where hashes are stored) are colon-separated. of /etc/shadow (file where hashes are stored) are colon-separated.
Please check the value of option `users.users."${user.name}".hashedPassword`.''; Please check the value of option `users.users."${user.name}".hashedPassword`.'';
} }
); {
assertion = let
xor = a: b: a && !b || b && !a;
isEffectivelySystemUser = user.isSystemUser || (user.uid != null && user.uid < 500);
in xor isEffectivelySystemUser user.isNormalUser;
message = ''
Exactly one of users.users.${user.name}.isSystemUser and users.users.${user.name}.isNormalUser must be set.
'';
}
]
));
warnings = warnings =
builtins.filter (x: x != null) ( builtins.filter (x: x != null) (

View file

@ -169,6 +169,7 @@ let
(map (mkAuthorizedKey cfg false) cfg.authorizedKeys (map (mkAuthorizedKey cfg false) cfg.authorizedKeys
++ map (mkAuthorizedKey cfg true) cfg.authorizedKeysAppendOnly); ++ map (mkAuthorizedKey cfg true) cfg.authorizedKeysAppendOnly);
useDefaultShell = true; useDefaultShell = true;
isSystemUser = true;
}; };
groups.${cfg.group} = { }; groups.${cfg.group} = { };
}; };

View file

@ -197,6 +197,7 @@ in {
group = pgmanage; group = pgmanage;
home = cfg.sqlRoot; home = cfg.sqlRoot;
createHome = true; createHome = true;
isSystemUser = true;
}; };
groups.${pgmanage} = { groups.${pgmanage} = {
name = pgmanage; name = pgmanage;

View file

@ -64,6 +64,7 @@ in
users.users = mkIf (cfg.user == "bazarr") { users.users = mkIf (cfg.user == "bazarr") {
bazarr = { bazarr = {
isSystemUser = true;
group = cfg.group; group = cfg.group;
home = "/var/lib/${config.systemd.services.bazarr.serviceConfig.StateDirectory}"; home = "/var/lib/${config.systemd.services.bazarr.serviceConfig.StateDirectory}";
}; };

View file

@ -21,6 +21,7 @@ let
calls in `libstore/build.cc', don't add any supplementary group calls in `libstore/build.cc', don't add any supplementary group
here except "nixbld". */ here except "nixbld". */
uid = builtins.add config.ids.uids.nixbld nr; uid = builtins.add config.ids.uids.nixbld nr;
isSystemUser = true;
group = "nixbld"; group = "nixbld";
extraGroups = [ "nixbld" ]; extraGroups = [ "nixbld" ];
}; };

View file

@ -34,7 +34,10 @@ in {
users = { users = {
groups._tuptime.members = [ "_tuptime" ]; groups._tuptime.members = [ "_tuptime" ];
users._tuptime.description = "tuptime database owner"; users._tuptime = {
isSystemUser = true;
description = "tuptime database owner";
};
}; };
systemd = { systemd = {

View file

@ -73,6 +73,7 @@ let
users.${variant} = { users.${variant} = {
description = "BIRD Internet Routing Daemon user"; description = "BIRD Internet Routing Daemon user";
group = variant; group = variant;
isSystemUser = true;
}; };
groups.${variant} = {}; groups.${variant} = {};
}; };

View file

@ -243,8 +243,10 @@ in
xlog.journal = true; xlog.journal = true;
}; };
users.users.ncdns = users.users.ncdns = {
{ description = "ncdns daemon user"; }; isSystemUser = true;
description = "ncdns daemon user";
};
systemd.services.ncdns = { systemd.services.ncdns = {
description = "ncdns daemon"; description = "ncdns daemon";

View file

@ -93,6 +93,7 @@ in
users.users.pixiecore = { users.users.pixiecore = {
description = "Pixiecore daemon user"; description = "Pixiecore daemon user";
group = "pixiecore"; group = "pixiecore";
isSystemUser = true;
}; };
networking.firewall = mkIf cfg.openFirewall { networking.firewall = mkIf cfg.openFirewall {

View file

@ -75,6 +75,7 @@ in {
description = "Pleroma user"; description = "Pleroma user";
home = cfg.stateDir; home = cfg.stateDir;
extraGroups = [ cfg.group ]; extraGroups = [ cfg.group ];
isSystemUser = true;
}; };
groups."${cfg.group}" = {}; groups."${cfg.group}" = {};
}; };

View file

@ -264,6 +264,7 @@ in
users.users.privacyidea = mkIf (cfg.user == "privacyidea") { users.users.privacyidea = mkIf (cfg.user == "privacyidea") {
group = cfg.group; group = cfg.group;
isSystemUser = true;
}; };
users.groups.privacyidea = mkIf (cfg.group == "privacyidea") {}; users.groups.privacyidea = mkIf (cfg.group == "privacyidea") {};
@ -294,6 +295,7 @@ in
users.users.pi-ldap-proxy = mkIf (cfg.ldap-proxy.user == "pi-ldap-proxy") { users.users.pi-ldap-proxy = mkIf (cfg.ldap-proxy.user == "pi-ldap-proxy") {
group = cfg.ldap-proxy.group; group = cfg.ldap-proxy.group;
isSystemUser = true;
}; };
users.groups.pi-ldap-proxy = mkIf (cfg.ldap-proxy.group == "pi-ldap-proxy") {}; users.groups.pi-ldap-proxy = mkIf (cfg.ldap-proxy.group == "pi-ldap-proxy") {};

View file

@ -607,6 +607,7 @@ in {
home = "${cfg.home}"; home = "${cfg.home}";
group = "nextcloud"; group = "nextcloud";
createHome = true; createHome = true;
isSystemUser = true;
}; };
users.groups.nextcloud.members = [ "nextcloud" config.services.nginx.user ]; users.groups.nextcloud.members = [ "nextcloud" config.services.nginx.user ];

View file

@ -31,7 +31,7 @@ in {
firewall.allowedTCPPorts = [ 3306 4444 4567 4568 ]; firewall.allowedTCPPorts = [ 3306 4444 4567 4568 ];
firewall.allowedUDPPorts = [ 4567 ]; firewall.allowedUDPPorts = [ 4567 ];
}; };
users.users.testuser = { }; users.users.testuser = { isSystemUser = true; };
systemd.services.mysql = with pkgs; { systemd.services.mysql = with pkgs; {
path = [ mysqlenv-common mysqlenv-mariabackup ]; path = [ mysqlenv-common mysqlenv-mariabackup ];
}; };
@ -89,7 +89,7 @@ in {
firewall.allowedTCPPorts = [ 3306 4444 4567 4568 ]; firewall.allowedTCPPorts = [ 3306 4444 4567 4568 ];
firewall.allowedUDPPorts = [ 4567 ]; firewall.allowedUDPPorts = [ 4567 ];
}; };
users.users.testuser = { }; users.users.testuser = { isSystemUser = true; };
systemd.services.mysql = with pkgs; { systemd.services.mysql = with pkgs; {
path = [ mysqlenv-common mysqlenv-mariabackup ]; path = [ mysqlenv-common mysqlenv-mariabackup ];
}; };
@ -136,7 +136,7 @@ in {
firewall.allowedTCPPorts = [ 3306 4444 4567 4568 ]; firewall.allowedTCPPorts = [ 3306 4444 4567 4568 ];
firewall.allowedUDPPorts = [ 4567 ]; firewall.allowedUDPPorts = [ 4567 ];
}; };
users.users.testuser = { }; users.users.testuser = { isSystemUser = true; };
systemd.services.mysql = with pkgs; { systemd.services.mysql = with pkgs; {
path = [ mysqlenv-common mysqlenv-mariabackup ]; path = [ mysqlenv-common mysqlenv-mariabackup ];
}; };

View file

@ -31,7 +31,7 @@ in {
firewall.allowedTCPPorts = [ 3306 4444 4567 4568 ]; firewall.allowedTCPPorts = [ 3306 4444 4567 4568 ];
firewall.allowedUDPPorts = [ 4567 ]; firewall.allowedUDPPorts = [ 4567 ];
}; };
users.users.testuser = { }; users.users.testuser = { isSystemUser = true; };
systemd.services.mysql = with pkgs; { systemd.services.mysql = with pkgs; {
path = [ mysqlenv-common mysqlenv-rsync ]; path = [ mysqlenv-common mysqlenv-rsync ];
}; };
@ -84,7 +84,7 @@ in {
firewall.allowedTCPPorts = [ 3306 4444 4567 4568 ]; firewall.allowedTCPPorts = [ 3306 4444 4567 4568 ];
firewall.allowedUDPPorts = [ 4567 ]; firewall.allowedUDPPorts = [ 4567 ];
}; };
users.users.testuser = { }; users.users.testuser = { isSystemUser = true; };
systemd.services.mysql = with pkgs; { systemd.services.mysql = with pkgs; {
path = [ mysqlenv-common mysqlenv-rsync ]; path = [ mysqlenv-common mysqlenv-rsync ];
}; };
@ -130,7 +130,7 @@ in {
firewall.allowedTCPPorts = [ 3306 4444 4567 4568 ]; firewall.allowedTCPPorts = [ 3306 4444 4567 4568 ];
firewall.allowedUDPPorts = [ 4567 ]; firewall.allowedUDPPorts = [ 4567 ];
}; };
users.users.testuser = { }; users.users.testuser = { isSystemUser = true; };
systemd.services.mysql = with pkgs; { systemd.services.mysql = with pkgs; {
path = [ mysqlenv-common mysqlenv-rsync ]; path = [ mysqlenv-common mysqlenv-rsync ];
}; };

View file

@ -9,8 +9,8 @@ import ./../make-test-python.nix ({ pkgs, ...} : {
{ pkgs, ... }: { pkgs, ... }:
{ {
users.users.testuser = { }; users.users.testuser = { isSystemUser = true; };
users.users.testuser2 = { }; users.users.testuser2 = { isSystemUser = true; };
services.mysql.enable = true; services.mysql.enable = true;
services.mysql.initialDatabases = [ services.mysql.initialDatabases = [
{ name = "testdb3"; schema = ./testdb.sql; } { name = "testdb3"; schema = ./testdb.sql; }
@ -44,8 +44,8 @@ import ./../make-test-python.nix ({ pkgs, ...} : {
# Kernel panic - not syncing: Out of memory: compulsory panic_on_oom is enabled # Kernel panic - not syncing: Out of memory: compulsory panic_on_oom is enabled
virtualisation.memorySize = 1024; virtualisation.memorySize = 1024;
users.users.testuser = { }; users.users.testuser = { isSystemUser = true; };
users.users.testuser2 = { }; users.users.testuser2 = { isSystemUser = true; };
services.mysql.enable = true; services.mysql.enable = true;
services.mysql.initialDatabases = [ services.mysql.initialDatabases = [
{ name = "testdb3"; schema = ./testdb.sql; } { name = "testdb3"; schema = ./testdb.sql; }
@ -75,8 +75,8 @@ import ./../make-test-python.nix ({ pkgs, ...} : {
{ pkgs, ... }: { pkgs, ... }:
{ {
users.users.testuser = { }; users.users.testuser = { isSystemUser = true; };
users.users.testuser2 = { }; users.users.testuser2 = { isSystemUser = true; };
services.mysql.enable = true; services.mysql.enable = true;
services.mysql.initialScript = pkgs.writeText "mariadb-init.sql" '' services.mysql.initialScript = pkgs.writeText "mariadb-init.sql" ''
ALTER USER root@localhost IDENTIFIED WITH unix_socket; ALTER USER root@localhost IDENTIFIED WITH unix_socket;

View file

@ -22,11 +22,10 @@ in
users.users."member" = { users.users."member" = {
createHome = false; createHome = false;
description = "A member of the redis group"; description = "A member of the redis group";
isNormalUser = true;
extraGroups = [ extraGroups = [
"redis" "redis"
]; ];
group = "users";
shell = "/bin/sh";
}; };
}; };
}; };

View file

@ -274,7 +274,10 @@ in
I find cows to be evil don't you? I find cows to be evil don't you?
''; '';
users.users.tester.password = "test"; users.users.tester = {
isNormalUser = true;
password = "test";
};
services.postfix = { services.postfix = {
enable = true; enable = true;
destination = ["example.com"]; destination = ["example.com"];

View file

@ -13,14 +13,17 @@ in import ./make-test-python.nix ({ pkgs, ... }: {
users = { users = {
mutableUsers = true; mutableUsers = true;
users.emma = { users.emma = {
isNormalUser = true;
password = password1; password = password1;
shell = pkgs.bash; shell = pkgs.bash;
}; };
users.layla = { users.layla = {
isNormalUser = true;
password = password2; password = password2;
shell = pkgs.shadow; shell = pkgs.shadow;
}; };
users.ash = { users.ash = {
isNormalUser = true;
password = password4; password = password4;
shell = pkgs.bash; shell = pkgs.bash;
}; };

View file

@ -150,6 +150,7 @@ import ./make-test-python.nix {
config.users.groups.chroot-testgroup = {}; config.users.groups.chroot-testgroup = {};
config.users.users.chroot-testuser = { config.users.users.chroot-testuser = {
isSystemUser = true;
description = "Chroot Test User"; description = "Chroot Test User";
group = "chroot-testgroup"; group = "chroot-testgroup";
}; };

View file

@ -132,12 +132,15 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
users.users = { users.users = {
# user that is permitted to access the unix socket # user that is permitted to access the unix socket
someuser.extraGroups = [ someuser = {
config.users.users.unbound.group isSystemUser = true;
]; extraGroups = [
config.users.users.unbound.group
];
};
# user that is not permitted to access the unix socket # user that is not permitted to access the unix socket
unauthorizeduser = {}; unauthorizeduser = { isSystemUser = true; };
}; };
environment.etc = { environment.etc = {