at master 42 kB view raw
1{ 2 config, 3 lib, 4 utils, 5 pkgs, 6 ... 7}: 8 9let 10 inherit (lib) 11 any 12 attrNames 13 attrValues 14 concatMap 15 concatMapStringsSep 16 concatStrings 17 elem 18 filter 19 filterAttrs 20 flatten 21 flip 22 foldr 23 generators 24 getAttr 25 hasAttr 26 id 27 length 28 listToAttrs 29 literalExpression 30 mapAttrs' 31 mapAttrsToList 32 match 33 mkAliasOptionModuleMD 34 mkDefault 35 mkIf 36 mkMerge 37 mkOption 38 mkRenamedOptionModule 39 optional 40 optionals 41 sort 42 stringAfter 43 stringLength 44 trace 45 types 46 xor 47 ; 48 49 ids = config.ids; 50 cfg = config.users; 51 52 # Check whether a password hash will allow login. 53 allowsLogin = 54 hash: 55 hash == "" # login without password 56 || !(lib.elem hash [ 57 null # password login disabled 58 "!" # password login disabled 59 "!!" # a variant of "!" 60 "*" # password unset 61 ]); 62 63 overrideOrderMutable = ''{option}`initialHashedPassword` -> {option}`initialPassword` -> {option}`hashedPassword` -> {option}`password` -> {option}`hashedPasswordFile`''; 64 65 overrideOrderImmutable = ''{option}`initialHashedPassword` -> {option}`hashedPassword` -> {option}`initialPassword` -> {option}`password` -> {option}`hashedPasswordFile`''; 66 67 overrideOrderText = isMutable: '' 68 If the option {option}`users.mutableUsers` is 69 `${if isMutable then "true" else "false"}`, then the order of precedence is as shown 70 below, where values on the left are overridden by values on the right: 71 ${if isMutable then overrideOrderMutable else overrideOrderImmutable} 72 ''; 73 74 multiplePasswordsWarning = '' 75 If multiple of these password options are set at the same time then a 76 specific order of precedence is followed, which can lead to surprising 77 results. The order of precedence differs depending on whether the 78 {option}`users.mutableUsers` option is set. 79 ''; 80 81 overrideDescription = '' 82 ${multiplePasswordsWarning} 83 84 ${overrideOrderText false} 85 86 ${overrideOrderText true} 87 ''; 88 89 passwordDescription = '' 90 The {option}`initialHashedPassword`, {option}`hashedPassword`, 91 {option}`initialPassword`, {option}`password` and 92 {option}`hashedPasswordFile` options all control what password is set for 93 the user. 94 95 In a system where [](#opt-systemd.sysusers.enable) is `false`, typically 96 only one of {option}`hashedPassword`, {option}`password`, or 97 {option}`hashedPasswordFile` will be set. 98 99 In a system where [](#opt-systemd.sysusers.enable) is `true`, typically 100 only one of {option}`initialPassword`, {option}`initialHashedPassword`, 101 or {option}`hashedPasswordFile` will be set. 102 103 If the option {option}`users.mutableUsers` is true, the password defined 104 in one of the above password options will only be set when the user is 105 created for the first time. After that, you are free to change the 106 password with the ordinary user management commands. If 107 {option}`users.mutableUsers` is false, you cannot change user passwords, 108 they will always be set according to the password options. 109 110 If none of the password options are set, then no password is assigned to 111 the user, and the user will not be able to do password-based logins. 112 113 ${overrideDescription} 114 ''; 115 116 hashedPasswordDescription = '' 117 To generate a hashed password run `mkpasswd`. 118 119 If set to an empty string (`""`), this user will be able to log in without 120 being asked for a password (but not via remote services such as SSH, or 121 indirectly via {command}`su` or {command}`sudo`). This should only be used 122 for e.g. bootable live systems. Note: this is different from setting an 123 empty password, which can be achieved using 124 {option}`users.users.<name?>.password`. 125 126 If set to `null` (default) this user will not be able to log in using a 127 password (i.e. via {command}`login` command). 128 ''; 129 130 userOpts = 131 { name, config, ... }: 132 { 133 134 options = { 135 136 enable = mkOption { 137 type = types.bool; 138 default = true; 139 example = false; 140 description = '' 141 If set to false, the user account will not be created. This is useful for when you wish to conditionally 142 disable user accounts. 143 ''; 144 }; 145 146 name = mkOption { 147 type = types.passwdEntry types.str; 148 apply = 149 x: 150 assert ( 151 stringLength x < 32 || abort "Username '${x}' is longer than 31 characters which is not allowed!" 152 ); 153 x; 154 description = '' 155 The name of the user account. If undefined, the name of the 156 attribute set will be used. 157 ''; 158 }; 159 160 description = mkOption { 161 type = types.passwdEntry types.str; 162 default = ""; 163 example = "Alice Q. User"; 164 description = '' 165 A short description of the user account, typically the 166 user's full name. This is actually the GECOS or comment 167 field in {file}`/etc/passwd`. 168 ''; 169 }; 170 171 uid = mkOption { 172 type = with types; nullOr int; 173 default = null; 174 description = '' 175 The account UID. If the UID is null, a free UID is picked on 176 activation. 177 ''; 178 }; 179 180 isSystemUser = mkOption { 181 type = types.bool; 182 default = false; 183 description = '' 184 Indicates if the user is a system user or not. This option 185 only has an effect if {option}`uid` is 186 {option}`null`, in which case it determines whether 187 the user's UID is allocated in the range for system users 188 (below 1000) or in the range for normal users (starting at 189 1000). 190 Exactly one of `isNormalUser` and 191 `isSystemUser` must be true. 192 ''; 193 }; 194 195 isNormalUser = mkOption { 196 type = types.bool; 197 default = false; 198 description = '' 199 Indicates whether this is an account for a real user. 200 This automatically sets {option}`group` to `users`, 201 {option}`createHome` to `true`, 202 {option}`home` to {file}`/home/«username»`, 203 {option}`useDefaultShell` to `true`, 204 and {option}`isSystemUser` to `false`. 205 Exactly one of `isNormalUser` and `isSystemUser` must be true. 206 ''; 207 }; 208 209 group = mkOption { 210 type = types.str; 211 apply = 212 x: 213 assert ( 214 stringLength x < 32 || abort "Group name '${x}' is longer than 31 characters which is not allowed!" 215 ); 216 x; 217 default = ""; 218 description = "The user's primary group."; 219 }; 220 221 extraGroups = mkOption { 222 type = types.listOf types.str; 223 default = [ ]; 224 description = "The user's auxiliary groups."; 225 }; 226 227 home = mkOption { 228 type = types.passwdEntry types.path; 229 default = "/var/empty"; 230 description = "The user's home directory."; 231 }; 232 233 homeMode = mkOption { 234 type = types.strMatching "[0-7]{1,5}"; 235 default = "700"; 236 description = "The user's home directory mode in numeric format. See {manpage}`chmod(1)`. The mode is only applied if {option}`users.users.<name>.createHome` is true."; 237 }; 238 239 cryptHomeLuks = mkOption { 240 type = with types; nullOr str; 241 default = null; 242 description = '' 243 Path to encrypted luks device that contains 244 the user's home directory. 245 ''; 246 }; 247 248 pamMount = mkOption { 249 type = with types; attrsOf str; 250 default = { }; 251 description = '' 252 Attributes for user's entry in 253 {file}`pam_mount.conf.xml`. 254 Useful attributes might include `path`, 255 `options`, `fstype`, and `server`. 256 See <https://pam-mount.sourceforge.net/pam_mount.conf.5.html> 257 for more information. 258 ''; 259 }; 260 261 shell = mkOption { 262 type = types.nullOr (types.either types.shellPackage (types.passwdEntry types.path)); 263 default = pkgs.shadow; 264 defaultText = literalExpression "pkgs.shadow"; 265 example = literalExpression "pkgs.bashInteractive"; 266 description = '' 267 The path to the user's shell. Can use shell derivations, 268 like `pkgs.bashInteractive`. Dont 269 forget to enable your shell in 270 `programs` if necessary, 271 like `programs.zsh.enable = true;`. 272 ''; 273 }; 274 275 ignoreShellProgramCheck = mkOption { 276 type = types.bool; 277 default = false; 278 description = '' 279 By default, nixos will check that programs.SHELL.enable is set to 280 true if the user has a custom shell specified. If that behavior isn't 281 required and there are custom overrides in place to make sure that the 282 shell is functional, set this to true. 283 ''; 284 }; 285 286 subUidRanges = mkOption { 287 type = with types; listOf (submodule subordinateUidRange); 288 default = [ ]; 289 example = [ 290 { 291 startUid = 1000; 292 count = 1; 293 } 294 { 295 startUid = 100001; 296 count = 65534; 297 } 298 ]; 299 description = '' 300 Subordinate user ids that user is allowed to use. 301 They are set into {file}`/etc/subuid` and are used 302 by `newuidmap` for user namespaces. 303 ''; 304 }; 305 306 subGidRanges = mkOption { 307 type = with types; listOf (submodule subordinateGidRange); 308 default = [ ]; 309 example = [ 310 { 311 startGid = 100; 312 count = 1; 313 } 314 { 315 startGid = 1001; 316 count = 999; 317 } 318 ]; 319 description = '' 320 Subordinate group ids that user is allowed to use. 321 They are set into {file}`/etc/subgid` and are used 322 by `newgidmap` for user namespaces. 323 ''; 324 }; 325 326 autoSubUidGidRange = mkOption { 327 type = types.bool; 328 default = false; 329 example = true; 330 description = '' 331 Automatically allocate subordinate user and group ids for this user. 332 Allocated range is currently always of size 65536. 333 ''; 334 }; 335 336 createHome = mkOption { 337 type = types.bool; 338 default = false; 339 description = '' 340 Whether to create the home directory and ensure ownership as well as 341 permissions to match the user. 342 ''; 343 }; 344 345 useDefaultShell = mkOption { 346 type = types.bool; 347 default = false; 348 description = '' 349 If true, the user's shell will be set to 350 {option}`users.defaultUserShell`. 351 ''; 352 }; 353 354 hashedPassword = mkOption { 355 type = with types; nullOr (passwdEntry str); 356 default = null; 357 description = '' 358 Specifies the hashed password for the user. 359 360 ${passwordDescription} 361 ${hashedPasswordDescription} 362 ''; 363 }; 364 365 password = mkOption { 366 type = with types; nullOr str; 367 default = null; 368 description = '' 369 Specifies the (clear text) password for the user. 370 Warning: do not set confidential information here 371 because it is world-readable in the Nix store. This option 372 should only be used for public accounts. 373 374 ${passwordDescription} 375 ''; 376 }; 377 378 hashedPasswordFile = mkOption { 379 type = with types; nullOr str; 380 default = cfg.users.${name}.passwordFile; 381 defaultText = literalExpression "null"; 382 description = '' 383 The full path to a file that contains the hash of the user's 384 password. The password file is read on each system activation. The 385 file should contain exactly one line, which should be the password in 386 an encrypted form that is suitable for the `chpasswd -e` command. 387 388 ${passwordDescription} 389 ''; 390 }; 391 392 passwordFile = mkOption { 393 type = with types; nullOr str; 394 default = null; 395 visible = false; 396 description = "Deprecated alias of hashedPasswordFile"; 397 }; 398 399 initialHashedPassword = mkOption { 400 type = with types; nullOr (passwdEntry str); 401 default = null; 402 description = '' 403 Specifies the initial hashed password for the user, i.e. the 404 hashed password assigned if the user does not already 405 exist. If {option}`users.mutableUsers` is true, the 406 password can be changed subsequently using the 407 {command}`passwd` command. Otherwise, it's 408 equivalent to setting the {option}`hashedPassword` option. 409 410 ${passwordDescription} 411 ${hashedPasswordDescription} 412 ''; 413 }; 414 415 initialPassword = mkOption { 416 type = with types; nullOr str; 417 default = null; 418 description = '' 419 Specifies the initial password for the user, i.e. the 420 password assigned if the user does not already exist. If 421 {option}`users.mutableUsers` is true, the password 422 can be changed subsequently using the 423 {command}`passwd` command. Otherwise, it's 424 equivalent to setting the {option}`password` 425 option. The same caveat applies: the password specified here 426 is world-readable in the Nix store, so it should only be 427 used for guest accounts or passwords that will be changed 428 promptly. 429 430 ${passwordDescription} 431 ''; 432 }; 433 434 packages = mkOption { 435 type = types.listOf types.package; 436 default = [ ]; 437 example = literalExpression "[ pkgs.firefox pkgs.thunderbird ]"; 438 description = '' 439 The set of packages that should be made available to the user. 440 This is in contrast to {option}`environment.systemPackages`, 441 which adds packages to all users. 442 ''; 443 }; 444 445 expires = mkOption { 446 type = types.nullOr (types.strMatching "[[:digit:]]{4}-[[:digit:]]{2}-[[:digit:]]{2}"); 447 default = null; 448 description = '' 449 Set the date on which the user's account will no longer be 450 accessible. The date is expressed in the format YYYY-MM-DD, or null 451 to disable the expiry. 452 A user whose account is locked must contact the system 453 administrator before being able to use the system again. 454 ''; 455 }; 456 457 linger = mkOption { 458 type = types.bool; 459 default = false; 460 description = '' 461 Whether to enable lingering for this user. If true, systemd user 462 units will start at boot, rather than starting at login and stopping 463 at logout. This is the declarative equivalent of running 464 `loginctl enable-linger` for this user. 465 466 If false, user units will not be started until the user logs in, and 467 may be stopped on logout depending on the settings in `logind.conf`. 468 ''; 469 }; 470 }; 471 472 config = mkMerge [ 473 { 474 name = mkDefault name; 475 shell = mkIf config.useDefaultShell (mkDefault cfg.defaultUserShell); 476 } 477 (mkIf config.isNormalUser { 478 group = mkDefault "users"; 479 createHome = mkDefault true; 480 home = mkDefault "${cfg.defaultUserHome}/${config.name}"; 481 homeMode = mkDefault "700"; 482 useDefaultShell = mkDefault true; 483 isSystemUser = mkDefault false; 484 }) 485 # If !mutableUsers, setting ‘initialPassword’ is equivalent to 486 # setting ‘password’ (and similarly for hashed passwords). 487 (mkIf (!cfg.mutableUsers && config.initialPassword != null) { 488 password = mkDefault config.initialPassword; 489 }) 490 (mkIf (!cfg.mutableUsers && config.initialHashedPassword != null) { 491 hashedPassword = mkDefault config.initialHashedPassword; 492 }) 493 (mkIf (config.isNormalUser && config.subUidRanges == [ ] && config.subGidRanges == [ ]) { 494 autoSubUidGidRange = mkDefault true; 495 }) 496 ]; 497 498 }; 499 500 groupOpts = 501 { name, config, ... }: 502 { 503 504 options = { 505 506 name = mkOption { 507 type = types.passwdEntry types.str; 508 description = '' 509 The name of the group. If undefined, the name of the attribute set 510 will be used. 511 ''; 512 }; 513 514 gid = mkOption { 515 type = with types; nullOr int; 516 default = null; 517 description = '' 518 The group GID. If the GID is null, a free GID is picked on 519 activation. 520 ''; 521 }; 522 523 members = mkOption { 524 type = with types; listOf (passwdEntry str); 525 default = [ ]; 526 description = '' 527 The user names of the group members, added to the 528 `/etc/group` file. 529 ''; 530 }; 531 532 }; 533 534 config = { 535 name = mkDefault name; 536 537 members = mapAttrsToList (n: u: u.name) ( 538 filterAttrs (n: u: u.enable && elem config.name u.extraGroups) cfg.users 539 ); 540 }; 541 542 }; 543 544 subordinateUidRange = { 545 options = { 546 startUid = mkOption { 547 type = types.int; 548 description = '' 549 Start of the range of subordinate user ids that user is 550 allowed to use. 551 ''; 552 }; 553 count = mkOption { 554 type = types.int; 555 default = 1; 556 description = "Count of subordinate user ids"; 557 }; 558 }; 559 }; 560 561 subordinateGidRange = { 562 options = { 563 startGid = mkOption { 564 type = types.int; 565 description = '' 566 Start of the range of subordinate group ids that user is 567 allowed to use. 568 ''; 569 }; 570 count = mkOption { 571 type = types.int; 572 default = 1; 573 description = "Count of subordinate group ids"; 574 }; 575 }; 576 }; 577 578 idsAreUnique = 579 set: idAttr: 580 !(foldr 581 ( 582 name: 583 args@{ dup, acc }: 584 let 585 id = toString (getAttr idAttr (getAttr name set)); 586 exists = hasAttr id acc; 587 newAcc = 588 acc 589 // (listToAttrs [ 590 { 591 name = id; 592 value = true; 593 } 594 ]); 595 in 596 if dup then 597 args 598 else if exists then 599 trace "Duplicate ${idAttr} ${id}" { 600 dup = true; 601 acc = null; 602 } 603 else 604 { 605 dup = false; 606 acc = newAcc; 607 } 608 ) 609 { 610 dup = false; 611 acc = { }; 612 } 613 (attrNames set) 614 ).dup; 615 616 uidsAreUnique = idsAreUnique (filterAttrs (n: u: u.uid != null) cfg.users) "uid"; 617 gidsAreUnique = idsAreUnique (filterAttrs (n: g: g.gid != null) cfg.groups) "gid"; 618 sdInitrdUidsAreUnique = idsAreUnique (filterAttrs ( 619 n: u: u.uid != null 620 ) config.boot.initrd.systemd.users) "uid"; 621 sdInitrdGidsAreUnique = idsAreUnique (filterAttrs ( 622 n: g: g.gid != null 623 ) config.boot.initrd.systemd.groups) "gid"; 624 groupNames = lib.mapAttrsToList (n: g: g.name) cfg.groups; 625 usersWithoutExistingGroup = lib.filterAttrs ( 626 n: u: u.group != "" && !lib.elem u.group groupNames 627 ) cfg.users; 628 usersWithNullShells = attrNames (filterAttrs (name: cfg: cfg.shell == null) cfg.users); 629 630 spec = pkgs.writeText "users-groups.json" ( 631 builtins.toJSON { 632 inherit (cfg) mutableUsers; 633 users = mapAttrsToList (_: u: { 634 inherit (u) 635 name 636 uid 637 group 638 description 639 home 640 homeMode 641 createHome 642 isSystemUser 643 password 644 hashedPasswordFile 645 hashedPassword 646 autoSubUidGidRange 647 subUidRanges 648 subGidRanges 649 initialPassword 650 initialHashedPassword 651 expires 652 ; 653 shell = utils.toShellPath u.shell; 654 }) (filterAttrs (_: u: u.enable) cfg.users); 655 groups = attrValues cfg.groups; 656 } 657 ); 658 659 systemShells = 660 let 661 shells = mapAttrsToList (_: u: u.shell) cfg.users; 662 in 663 filter types.shellPackage.check shells; 664 665 lingeringUsers = map (u: u.name) (attrValues (flip filterAttrs cfg.users (n: u: u.linger))); 666in 667{ 668 imports = [ 669 (mkAliasOptionModuleMD [ "users" "extraUsers" ] [ "users" "users" ]) 670 (mkAliasOptionModuleMD [ "users" "extraGroups" ] [ "users" "groups" ]) 671 (mkRenamedOptionModule 672 [ "security" "initialRootPassword" ] 673 [ "users" "users" "root" "initialHashedPassword" ] 674 ) 675 ]; 676 677 ###### interface 678 options = { 679 680 users.mutableUsers = mkOption { 681 type = types.bool; 682 default = true; 683 description = '' 684 If set to `true`, you are free to add new users and groups to the system 685 with the ordinary `useradd` and 686 `groupadd` commands. On system activation, the 687 existing contents of the `/etc/passwd` and 688 `/etc/group` files will be merged with the 689 contents generated from the `users.users` and 690 `users.groups` options. 691 The initial password for a user will be set 692 according to `users.users`, but existing passwords 693 will not be changed. 694 695 ::: {.warning} 696 If set to `false`, the contents of the user and 697 group files will simply be replaced on system activation. This also 698 holds for the user passwords; all changed 699 passwords will be reset according to the 700 `users.users` configuration on activation. 701 ::: 702 ''; 703 }; 704 705 users.enforceIdUniqueness = mkOption { 706 type = types.bool; 707 default = true; 708 description = '' 709 Whether to require that no two users/groups share the same uid/gid. 710 ''; 711 }; 712 713 users.users = mkOption { 714 default = { }; 715 type = with types; attrsOf (submodule userOpts); 716 example = { 717 alice = { 718 uid = 1234; 719 description = "Alice Q. User"; 720 home = "/home/alice"; 721 createHome = true; 722 group = "users"; 723 extraGroups = [ "wheel" ]; 724 shell = "/bin/sh"; 725 }; 726 }; 727 description = '' 728 Additional user accounts to be created automatically by the system. 729 This can also be used to set options for root. 730 ''; 731 }; 732 733 users.groups = mkOption { 734 default = { }; 735 example = { 736 students.gid = 1001; 737 hackers = { }; 738 }; 739 type = with types; attrsOf (submodule groupOpts); 740 description = '' 741 Additional groups to be created automatically by the system. 742 ''; 743 }; 744 745 users.allowNoPasswordLogin = mkOption { 746 type = types.bool; 747 default = false; 748 description = '' 749 Disable checking that at least the `root` user or a user in the `wheel` group can log in using 750 a password or an SSH key. 751 752 WARNING: enabling this can lock you out of your system. Enable this only if you know what are you doing. 753 ''; 754 }; 755 756 users.defaultUserHome = mkOption { 757 type = types.str; 758 default = "/home"; 759 description = '' 760 The default home directory for normal users. 761 ''; 762 }; 763 764 # systemd initrd 765 boot.initrd.systemd.users = mkOption { 766 description = '' 767 Users to include in initrd. 768 ''; 769 default = { }; 770 type = types.attrsOf ( 771 types.submodule ( 772 { name, ... }: 773 { 774 options.uid = mkOption { 775 type = types.int; 776 description = '' 777 ID of the user in initrd. 778 ''; 779 defaultText = literalExpression "config.users.users.\${name}.uid"; 780 default = cfg.users.${name}.uid; 781 }; 782 options.group = mkOption { 783 type = types.singleLineStr; 784 description = '' 785 Group the user belongs to in initrd. 786 ''; 787 defaultText = literalExpression "config.users.users.\${name}.group"; 788 default = cfg.users.${name}.group; 789 }; 790 options.shell = mkOption { 791 type = types.passwdEntry types.path; 792 description = '' 793 The path to the user's shell in initrd. 794 ''; 795 default = "${pkgs.shadow}/bin/nologin"; 796 defaultText = literalExpression "\${pkgs.shadow}/bin/nologin"; 797 }; 798 } 799 ) 800 ); 801 }; 802 803 boot.initrd.systemd.groups = mkOption { 804 description = '' 805 Groups to include in initrd. 806 ''; 807 default = { }; 808 type = types.attrsOf ( 809 types.submodule ( 810 { name, ... }: 811 { 812 options.gid = mkOption { 813 type = types.int; 814 description = '' 815 ID of the group in initrd. 816 ''; 817 defaultText = literalExpression "config.users.groups.\${name}.gid"; 818 default = cfg.groups.${name}.gid; 819 }; 820 } 821 ) 822 ); 823 }; 824 }; 825 826 ###### implementation 827 828 config = 829 let 830 cryptSchemeIdPatternGroup = "(${lib.concatStringsSep "|" pkgs.libxcrypt.enabledCryptSchemeIds})"; 831 in 832 { 833 834 users.users = { 835 root = { 836 uid = ids.uids.root; 837 description = "System administrator"; 838 home = "/root"; 839 shell = mkDefault cfg.defaultUserShell; 840 group = "root"; 841 }; 842 nobody = { 843 uid = ids.uids.nobody; 844 isSystemUser = true; 845 description = "Unprivileged account (don't use!)"; 846 group = "nogroup"; 847 }; 848 }; 849 850 users.groups = { 851 root.gid = ids.gids.root; 852 wheel.gid = ids.gids.wheel; 853 disk.gid = ids.gids.disk; 854 kmem.gid = ids.gids.kmem; 855 tty.gid = ids.gids.tty; 856 floppy.gid = ids.gids.floppy; 857 uucp.gid = ids.gids.uucp; 858 lp.gid = ids.gids.lp; 859 cdrom.gid = ids.gids.cdrom; 860 tape.gid = ids.gids.tape; 861 audio.gid = ids.gids.audio; 862 video.gid = ids.gids.video; 863 dialout.gid = ids.gids.dialout; 864 nogroup.gid = ids.gids.nogroup; 865 users.gid = ids.gids.users; 866 nixbld.gid = ids.gids.nixbld; 867 utmp.gid = ids.gids.utmp; 868 adm.gid = ids.gids.adm; 869 input.gid = ids.gids.input; 870 kvm.gid = ids.gids.kvm; 871 render.gid = ids.gids.render; 872 sgx.gid = ids.gids.sgx; 873 shadow.gid = ids.gids.shadow; 874 }; 875 876 system.activationScripts.users = 877 if !config.systemd.sysusers.enable then 878 { 879 supportsDryActivation = true; 880 text = '' 881 install -m 0700 -d /root 882 install -m 0755 -d /home 883 884 ${ 885 pkgs.perl.withPackages (p: [ 886 p.FileSlurp 887 p.JSON 888 ]) 889 }/bin/perl \ 890 -w ${./update-users-groups.pl} ${spec} 891 ''; 892 } 893 else 894 ""; # keep around for backwards compatibility 895 896 systemd.services.linger-users = lib.mkIf ((length lingeringUsers) > 0) { 897 wantedBy = [ "multi-user.target" ]; 898 after = [ "systemd-logind.service" ]; 899 requires = [ "systemd-logind.service" ]; 900 901 script = 902 let 903 lingerDir = "/var/lib/systemd/linger"; 904 lingeringUsersFile = builtins.toFile "lingering-users" ( 905 concatStrings (map (s: "${s}\n") (sort (a: b: a < b) lingeringUsers)) 906 ); # this sorting is important for `comm` to work correctly 907 in 908 '' 909 mkdir -vp ${lingerDir} 910 cd ${lingerDir} 911 for user in $(ls); do 912 if ! id "$user" >/dev/null; then 913 echo "Removing linger for missing user $user" 914 rm --force -- "$user" 915 fi 916 done 917 ls | sort | comm -3 -1 ${lingeringUsersFile} - | xargs -r ${pkgs.systemd}/bin/loginctl disable-linger 918 ls | sort | comm -3 -2 ${lingeringUsersFile} - | xargs -r ${pkgs.systemd}/bin/loginctl enable-linger 919 ''; 920 921 serviceConfig.Type = "oneshot"; 922 }; 923 924 # Warn about user accounts with deprecated password hashing schemes 925 # This does not work when the users and groups are created by 926 # systemd-sysusers because the users are created too late then. 927 system.activationScripts.hashes = 928 if !config.systemd.sysusers.enable then 929 { 930 deps = [ "users" ]; 931 text = '' 932 users=() 933 while IFS=: read -r user hash _; do 934 if [[ "$hash" = "$"* && ! "$hash" =~ ^\''$${cryptSchemeIdPatternGroup}\$ ]]; then 935 users+=("$user") 936 fi 937 done </etc/shadow 938 939 if (( "''${#users[@]}" )); then 940 echo " 941 WARNING: The following user accounts rely on password hashing algorithms 942 that have been removed. They need to be renewed as soon as possible, as 943 they do prevent their users from logging in." 944 printf ' - %s\n' "''${users[@]}" 945 fi 946 ''; 947 } 948 else 949 ""; # keep around for backwards compatibility 950 951 # for backwards compatibility 952 system.activationScripts.groups = stringAfter [ "users" ] ""; 953 954 # Install all the user shells 955 environment.systemPackages = systemShells; 956 957 environment.etc = mapAttrs' ( 958 _: 959 { packages, name, ... }: 960 { 961 name = "profiles/per-user/${name}"; 962 value.source = pkgs.buildEnv { 963 name = "user-environment"; 964 paths = packages; 965 inherit (config.environment) pathsToLink extraOutputsToInstall; 966 inherit (config.system.path) ignoreCollisions postBuild; 967 }; 968 } 969 ) (filterAttrs (_: u: u.packages != [ ]) cfg.users); 970 971 environment.profiles = [ 972 "$HOME/.nix-profile" 973 "\${XDG_STATE_HOME}/nix/profile" 974 "$HOME/.local/state/nix/profile" 975 "/etc/profiles/per-user/$USER" 976 ]; 977 978 # systemd initrd 979 boot.initrd.systemd = lib.mkIf config.boot.initrd.systemd.enable { 980 contents = { 981 "/etc/passwd".text = '' 982 ${lib.concatStringsSep "\n" ( 983 lib.mapAttrsToList ( 984 n: 985 { 986 uid, 987 group, 988 shell, 989 }: 990 let 991 g = config.boot.initrd.systemd.groups.${group}; 992 in 993 "${n}:x:${toString uid}:${toString g.gid}::/var/empty:${shell}" 994 ) config.boot.initrd.systemd.users 995 )} 996 ''; 997 "/etc/group".text = '' 998 ${lib.concatStringsSep "\n" ( 999 lib.mapAttrsToList (n: { gid }: "${n}:x:${toString gid}:") config.boot.initrd.systemd.groups 1000 )} 1001 ''; 1002 "/etc/shells".text = 1003 lib.concatStringsSep "\n" ( 1004 lib.unique (lib.mapAttrsToList (_: u: u.shell) config.boot.initrd.systemd.users) 1005 ) 1006 + "\n"; 1007 }; 1008 1009 storePaths = [ "${pkgs.shadow}/bin/nologin" ]; 1010 1011 users = { 1012 root = { 1013 shell = lib.mkDefault "/bin/bash"; 1014 }; 1015 nobody = { }; 1016 }; 1017 1018 groups = { 1019 root = { }; 1020 nogroup = { }; 1021 systemd-journal = { }; 1022 tty = { }; 1023 dialout = { }; 1024 kmem = { }; 1025 input = { }; 1026 video = { }; 1027 render = { }; 1028 sgx = { }; 1029 audio = { }; 1030 video = { }; 1031 lp = { }; 1032 disk = { }; 1033 cdrom = { }; 1034 tape = { }; 1035 kvm = { }; 1036 }; 1037 }; 1038 1039 assertions = [ 1040 { 1041 assertion = !cfg.enforceIdUniqueness || (uidsAreUnique && gidsAreUnique); 1042 message = "UIDs and GIDs must be unique!"; 1043 } 1044 { 1045 assertion = !cfg.enforceIdUniqueness || (sdInitrdUidsAreUnique && sdInitrdGidsAreUnique); 1046 message = "systemd initrd UIDs and GIDs must be unique!"; 1047 } 1048 { 1049 assertion = usersWithoutExistingGroup == { }; 1050 message = 1051 let 1052 errUsers = lib.attrNames usersWithoutExistingGroup; 1053 missingGroups = lib.unique (lib.mapAttrsToList (n: u: u.group) usersWithoutExistingGroup); 1054 mkConfigHint = group: "users.groups.${group} = {};"; 1055 in 1056 '' 1057 The following users have a primary group that is undefined: ${lib.concatStringsSep " " errUsers} 1058 Hint: Add this to your NixOS configuration: 1059 ${lib.concatStringsSep "\n " (map mkConfigHint missingGroups)} 1060 ''; 1061 } 1062 { 1063 assertion = !cfg.mutableUsers -> length usersWithNullShells == 0; 1064 message = '' 1065 users.mutableUsers = false has been set, 1066 but found users that have their shell set to null. 1067 If you wish to disable login, set their shell to pkgs.shadow (the default). 1068 Misconfigured users: ${lib.concatStringsSep " " usersWithNullShells} 1069 ''; 1070 } 1071 { 1072 # If mutableUsers is false, to prevent users creating a 1073 # configuration that locks them out of the system, ensure that 1074 # there is at least one "privileged" account that has a 1075 # password or an SSH authorized key. Privileged accounts are 1076 # root and users in the wheel group. 1077 # The check does not apply when users.allowNoPasswordLogin 1078 # The check does not apply when users.mutableUsers 1079 assertion = 1080 !cfg.mutableUsers 1081 -> !cfg.allowNoPasswordLogin 1082 -> any id ( 1083 mapAttrsToList ( 1084 name: cfg: 1085 (name == "root" || cfg.group == "wheel" || elem "wheel" cfg.extraGroups) 1086 && ( 1087 allowsLogin cfg.hashedPassword 1088 || cfg.password != null 1089 || cfg.hashedPasswordFile != null 1090 || cfg.openssh.authorizedKeys.keys != [ ] 1091 || cfg.openssh.authorizedKeys.keyFiles != [ ] 1092 ) 1093 ) cfg.users 1094 ++ [ 1095 config.security.googleOsLogin.enable 1096 ] 1097 ); 1098 message = '' 1099 Neither the root account nor any wheel user has a password or SSH authorized key. 1100 You must set one to prevent being locked out of your system. 1101 If you really want to be locked out of your system, set users.allowNoPasswordLogin = true; 1102 However you are most probably better off by setting users.mutableUsers = true; and 1103 manually running passwd root to set the root password. 1104 ''; 1105 } 1106 ] 1107 ++ flatten ( 1108 flip mapAttrsToList cfg.users ( 1109 name: user: 1110 [ 1111 ( 1112 let 1113 # Things fail in various ways with especially non-ascii usernames. 1114 # This regex mirrors the one from shadow's is_valid_name: 1115 # https://github.com/shadow-maint/shadow/blob/bee77ffc291dfed2a133496db465eaa55e2b0fec/lib/chkname.c#L68 1116 # though without the trailing $, because Samba 3 got its last release 1117 # over 10 years ago and is not in Nixpkgs anymore, 1118 # while later versions don't appear to require anything like that. 1119 nameRegex = "[a-zA-Z0-9_.][a-zA-Z0-9_.-]*"; 1120 in 1121 { 1122 assertion = builtins.match nameRegex user.name != null; 1123 message = "The username \"${user.name}\" is not valid, it does not match the regex \"${nameRegex}\"."; 1124 } 1125 ) 1126 { 1127 assertion = (user.hashedPassword != null) -> (match ".*:.*" user.hashedPassword == null); 1128 message = '' 1129 The password hash of user "${user.name}" contains a ":" character. 1130 This is invalid and would break the login system because the fields 1131 of /etc/shadow (file where hashes are stored) are colon-separated. 1132 Please check the value of option `users.users."${user.name}".hashedPassword`.''; 1133 } 1134 { 1135 assertion = user.isNormalUser && user.uid != null -> user.uid >= 1000; 1136 message = '' 1137 A user cannot have a users.users.${user.name}.uid set below 1000 and set users.users.${user.name}.isNormalUser. 1138 Either users.users.${user.name}.isSystemUser must be set to true instead of users.users.${user.name}.isNormalUser 1139 or users.users.${user.name}.uid must be changed to 1000 or above. 1140 ''; 1141 } 1142 { 1143 assertion = 1144 let 1145 # we do an extra check on isNormalUser here, to not trigger this assertion when isNormalUser is set and uid to < 1000 1146 isEffectivelySystemUser = 1147 user.isSystemUser || (user.uid != null && user.uid < 1000 && !user.isNormalUser); 1148 in 1149 xor isEffectivelySystemUser user.isNormalUser; 1150 message = '' 1151 Exactly one of users.users.${user.name}.isSystemUser and users.users.${user.name}.isNormalUser must be set. 1152 ''; 1153 } 1154 { 1155 assertion = user.group != ""; 1156 message = '' 1157 users.users.${user.name}.group is unset. This used to default to 1158 nogroup, but this is unsafe. For example you can create a group 1159 for this user with: 1160 users.users.${user.name}.group = "${user.name}"; 1161 users.groups.${user.name} = {}; 1162 ''; 1163 } 1164 ] 1165 ++ (map 1166 (shell: { 1167 assertion = 1168 !user.ignoreShellProgramCheck 1169 -> (user.shell == pkgs.${shell}) 1170 -> (config.programs.${shell}.enable == true); 1171 message = '' 1172 users.users.${user.name}.shell is set to ${shell}, but 1173 programs.${shell}.enable is not true. This will cause the ${shell} 1174 shell to lack the basic nix directories in its PATH and might make 1175 logging in as that user impossible. You can fix it with: 1176 programs.${shell}.enable = true; 1177 1178 If you know what you're doing and you are fine with the behavior, 1179 set users.users.${user.name}.ignoreShellProgramCheck = true; 1180 instead. 1181 ''; 1182 }) 1183 [ 1184 "fish" 1185 "xonsh" 1186 "zsh" 1187 ] 1188 ) 1189 ) 1190 ); 1191 1192 warnings = 1193 flip concatMap (attrValues cfg.users) ( 1194 user: 1195 let 1196 passwordOptions = [ 1197 "hashedPassword" 1198 "hashedPasswordFile" 1199 "password" 1200 ] 1201 ++ optionals cfg.mutableUsers [ 1202 # For immutable users, initialHashedPassword is set to hashedPassword, 1203 # so using these options would always trigger the assertion. 1204 "initialHashedPassword" 1205 "initialPassword" 1206 ]; 1207 unambiguousPasswordConfiguration = 1208 1 >= length (filter (x: x != null) (map (flip getAttr user) passwordOptions)); 1209 in 1210 optional (!unambiguousPasswordConfiguration) '' 1211 The user '${user.name}' has multiple of the options 1212 `initialHashedPassword`, `hashedPassword`, `initialPassword`, `password` 1213 & `hashedPasswordFile` set to a non-null value. 1214 1215 ${multiplePasswordsWarning} 1216 ${overrideOrderText cfg.mutableUsers} 1217 The values of these options are: 1218 ${concatMapStringsSep "\n" ( 1219 value: "* users.users.\"${user.name}\".${value}: ${generators.toPretty { } user.${value}}" 1220 ) passwordOptions} 1221 '' 1222 ) 1223 ++ filter (x: x != null) ( 1224 flip mapAttrsToList cfg.users ( 1225 _: user: 1226 # This regex matches a subset of the Modular Crypto Format (MCF)[1] 1227 # informal standard. Since this depends largely on the OS or the 1228 # specific implementation of crypt(3) we only support the (sane) 1229 # schemes implemented by glibc and BSDs. In particular the original 1230 # DES hash is excluded since, having no structure, it would validate 1231 # common mistakes like typing the plaintext password. 1232 # 1233 # [1]: https://en.wikipedia.org/wiki/Crypt_(C) 1234 let 1235 sep = "\\$"; 1236 base64 = "[a-zA-Z0-9./]+"; 1237 id = cryptSchemeIdPatternGroup; 1238 name = "[a-z0-9-]+"; 1239 value = "[a-zA-Z0-9/+.-]+"; 1240 options = "${name}(=${value})?(,${name}=${value})*"; 1241 scheme = "${id}(${sep}${options})?"; 1242 content = "${base64}${sep}${base64}(${sep}${base64})?"; 1243 mcf = "^${sep}${scheme}${sep}${content}$"; 1244 in 1245 if 1246 ( 1247 allowsLogin user.hashedPassword 1248 && user.hashedPassword != "" # login without password 1249 && match mcf user.hashedPassword == null 1250 ) 1251 then 1252 '' 1253 The password hash of user "${user.name}" may be invalid. You must set a 1254 valid hash or the user will be locked out of their account. Please 1255 check the value of option `users.users."${user.name}".hashedPassword`.'' 1256 else 1257 null 1258 ) 1259 ++ flip mapAttrsToList cfg.users ( 1260 name: user: 1261 if user.passwordFile != null then 1262 ''The option `users.users."${name}".passwordFile' has been renamed '' 1263 + ''to `users.users."${name}".hashedPasswordFile'.'' 1264 else 1265 null 1266 ) 1267 ); 1268 }; 1269 1270}