diff options
Diffstat (limited to 'nixos/modules/config/users-groups.nix')
-rw-r--r-- | nixos/modules/config/users-groups.nix | 108 |
1 files changed, 70 insertions, 38 deletions
diff --git a/nixos/modules/config/users-groups.nix b/nixos/modules/config/users-groups.nix index 56b7af98b61..d5e7745c53f 100644 --- a/nixos/modules/config/users-groups.nix +++ b/nixos/modules/config/users-groups.nix @@ -6,6 +6,12 @@ let ids = config.ids; cfg = config.users; + isPasswdCompatible = str: !(hasInfix ":" str || hasInfix "\n" str); + passwdEntry = type: lib.types.addCheck type isPasswdCompatible // { + name = "passwdEntry ${type.name}"; + description = "${type.description}, not containing newlines or colons"; + }; + # Check whether a password hash will allow login. allowsLogin = hash: hash == "" # login without password @@ -35,8 +41,7 @@ let ''; hashedPasswordDescription = '' - To generate a hashed password install the <literal>mkpasswd</literal> - package and run <literal>mkpasswd -m sha-512</literal>. + To generate a hashed password run <literal>mkpasswd -m sha-512</literal>. If set to an empty string (<literal>""</literal>), this user will be able to log in without being asked for a password (but not via remote @@ -55,7 +60,7 @@ let options = { name = mkOption { - type = types.str; + type = passwdEntry types.str; apply = x: assert (builtins.stringLength x < 32 || abort "Username '${x}' is longer than 31 characters which is not allowed!"); x; description = '' The name of the user account. If undefined, the name of the @@ -64,7 +69,7 @@ let }; description = mkOption { - type = types.str; + type = passwdEntry types.str; default = ""; example = "Alice Q. User"; description = '' @@ -93,6 +98,8 @@ let the user's UID is allocated in the range for system users (below 500) or in the range for normal users (starting at 1000). + Exactly one of <literal>isNormalUser</literal> and + <literal>isSystemUser</literal> must be true. ''; }; @@ -108,6 +115,8 @@ let <option>useDefaultShell</option> to <literal>true</literal>, and <option>isSystemUser</option> to <literal>false</literal>. + Exactly one of <literal>isNormalUser</literal> and + <literal>isSystemUser</literal> must be true. ''; }; @@ -125,7 +134,7 @@ let }; home = mkOption { - type = types.path; + type = passwdEntry types.path; default = "/var/empty"; description = "The user's home directory."; }; @@ -139,8 +148,22 @@ let ''; }; + pamMount = mkOption { + type = with types; attrsOf str; + default = {}; + description = '' + Attributes for user's entry in + <filename>pam_mount.conf.xml</filename>. + Useful attributes might include <code>path</code>, + <code>options</code>, <code>fstype</code>, and <code>server</code>. + See <link + xlink:href="http://pam-mount.sourceforge.net/pam_mount.conf.5.html" /> + for more information. + ''; + }; + shell = mkOption { - type = types.either types.shellPackage types.path; + type = types.nullOr (types.either types.shellPackage (passwdEntry types.path)); default = pkgs.shadow; defaultText = "pkgs.shadow"; example = literalExample "pkgs.bashInteractive"; @@ -185,10 +208,8 @@ let type = types.bool; default = false; description = '' - If true, the home directory will be created automatically. If this - option is true and the home directory already exists but is not - owned by the user, directory owner and group will be changed to - match the user. + Whether to create the home directory and ensure ownership as well as + permissions to match the user. ''; }; @@ -202,7 +223,7 @@ let }; hashedPassword = mkOption { - type = with types; nullOr str; + type = with types; nullOr (passwdEntry str); default = null; description = '' Specifies the hashed password for the user. @@ -236,7 +257,7 @@ let }; initialHashedPassword = mkOption { - type = with types; nullOr str; + type = with types; nullOr (passwdEntry str); default = null; description = '' Specifies the initial hashed password for the user, i.e. the @@ -308,7 +329,7 @@ let options = { name = mkOption { - type = types.str; + type = passwdEntry types.str; description = '' The name of the group. If undefined, the name of the attribute set will be used. @@ -325,7 +346,7 @@ let }; members = mkOption { - type = with types; listOf str; + type = with types; listOf (passwdEntry str); default = []; description = '' The user names of the group members, added to the @@ -353,7 +374,7 @@ let count = mkOption { type = types.int; default = 1; - description = ''Count of subordinate user ids''; + description = "Count of subordinate user ids"; }; }; }; @@ -370,12 +391,12 @@ let count = mkOption { type = types.int; default = 1; - description = ''Count of subordinate group ids''; + description = "Count of subordinate group ids"; }; }; }; - idsAreUnique = set: idAttr: !(fold (name: args@{ dup, acc }: + idsAreUnique = set: idAttr: !(foldr (name: args@{ dup, acc }: let id = builtins.toString (builtins.getAttr idAttr (builtins.getAttr name set)); exists = builtins.hasAttr id acc; @@ -463,7 +484,7 @@ in { users.users = mkOption { default = {}; - type = with types; loaOf (submodule userOpts); + type = with types; attrsOf (submodule userOpts); example = { alice = { uid = 1234; @@ -487,7 +508,7 @@ in { { students.gid = 1001; hackers = { }; }; - type = with types; loaOf (submodule groupOpts); + type = with types; attrsOf (submodule groupOpts); description = '' Additional groups to be created automatically by the system. ''; @@ -510,6 +531,7 @@ in { }; nobody = { uid = ids.uids.nobody; + isSystemUser = true; description = "Unprivileged account (don't use!)"; group = "nogroup"; }; @@ -537,6 +559,7 @@ in { input.gid = ids.gids.input; kvm.gid = ids.gids.kvm; render.gid = ids.gids.render; + shadow.gid = ids.gids.shadow; }; system.activationScripts.users = stringAfter [ "stdio" ] @@ -544,10 +567,8 @@ in { install -m 0700 -d /root install -m 0755 -d /home - ${pkgs.perl}/bin/perl -w \ - -I${pkgs.perlPackages.FileSlurp}/${pkgs.perl.libPrefix} \ - -I${pkgs.perlPackages.JSON}/${pkgs.perl.libPrefix} \ - ${./update-users-groups.pl} ${spec} + ${pkgs.perl.withPackages (p: [ p.FileSlurp p.JSON ])}/bin/perl \ + -w ${./update-users-groups.pl} ${spec} ''; # for backwards compatibility @@ -556,7 +577,7 @@ in { # Install all the user shells environment.systemPackages = systemShells; - environment.etc = (mapAttrs' (name: { packages, ... }: { + environment.etc = (mapAttrs' (_: { packages, name, ... }: { name = "profiles/per-user/${name}"; value.source = pkgs.buildEnv { name = "user-environment"; @@ -581,8 +602,8 @@ in { # password or an SSH authorized key. Privileged accounts are # root and users in the wheel group. assertion = !cfg.mutableUsers -> - any id ((mapAttrsToList (name: cfg: - (name == "root" + any id ((mapAttrsToList (_: cfg: + (cfg.name == "root" || cfg.group == "wheel" || elem "wheel" cfg.extraGroups) && @@ -598,21 +619,32 @@ in { 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.''; } - ] ++ flip mapAttrsToList cfg.users (name: user: - { + ] ++ flatten (flip mapAttrsToList cfg.users (name: user: + [ + { assertion = (user.hashedPassword != null) - -> (builtins.match ".*:.*" user.hashedPassword == null); + -> (builtins.match ".*:.*" user.hashedPassword == null); message = '' - The password hash of user "${name}" contains a ":" character. - This is invalid and would break the login system because the fields - of /etc/shadow (file where hashes are stored) are colon-separated. - Please check the value of option `users.users."${name}".hashedPassword`.''; - } - ); + The password hash of user "${user.name}" contains a ":" character. + This is invalid and would break the login system because the fields + of /etc/shadow (file where hashes are stored) are colon-separated. + 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 = builtins.filter (x: x != null) ( - flip mapAttrsToList cfg.users (name: user: + flip mapAttrsToList cfg.users (_: user: # This regex matches a subset of the Modular Crypto Format (MCF)[1] # informal standard. Since this depends largely on the OS or the # specific implementation of crypt(3) we only support the (sane) @@ -635,9 +667,9 @@ in { && user.hashedPassword != "" # login without password && builtins.match mcf user.hashedPassword == null) then '' - The password hash of user "${name}" may be invalid. You must set a + The password hash of user "${user.name}" may be invalid. You must set a valid hash or the user will be locked out of their account. Please - check the value of option `users.users."${name}".hashedPassword`.'' + check the value of option `users.users."${user.name}".hashedPassword`.'' else null )); |