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