vuls: init at 0.27.0
[NixPkgs.git] / nixos / modules / config / users-groups.nix
blob69646e550f1f3b0eceb9fb427dce0c1442aa6273
1 { config, lib, utils, pkgs, ... }:
3 let
4   inherit (lib)
5     any
6     attrNames
7     attrValues
8     concatMap
9     concatStrings
10     elem
11     filter
12     filterAttrs
13     flatten
14     flip
15     foldr
16     getAttr
17     hasAttr
18     id
19     length
20     listToAttrs
21     literalExpression
22     mapAttrs'
23     mapAttrsToList
24     match
25     mkAliasOptionModuleMD
26     mkDefault
27     mkIf
28     mkMerge
29     mkOption
30     mkRenamedOptionModule
31     optional
32     optionals
33     sort
34     stringAfter
35     stringLength
36     trace
37     types
38     xor
39     ;
41   ids = config.ids;
42   cfg = config.users;
44   # Check whether a password hash will allow login.
45   allowsLogin = hash:
46     hash == "" # login without password
47     || !(lib.elem hash
48       [ null   # password login disabled
49         "!"    # password login disabled
50         "!!"   # a variant of "!"
51         "*"    # password unset
52       ]);
54   passwordDescription = ''
55     The options {option}`hashedPassword`,
56     {option}`password` and {option}`hashedPasswordFile`
57     controls what password is set for the user.
58     {option}`hashedPassword` overrides both
59     {option}`password` and {option}`hashedPasswordFile`.
60     {option}`password` overrides {option}`hashedPasswordFile`.
61     If none of these three options are set, no password is assigned to
62     the user, and the user will not be able to do password logins.
63     If the option {option}`users.mutableUsers` is true, the
64     password defined in one of the three options will only be set when
65     the user is created for the first time. After that, you are free to
66     change the password with the ordinary user management commands. If
67     {option}`users.mutableUsers` is false, you cannot change
68     user passwords, they will always be set according to the password
69     options.
70   '';
72   hashedPasswordDescription = ''
73     To generate a hashed password run `mkpasswd`.
75     If set to an empty string (`""`), this user will
76     be able to log in without being asked for a password (but not via remote
77     services such as SSH, or indirectly via {command}`su` or
78     {command}`sudo`). This should only be used for e.g. bootable
79     live systems. Note: this is different from setting an empty password,
80     which can be achieved using {option}`users.users.<name?>.password`.
82     If set to `null` (default) this user will not
83     be able to log in using a password (i.e. via {command}`login`
84     command).
85   '';
87   userOpts = { name, config, ... }: {
89     options = {
91       name = mkOption {
92         type = types.passwdEntry types.str;
93         apply = x: assert (stringLength x < 32 || abort "Username '${x}' is longer than 31 characters which is not allowed!"); x;
94         description = ''
95           The name of the user account. If undefined, the name of the
96           attribute set will be used.
97         '';
98       };
100       description = mkOption {
101         type = types.passwdEntry types.str;
102         default = "";
103         example = "Alice Q. User";
104         description = ''
105           A short description of the user account, typically the
106           user's full name.  This is actually the “GECOS” or “comment”
107           field in {file}`/etc/passwd`.
108         '';
109       };
111       uid = mkOption {
112         type = with types; nullOr int;
113         default = null;
114         description = ''
115           The account UID. If the UID is null, a free UID is picked on
116           activation.
117         '';
118       };
120       isSystemUser = mkOption {
121         type = types.bool;
122         default = false;
123         description = ''
124           Indicates if the user is a system user or not. This option
125           only has an effect if {option}`uid` is
126           {option}`null`, in which case it determines whether
127           the user's UID is allocated in the range for system users
128           (below 1000) or in the range for normal users (starting at
129           1000).
130           Exactly one of `isNormalUser` and
131           `isSystemUser` must be true.
132         '';
133       };
135       isNormalUser = mkOption {
136         type = types.bool;
137         default = false;
138         description = ''
139           Indicates whether this is an account for a “real” user.
140           This automatically sets {option}`group` to `users`,
141           {option}`createHome` to `true`,
142           {option}`home` to {file}`/home/«username»`,
143           {option}`useDefaultShell` to `true`,
144           and {option}`isSystemUser` to `false`.
145           Exactly one of `isNormalUser` and `isSystemUser` must be true.
146         '';
147       };
149       group = mkOption {
150         type = types.str;
151         apply = x: assert (stringLength x < 32 || abort "Group name '${x}' is longer than 31 characters which is not allowed!"); x;
152         default = "";
153         description = "The user's primary group.";
154       };
156       extraGroups = mkOption {
157         type = types.listOf types.str;
158         default = [];
159         description = "The user's auxiliary groups.";
160       };
162       home = mkOption {
163         type = types.passwdEntry types.path;
164         default = "/var/empty";
165         description = "The user's home directory.";
166       };
168       homeMode = mkOption {
169         type = types.strMatching "[0-7]{1,5}";
170         default = "700";
171         description = "The user's home directory mode in numeric format. See chmod(1). The mode is only applied if {option}`users.users.<name>.createHome` is true.";
172       };
174       cryptHomeLuks = mkOption {
175         type = with types; nullOr str;
176         default = null;
177         description = ''
178           Path to encrypted luks device that contains
179           the user's home directory.
180         '';
181       };
183       pamMount = mkOption {
184         type = with types; attrsOf str;
185         default = {};
186         description = ''
187           Attributes for user's entry in
188           {file}`pam_mount.conf.xml`.
189           Useful attributes might include `path`,
190           `options`, `fstype`, and `server`.
191           See <https://pam-mount.sourceforge.net/pam_mount.conf.5.html>
192           for more information.
193         '';
194       };
196       shell = mkOption {
197         type = types.nullOr (types.either types.shellPackage (types.passwdEntry types.path));
198         default = pkgs.shadow;
199         defaultText = literalExpression "pkgs.shadow";
200         example = literalExpression "pkgs.bashInteractive";
201         description = ''
202           The path to the user's shell. Can use shell derivations,
203           like `pkgs.bashInteractive`. Don’t
204           forget to enable your shell in
205           `programs` if necessary,
206           like `programs.zsh.enable = true;`.
207         '';
208       };
210       ignoreShellProgramCheck = mkOption {
211         type = types.bool;
212         default = false;
213         description = ''
214           By default, nixos will check that programs.SHELL.enable is set to
215           true if the user has a custom shell specified. If that behavior isn't
216           required and there are custom overrides in place to make sure that the
217           shell is functional, set this to true.
218         '';
219       };
221       subUidRanges = mkOption {
222         type = with types; listOf (submodule subordinateUidRange);
223         default = [];
224         example = [
225           { startUid = 1000; count = 1; }
226           { startUid = 100001; count = 65534; }
227         ];
228         description = ''
229           Subordinate user ids that user is allowed to use.
230           They are set into {file}`/etc/subuid` and are used
231           by `newuidmap` for user namespaces.
232         '';
233       };
235       subGidRanges = mkOption {
236         type = with types; listOf (submodule subordinateGidRange);
237         default = [];
238         example = [
239           { startGid = 100; count = 1; }
240           { startGid = 1001; count = 999; }
241         ];
242         description = ''
243           Subordinate group ids that user is allowed to use.
244           They are set into {file}`/etc/subgid` and are used
245           by `newgidmap` for user namespaces.
246         '';
247       };
249       autoSubUidGidRange = mkOption {
250         type = types.bool;
251         default = false;
252         example = true;
253         description = ''
254           Automatically allocate subordinate user and group ids for this user.
255           Allocated range is currently always of size 65536.
256         '';
257       };
259       createHome = mkOption {
260         type = types.bool;
261         default = false;
262         description = ''
263           Whether to create the home directory and ensure ownership as well as
264           permissions to match the user.
265         '';
266       };
268       useDefaultShell = mkOption {
269         type = types.bool;
270         default = false;
271         description = ''
272           If true, the user's shell will be set to
273           {option}`users.defaultUserShell`.
274         '';
275       };
277       hashedPassword = mkOption {
278         type = with types; nullOr (passwdEntry str);
279         default = null;
280         description = ''
281           Specifies the hashed password for the user.
282           ${passwordDescription}
283           ${hashedPasswordDescription}
284         '';
285       };
287       password = mkOption {
288         type = with types; nullOr str;
289         default = null;
290         description = ''
291           Specifies the (clear text) password for the user.
292           Warning: do not set confidential information here
293           because it is world-readable in the Nix store. This option
294           should only be used for public accounts.
295           ${passwordDescription}
296         '';
297       };
299       hashedPasswordFile = mkOption {
300         type = with types; nullOr str;
301         default = cfg.users.${name}.passwordFile;
302         defaultText = literalExpression "null";
303         description = ''
304           The full path to a file that contains the hash of the user's
305           password. The password file is read on each system activation. The
306           file should contain exactly one line, which should be the password in
307           an encrypted form that is suitable for the `chpasswd -e` command.
308           ${passwordDescription}
309         '';
310       };
312       passwordFile = mkOption {
313         type = with types; nullOr str;
314         default = null;
315         visible = false;
316         description = "Deprecated alias of hashedPasswordFile";
317       };
319       initialHashedPassword = mkOption {
320         type = with types; nullOr (passwdEntry str);
321         default = null;
322         description = ''
323           Specifies the initial hashed password for the user, i.e. the
324           hashed password assigned if the user does not already
325           exist. If {option}`users.mutableUsers` is true, the
326           password can be changed subsequently using the
327           {command}`passwd` command. Otherwise, it's
328           equivalent to setting the {option}`hashedPassword` option.
330           Note that the {option}`hashedPassword` option will override
331           this option if both are set.
333           ${hashedPasswordDescription}
334         '';
335       };
337       initialPassword = mkOption {
338         type = with types; nullOr str;
339         default = null;
340         description = ''
341           Specifies the initial password for the user, i.e. the
342           password assigned if the user does not already exist. If
343           {option}`users.mutableUsers` is true, the password
344           can be changed subsequently using the
345           {command}`passwd` command. Otherwise, it's
346           equivalent to setting the {option}`password`
347           option. The same caveat applies: the password specified here
348           is world-readable in the Nix store, so it should only be
349           used for guest accounts or passwords that will be changed
350           promptly.
352           Note that the {option}`password` option will override this
353           option if both are set.
354         '';
355       };
357       packages = mkOption {
358         type = types.listOf types.package;
359         default = [];
360         example = literalExpression "[ pkgs.firefox pkgs.thunderbird ]";
361         description = ''
362           The set of packages that should be made available to the user.
363           This is in contrast to {option}`environment.systemPackages`,
364           which adds packages to all users.
365         '';
366       };
368       expires = mkOption {
369         type = types.nullOr (types.strMatching "[[:digit:]]{4}-[[:digit:]]{2}-[[:digit:]]{2}");
370         default = null;
371         description = ''
372           Set the date on which the user's account will no longer be
373           accessible. The date is expressed in the format YYYY-MM-DD, or null
374           to disable the expiry.
375           A user whose account is locked must contact the system
376           administrator before being able to use the system again.
377         '';
378       };
380       linger = mkOption {
381         type = types.bool;
382         default = false;
383         description = ''
384           Whether to enable lingering for this user. If true, systemd user
385           units will start at boot, rather than starting at login and stopping
386           at logout. This is the declarative equivalent of running
387           `loginctl enable-linger` for this user.
389           If false, user units will not be started until the user logs in, and
390           may be stopped on logout depending on the settings in `logind.conf`.
391         '';
392       };
393     };
395     config = mkMerge
396       [ { name = mkDefault name;
397           shell = mkIf config.useDefaultShell (mkDefault cfg.defaultUserShell);
398         }
399         (mkIf config.isNormalUser {
400           group = mkDefault "users";
401           createHome = mkDefault true;
402           home = mkDefault "/home/${config.name}";
403           homeMode = mkDefault "700";
404           useDefaultShell = mkDefault true;
405           isSystemUser = mkDefault false;
406         })
407         # If !mutableUsers, setting ‘initialPassword’ is equivalent to
408         # setting ‘password’ (and similarly for hashed passwords).
409         (mkIf (!cfg.mutableUsers && config.initialPassword != null) {
410           password = mkDefault config.initialPassword;
411         })
412         (mkIf (!cfg.mutableUsers && config.initialHashedPassword != null) {
413           hashedPassword = mkDefault config.initialHashedPassword;
414         })
415         (mkIf (config.isNormalUser && config.subUidRanges == [] && config.subGidRanges == []) {
416           autoSubUidGidRange = mkDefault true;
417         })
418       ];
420   };
422   groupOpts = { name, config, ... }: {
424     options = {
426       name = mkOption {
427         type = types.passwdEntry types.str;
428         description = ''
429           The name of the group. If undefined, the name of the attribute set
430           will be used.
431         '';
432       };
434       gid = mkOption {
435         type = with types; nullOr int;
436         default = null;
437         description = ''
438           The group GID. If the GID is null, a free GID is picked on
439           activation.
440         '';
441       };
443       members = mkOption {
444         type = with types; listOf (passwdEntry str);
445         default = [];
446         description = ''
447           The user names of the group members, added to the
448           `/etc/group` file.
449         '';
450       };
452     };
454     config = {
455       name = mkDefault name;
457       members = mapAttrsToList (n: u: u.name) (
458         filterAttrs (n: u: elem config.name u.extraGroups) cfg.users
459       );
460     };
462   };
464   subordinateUidRange = {
465     options = {
466       startUid = mkOption {
467         type = types.int;
468         description = ''
469           Start of the range of subordinate user ids that user is
470           allowed to use.
471         '';
472       };
473       count = mkOption {
474         type = types.int;
475         default = 1;
476         description = "Count of subordinate user ids";
477       };
478     };
479   };
481   subordinateGidRange = {
482     options = {
483       startGid = mkOption {
484         type = types.int;
485         description = ''
486           Start of the range of subordinate group ids that user is
487           allowed to use.
488         '';
489       };
490       count = mkOption {
491         type = types.int;
492         default = 1;
493         description = "Count of subordinate group ids";
494       };
495     };
496   };
498   idsAreUnique = set: idAttr: !(foldr (name: args@{ dup, acc }:
499     let
500       id = toString (getAttr idAttr (getAttr name set));
501       exists = hasAttr id acc;
502       newAcc = acc // (listToAttrs [ { name = id; value = true; } ]);
503     in if dup then args else if exists
504       then trace "Duplicate ${idAttr} ${id}" { dup = true; acc = null; }
505       else { dup = false; acc = newAcc; }
506     ) { dup = false; acc = {}; } (attrNames set)).dup;
508   uidsAreUnique = idsAreUnique (filterAttrs (n: u: u.uid != null) cfg.users) "uid";
509   gidsAreUnique = idsAreUnique (filterAttrs (n: g: g.gid != null) cfg.groups) "gid";
510   sdInitrdUidsAreUnique = idsAreUnique (filterAttrs (n: u: u.uid != null) config.boot.initrd.systemd.users) "uid";
511   sdInitrdGidsAreUnique = idsAreUnique (filterAttrs (n: g: g.gid != null) config.boot.initrd.systemd.groups) "gid";
512   groupNames = lib.mapAttrsToList (n: g: g.name) cfg.groups;
513   usersWithoutExistingGroup = lib.filterAttrs (n: u: u.group != "" && !lib.elem u.group groupNames) cfg.users;
515   spec = pkgs.writeText "users-groups.json" (builtins.toJSON {
516     inherit (cfg) mutableUsers;
517     users = mapAttrsToList (_: u:
518       { inherit (u)
519           name uid group description home homeMode createHome isSystemUser
520           password hashedPasswordFile hashedPassword
521           autoSubUidGidRange subUidRanges subGidRanges
522           initialPassword initialHashedPassword expires;
523         shell = utils.toShellPath u.shell;
524       }) cfg.users;
525     groups = attrValues cfg.groups;
526   });
528   systemShells =
529     let
530       shells = mapAttrsToList (_: u: u.shell) cfg.users;
531     in
532       filter types.shellPackage.check shells;
534   lingeringUsers = map (u: u.name) (attrValues (flip filterAttrs cfg.users (n: u: u.linger)));
535 in {
536   imports = [
537     (mkAliasOptionModuleMD [ "users" "extraUsers" ] [ "users" "users" ])
538     (mkAliasOptionModuleMD [ "users" "extraGroups" ] [ "users" "groups" ])
539     (mkRenamedOptionModule ["security" "initialRootPassword"] ["users" "users" "root" "initialHashedPassword"])
540   ];
542   ###### interface
543   options = {
545     users.mutableUsers = mkOption {
546       type = types.bool;
547       default = true;
548       description = ''
549         If set to `true`, you are free to add new users and groups to the system
550         with the ordinary `useradd` and
551         `groupadd` commands. On system activation, the
552         existing contents of the `/etc/passwd` and
553         `/etc/group` files will be merged with the
554         contents generated from the `users.users` and
555         `users.groups` options.
556         The initial password for a user will be set
557         according to `users.users`, but existing passwords
558         will not be changed.
560         ::: {.warning}
561         If set to `false`, the contents of the user and
562         group files will simply be replaced on system activation. This also
563         holds for the user passwords; all changed
564         passwords will be reset according to the
565         `users.users` configuration on activation.
566         :::
567       '';
568     };
570     users.enforceIdUniqueness = mkOption {
571       type = types.bool;
572       default = true;
573       description = ''
574         Whether to require that no two users/groups share the same uid/gid.
575       '';
576     };
578     users.users = mkOption {
579       default = {};
580       type = with types; attrsOf (submodule userOpts);
581       example = {
582         alice = {
583           uid = 1234;
584           description = "Alice Q. User";
585           home = "/home/alice";
586           createHome = true;
587           group = "users";
588           extraGroups = ["wheel"];
589           shell = "/bin/sh";
590         };
591       };
592       description = ''
593         Additional user accounts to be created automatically by the system.
594         This can also be used to set options for root.
595       '';
596     };
598     users.groups = mkOption {
599       default = {};
600       example =
601         { students.gid = 1001;
602           hackers = { };
603         };
604       type = with types; attrsOf (submodule groupOpts);
605       description = ''
606         Additional groups to be created automatically by the system.
607       '';
608     };
611     users.allowNoPasswordLogin = mkOption {
612       type = types.bool;
613       default = false;
614       description = ''
615         Disable checking that at least the `root` user or a user in the `wheel` group can log in using
616         a password or an SSH key.
618         WARNING: enabling this can lock you out of your system. Enable this only if you know what are you doing.
619       '';
620     };
622     # systemd initrd
623     boot.initrd.systemd.users = mkOption {
624       description = ''
625         Users to include in initrd.
626       '';
627       default = {};
628       type = types.attrsOf (types.submodule ({ name, ... }: {
629         options.uid = mkOption {
630           type = types.int;
631           description = ''
632             ID of the user in initrd.
633           '';
634           defaultText = literalExpression "config.users.users.\${name}.uid";
635           default = cfg.users.${name}.uid;
636         };
637         options.group = mkOption {
638           type = types.singleLineStr;
639           description = ''
640             Group the user belongs to in initrd.
641           '';
642           defaultText = literalExpression "config.users.users.\${name}.group";
643           default = cfg.users.${name}.group;
644         };
645         options.shell = mkOption {
646           type = types.passwdEntry types.path;
647           description = ''
648             The path to the user's shell in initrd.
649           '';
650           default = "${pkgs.shadow}/bin/nologin";
651           defaultText = literalExpression "\${pkgs.shadow}/bin/nologin";
652         };
653       }));
654     };
656     boot.initrd.systemd.groups = mkOption {
657       description = ''
658         Groups to include in initrd.
659       '';
660       default = {};
661       type = types.attrsOf (types.submodule ({ name, ... }: {
662         options.gid = mkOption {
663           type = types.int;
664           description = ''
665             ID of the group in initrd.
666           '';
667           defaultText = literalExpression "config.users.groups.\${name}.gid";
668           default = cfg.groups.${name}.gid;
669         };
670       }));
671     };
672   };
675   ###### implementation
677   config = let
678     cryptSchemeIdPatternGroup = "(${lib.concatStringsSep "|" pkgs.libxcrypt.enabledCryptSchemeIds})";
679   in {
681     users.users = {
682       root = {
683         uid = ids.uids.root;
684         description = "System administrator";
685         home = "/root";
686         shell = mkDefault cfg.defaultUserShell;
687         group = "root";
688       };
689       nobody = {
690         uid = ids.uids.nobody;
691         isSystemUser = true;
692         description = "Unprivileged account (don't use!)";
693         group = "nogroup";
694       };
695     };
697     users.groups = {
698       root.gid = ids.gids.root;
699       wheel.gid = ids.gids.wheel;
700       disk.gid = ids.gids.disk;
701       kmem.gid = ids.gids.kmem;
702       tty.gid = ids.gids.tty;
703       floppy.gid = ids.gids.floppy;
704       uucp.gid = ids.gids.uucp;
705       lp.gid = ids.gids.lp;
706       cdrom.gid = ids.gids.cdrom;
707       tape.gid = ids.gids.tape;
708       audio.gid = ids.gids.audio;
709       video.gid = ids.gids.video;
710       dialout.gid = ids.gids.dialout;
711       nogroup.gid = ids.gids.nogroup;
712       users.gid = ids.gids.users;
713       nixbld.gid = ids.gids.nixbld;
714       utmp.gid = ids.gids.utmp;
715       adm.gid = ids.gids.adm;
716       input.gid = ids.gids.input;
717       kvm.gid = ids.gids.kvm;
718       render.gid = ids.gids.render;
719       sgx.gid = ids.gids.sgx;
720       shadow.gid = ids.gids.shadow;
721     };
723     system.activationScripts.users = if !config.systemd.sysusers.enable then {
724       supportsDryActivation = true;
725       text = ''
726         install -m 0700 -d /root
727         install -m 0755 -d /home
729         ${pkgs.perl.withPackages (p: [ p.FileSlurp p.JSON ])}/bin/perl \
730         -w ${./update-users-groups.pl} ${spec}
731       '';
732     } else ""; # keep around for backwards compatibility
734     systemd.services.linger-users = lib.mkIf ((length lingeringUsers) > 0) {
735       wantedBy = ["multi-user.target"];
736       after = ["systemd-logind.service"];
737       requires = ["systemd-logind.service"];
739       script = let
740         lingerDir = "/var/lib/systemd/linger";
741         lingeringUsersFile = builtins.toFile "lingering-users"
742           (concatStrings (map (s: "${s}\n")
743             (sort (a: b: a < b) lingeringUsers)));  # this sorting is important for `comm` to work correctly
744       in ''
745         mkdir -vp ${lingerDir}
746         cd ${lingerDir}
747         for user in $(ls); do
748           if ! id "$user" >/dev/null; then
749             echo "Removing linger for missing user $user"
750             rm --force -- "$user"
751           fi
752         done
753         ls | sort | comm -3 -1 ${lingeringUsersFile} - | xargs -r ${pkgs.systemd}/bin/loginctl disable-linger
754         ls | sort | comm -3 -2 ${lingeringUsersFile} - | xargs -r ${pkgs.systemd}/bin/loginctl  enable-linger
755       '';
757       serviceConfig.Type = "oneshot";
758     };
760     # Warn about user accounts with deprecated password hashing schemes
761     # This does not work when the users and groups are created by
762     # systemd-sysusers because the users are created too late then.
763     system.activationScripts.hashes = if !config.systemd.sysusers.enable then {
764       deps = [ "users" ];
765       text = ''
766         users=()
767         while IFS=: read -r user hash _; do
768           if [[ "$hash" = "$"* && ! "$hash" =~ ^\''$${cryptSchemeIdPatternGroup}\$ ]]; then
769             users+=("$user")
770           fi
771         done </etc/shadow
773         if (( "''${#users[@]}" )); then
774           echo "
775         WARNING: The following user accounts rely on password hashing algorithms
776         that have been removed. They need to be renewed as soon as possible, as
777         they do prevent their users from logging in."
778           printf ' - %s\n' "''${users[@]}"
779         fi
780       '';
781     } else ""; # keep around for backwards compatibility
783     # for backwards compatibility
784     system.activationScripts.groups = stringAfter [ "users" ] "";
786     # Install all the user shells
787     environment.systemPackages = systemShells;
789     environment.etc = mapAttrs' (_: { packages, name, ... }: {
790       name = "profiles/per-user/${name}";
791       value.source = pkgs.buildEnv {
792         name = "user-environment";
793         paths = packages;
794         inherit (config.environment) pathsToLink extraOutputsToInstall;
795         inherit (config.system.path) ignoreCollisions postBuild;
796       };
797     }) (filterAttrs (_: u: u.packages != []) cfg.users);
799     environment.profiles = [
800       "$HOME/.nix-profile"
801       "\${XDG_STATE_HOME}/nix/profile"
802       "$HOME/.local/state/nix/profile"
803       "/etc/profiles/per-user/$USER"
804     ];
806     # systemd initrd
807     boot.initrd.systemd = lib.mkIf config.boot.initrd.systemd.enable {
808       contents = {
809         "/etc/passwd".text = ''
810           ${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: { uid, group, shell }: let
811             g = config.boot.initrd.systemd.groups.${group};
812           in "${n}:x:${toString uid}:${toString g.gid}::/var/empty:${shell}") config.boot.initrd.systemd.users)}
813         '';
814         "/etc/group".text = ''
815           ${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: { gid }: "${n}:x:${toString gid}:") config.boot.initrd.systemd.groups)}
816         '';
817         "/etc/shells".text = lib.concatStringsSep "\n" (lib.unique (lib.mapAttrsToList (_: u: u.shell) config.boot.initrd.systemd.users)) + "\n";
818       };
820       storePaths = [ "${pkgs.shadow}/bin/nologin" ];
822       users = {
823         root = { shell = lib.mkDefault "/bin/bash"; };
824         nobody = {};
825       };
827       groups = {
828         root = {};
829         nogroup = {};
830         systemd-journal = {};
831         tty = {};
832         dialout = {};
833         kmem = {};
834         input = {};
835         video = {};
836         render = {};
837         sgx = {};
838         audio = {};
839         video = {};
840         lp = {};
841         disk = {};
842         cdrom = {};
843         tape = {};
844         kvm = {};
845       };
846     };
848     assertions = [
849       { assertion = !cfg.enforceIdUniqueness || (uidsAreUnique && gidsAreUnique);
850         message = "UIDs and GIDs must be unique!";
851       }
852       { assertion = !cfg.enforceIdUniqueness || (sdInitrdUidsAreUnique && sdInitrdGidsAreUnique);
853         message = "systemd initrd UIDs and GIDs must be unique!";
854       }
855       { assertion = usersWithoutExistingGroup == {};
856         message =
857           let
858             errUsers = lib.attrNames usersWithoutExistingGroup;
859             missingGroups = lib.unique (lib.mapAttrsToList (n: u: u.group) usersWithoutExistingGroup);
860             mkConfigHint = group: "users.groups.${group} = {};";
861           in ''
862             The following users have a primary group that is undefined: ${lib.concatStringsSep " " errUsers}
863             Hint: Add this to your NixOS configuration:
864               ${lib.concatStringsSep "\n  " (map mkConfigHint missingGroups)}
865           '';
866       }
867       { # If mutableUsers is false, to prevent users creating a
868         # configuration that locks them out of the system, ensure that
869         # there is at least one "privileged" account that has a
870         # password or an SSH authorized key. Privileged accounts are
871         # root and users in the wheel group.
872         # The check does not apply when users.disableLoginPossibilityAssertion
873         # The check does not apply when users.mutableUsers
874         assertion = !cfg.mutableUsers -> !cfg.allowNoPasswordLogin ->
875           any id (mapAttrsToList (name: cfg:
876             (name == "root"
877              || cfg.group == "wheel"
878              || elem "wheel" cfg.extraGroups)
879             &&
880             (allowsLogin cfg.hashedPassword
881              || cfg.password != null
882              || cfg.hashedPasswordFile != null
883              || cfg.openssh.authorizedKeys.keys != []
884              || cfg.openssh.authorizedKeys.keyFiles != [])
885           ) cfg.users ++ [
886             config.security.googleOsLogin.enable
887           ]);
888         message = ''
889           Neither the root account nor any wheel user has a password or SSH authorized key.
890           You must set one to prevent being locked out of your system.
891           If you really want to be locked out of your system, set users.allowNoPasswordLogin = true;
892           However you are most probably better off by setting users.mutableUsers = true; and
893           manually running passwd root to set the root password.
894           '';
895       }
896     ] ++ flatten (flip mapAttrsToList cfg.users (name: user:
897       [
898         {
899         assertion = (user.hashedPassword != null)
900         -> (match ".*:.*" user.hashedPassword == null);
901         message = ''
902             The password hash of user "${user.name}" contains a ":" character.
903             This is invalid and would break the login system because the fields
904             of /etc/shadow (file where hashes are stored) are colon-separated.
905             Please check the value of option `users.users."${user.name}".hashedPassword`.'';
906           }
907           {
908             assertion = let
909               isEffectivelySystemUser = user.isSystemUser || (user.uid != null && user.uid < 1000);
910             in xor isEffectivelySystemUser user.isNormalUser;
911             message = ''
912               Exactly one of users.users.${user.name}.isSystemUser and users.users.${user.name}.isNormalUser must be set.
913             '';
914           }
915           {
916             assertion = user.group != "";
917             message = ''
918               users.users.${user.name}.group is unset. This used to default to
919               nogroup, but this is unsafe. For example you can create a group
920               for this user with:
921               users.users.${user.name}.group = "${user.name}";
922               users.groups.${user.name} = {};
923             '';
924           }
925         ] ++ (map (shell: {
926             assertion = !user.ignoreShellProgramCheck -> (user.shell == pkgs.${shell}) -> (config.programs.${shell}.enable == true);
927             message = ''
928               users.users.${user.name}.shell is set to ${shell}, but
929               programs.${shell}.enable is not true. This will cause the ${shell}
930               shell to lack the basic nix directories in its PATH and might make
931               logging in as that user impossible. You can fix it with:
932               programs.${shell}.enable = true;
934               If you know what you're doing and you are fine with the behavior,
935               set users.users.${user.name}.ignoreShellProgramCheck = true;
936               instead.
937             '';
938           }) [
939           "fish"
940           "xonsh"
941           "zsh"
942         ])
943     ));
945     warnings =
946       flip concatMap (attrValues cfg.users) (user: let
947         unambiguousPasswordConfiguration = 1 >= length (filter (x: x != null) ([
948           user.hashedPassword
949           user.hashedPasswordFile
950           user.password
951         ] ++ optionals cfg.mutableUsers [
952           # For immutable users, initialHashedPassword is set to hashedPassword,
953           # so using these options would always trigger the assertion.
954           user.initialHashedPassword
955           user.initialPassword
956         ]));
957       in optional (!unambiguousPasswordConfiguration) ''
958         The user '${user.name}' has multiple of the options
959         `hashedPassword`, `password`, `hashedPasswordFile`, `initialPassword`
960         & `initialHashedPassword` set to a non-null value.
961         The options silently discard others by the order of precedence
962         given above which can lead to surprising results. To resolve this warning,
963         set at most one of the options above to a non-`null` value.
964       '')
965       ++ filter (x: x != null) (
966         flip mapAttrsToList cfg.users (_: user:
967         # This regex matches a subset of the Modular Crypto Format (MCF)[1]
968         # informal standard. Since this depends largely on the OS or the
969         # specific implementation of crypt(3) we only support the (sane)
970         # schemes implemented by glibc and BSDs. In particular the original
971         # DES hash is excluded since, having no structure, it would validate
972         # common mistakes like typing the plaintext password.
973         #
974         # [1]: https://en.wikipedia.org/wiki/Crypt_(C)
975         let
976           sep = "\\$";
977           base64 = "[a-zA-Z0-9./]+";
978           id = cryptSchemeIdPatternGroup;
979           name = "[a-z0-9-]+";
980           value = "[a-zA-Z0-9/+.-]+";
981           options = "${name}(=${value})?(,${name}=${value})*";
982           scheme  = "${id}(${sep}${options})?";
983           content = "${base64}${sep}${base64}(${sep}${base64})?";
984           mcf = "^${sep}${scheme}${sep}${content}$";
985         in
986         if (allowsLogin user.hashedPassword
987             && user.hashedPassword != ""  # login without password
988             && match mcf user.hashedPassword == null)
989         then ''
990           The password hash of user "${user.name}" may be invalid. You must set a
991           valid hash or the user will be locked out of their account. Please
992           check the value of option `users.users."${user.name}".hashedPassword`.''
993         else null)
994         ++ flip mapAttrsToList cfg.users (name: user:
995           if user.passwordFile != null then
996             ''The option `users.users."${name}".passwordFile' has been renamed '' +
997             ''to `users.users."${name}".hashedPasswordFile'.''
998           else null)
999       );
1000   };