at 24.11-pre 34 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 = '' 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 = '' 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 = '' 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 = '' 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 = '' 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 = "The user's primary group."; 119 }; 120 121 extraGroups = mkOption { 122 type = types.listOf types.str; 123 default = []; 124 description = "The user's auxiliary groups."; 125 }; 126 127 home = mkOption { 128 type = types.passwdEntry types.path; 129 default = "/var/empty"; 130 description = "The user's home directory."; 131 }; 132 133 homeMode = mkOption { 134 type = types.strMatching "[0-7]{1,5}"; 135 default = "700"; 136 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."; 137 }; 138 139 cryptHomeLuks = mkOption { 140 type = with types; nullOr str; 141 default = null; 142 description = '' 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 = '' 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 = '' 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 = '' 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 = '' 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 = '' 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 = '' 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 = '' 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 = '' 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 = '' 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 = '' 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 = '' 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 = "Deprecated alias of hashedPasswordFile"; 282 }; 283 284 initialHashedPassword = mkOption { 285 type = with types; nullOr (passwdEntry str); 286 default = null; 287 description = '' 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 = '' 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 = '' 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 = '' 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 = '' 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 = '' 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 = '' 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 = '' 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 = '' 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 = "Count of subordinate user ids"; 442 }; 443 }; 444 }; 445 446 subordinateGidRange = { 447 options = { 448 startGid = mkOption { 449 type = types.int; 450 description = '' 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 = "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: u.group != "" && !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 499 lingeringUsers = map (u: u.name) (attrValues (flip filterAttrs cfg.users (n: u: u.linger))); 500in { 501 imports = [ 502 (mkAliasOptionModuleMD [ "users" "extraUsers" ] [ "users" "users" ]) 503 (mkAliasOptionModuleMD [ "users" "extraGroups" ] [ "users" "groups" ]) 504 (mkRenamedOptionModule ["security" "initialRootPassword"] ["users" "users" "root" "initialHashedPassword"]) 505 ]; 506 507 ###### interface 508 options = { 509 510 users.mutableUsers = mkOption { 511 type = types.bool; 512 default = true; 513 description = '' 514 If set to `true`, you are free to add new users and groups to the system 515 with the ordinary `useradd` and 516 `groupadd` commands. On system activation, the 517 existing contents of the `/etc/passwd` and 518 `/etc/group` files will be merged with the 519 contents generated from the `users.users` and 520 `users.groups` options. 521 The initial password for a user will be set 522 according to `users.users`, but existing passwords 523 will not be changed. 524 525 ::: {.warning} 526 If set to `false`, the contents of the user and 527 group files will simply be replaced on system activation. This also 528 holds for the user passwords; all changed 529 passwords will be reset according to the 530 `users.users` configuration on activation. 531 ::: 532 ''; 533 }; 534 535 users.enforceIdUniqueness = mkOption { 536 type = types.bool; 537 default = true; 538 description = '' 539 Whether to require that no two users/groups share the same uid/gid. 540 ''; 541 }; 542 543 users.users = mkOption { 544 default = {}; 545 type = with types; attrsOf (submodule userOpts); 546 example = { 547 alice = { 548 uid = 1234; 549 description = "Alice Q. User"; 550 home = "/home/alice"; 551 createHome = true; 552 group = "users"; 553 extraGroups = ["wheel"]; 554 shell = "/bin/sh"; 555 }; 556 }; 557 description = '' 558 Additional user accounts to be created automatically by the system. 559 This can also be used to set options for root. 560 ''; 561 }; 562 563 users.groups = mkOption { 564 default = {}; 565 example = 566 { students.gid = 1001; 567 hackers = { }; 568 }; 569 type = with types; attrsOf (submodule groupOpts); 570 description = '' 571 Additional groups to be created automatically by the system. 572 ''; 573 }; 574 575 576 users.allowNoPasswordLogin = mkOption { 577 type = types.bool; 578 default = false; 579 description = '' 580 Disable checking that at least the `root` user or a user in the `wheel` group can log in using 581 a password or an SSH key. 582 583 WARNING: enabling this can lock you out of your system. Enable this only if you know what are you doing. 584 ''; 585 }; 586 587 # systemd initrd 588 boot.initrd.systemd.users = mkOption { 589 description = '' 590 Users to include in initrd. 591 ''; 592 default = {}; 593 type = types.attrsOf (types.submodule ({ name, ... }: { 594 options.uid = mkOption { 595 type = types.int; 596 description = '' 597 ID of the user in initrd. 598 ''; 599 defaultText = literalExpression "config.users.users.\${name}.uid"; 600 default = cfg.users.${name}.uid; 601 }; 602 options.group = mkOption { 603 type = types.singleLineStr; 604 description = '' 605 Group the user belongs to in initrd. 606 ''; 607 defaultText = literalExpression "config.users.users.\${name}.group"; 608 default = cfg.users.${name}.group; 609 }; 610 options.shell = mkOption { 611 type = types.passwdEntry types.path; 612 description = '' 613 The path to the user's shell in initrd. 614 ''; 615 default = "${pkgs.shadow}/bin/nologin"; 616 defaultText = literalExpression "\${pkgs.shadow}/bin/nologin"; 617 }; 618 })); 619 }; 620 621 boot.initrd.systemd.groups = mkOption { 622 description = '' 623 Groups to include in initrd. 624 ''; 625 default = {}; 626 type = types.attrsOf (types.submodule ({ name, ... }: { 627 options.gid = mkOption { 628 type = types.int; 629 description = '' 630 ID of the group in initrd. 631 ''; 632 defaultText = literalExpression "config.users.groups.\${name}.gid"; 633 default = cfg.groups.${name}.gid; 634 }; 635 })); 636 }; 637 }; 638 639 640 ###### implementation 641 642 config = let 643 cryptSchemeIdPatternGroup = "(${lib.concatStringsSep "|" pkgs.libxcrypt.enabledCryptSchemeIds})"; 644 in { 645 646 users.users = { 647 root = { 648 uid = ids.uids.root; 649 description = "System administrator"; 650 home = "/root"; 651 shell = mkDefault cfg.defaultUserShell; 652 group = "root"; 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 = if !config.systemd.sysusers.enable then { 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 } else ""; # keep around for backwards compatibility 698 699 systemd.services.linger-users = lib.mkIf ((builtins.length lingeringUsers) > 0) { 700 wantedBy = ["multi-user.target"]; 701 after = ["systemd-logind.service"]; 702 requires = ["systemd-logind.service"]; 703 704 script = let 705 lingerDir = "/var/lib/systemd/linger"; 706 lingeringUsersFile = builtins.toFile "lingering-users" 707 (concatStrings (map (s: "${s}\n") 708 (sort (a: b: a < b) lingeringUsers))); # this sorting is important for `comm` to work correctly 709 in '' 710 mkdir -vp ${lingerDir} 711 cd ${lingerDir} 712 for user in $(ls); do 713 if ! id "$user" >/dev/null; then 714 echo "Removing linger for missing user $user" 715 rm --force -- "$user" 716 fi 717 done 718 ls | sort | comm -3 -1 ${lingeringUsersFile} - | xargs -r ${pkgs.systemd}/bin/loginctl disable-linger 719 ls | sort | comm -3 -2 ${lingeringUsersFile} - | xargs -r ${pkgs.systemd}/bin/loginctl enable-linger 720 ''; 721 722 serviceConfig.Type = "oneshot"; 723 }; 724 725 # Warn about user accounts with deprecated password hashing schemes 726 # This does not work when the users and groups are created by 727 # systemd-sysusers because the users are created too late then. 728 system.activationScripts.hashes = if !config.systemd.sysusers.enable then { 729 deps = [ "users" ]; 730 text = '' 731 users=() 732 while IFS=: read -r user hash _; do 733 if [[ "$hash" = "$"* && ! "$hash" =~ ^\''$${cryptSchemeIdPatternGroup}\$ ]]; then 734 users+=("$user") 735 fi 736 done </etc/shadow 737 738 if (( "''${#users[@]}" )); then 739 echo " 740 WARNING: The following user accounts rely on password hashing algorithms 741 that have been removed. They need to be renewed as soon as possible, as 742 they do prevent their users from logging in." 743 printf ' - %s\n' "''${users[@]}" 744 fi 745 ''; 746 } else ""; # keep around for backwards compatibility 747 748 # for backwards compatibility 749 system.activationScripts.groups = stringAfter [ "users" ] ""; 750 751 # Install all the user shells 752 environment.systemPackages = systemShells; 753 754 environment.etc = mapAttrs' (_: { packages, name, ... }: { 755 name = "profiles/per-user/${name}"; 756 value.source = pkgs.buildEnv { 757 name = "user-environment"; 758 paths = packages; 759 inherit (config.environment) pathsToLink extraOutputsToInstall; 760 inherit (config.system.path) ignoreCollisions postBuild; 761 }; 762 }) (filterAttrs (_: u: u.packages != []) cfg.users); 763 764 environment.profiles = [ 765 "$HOME/.nix-profile" 766 "\${XDG_STATE_HOME}/nix/profile" 767 "$HOME/.local/state/nix/profile" 768 "/etc/profiles/per-user/$USER" 769 ]; 770 771 # systemd initrd 772 boot.initrd.systemd = lib.mkIf config.boot.initrd.systemd.enable { 773 contents = { 774 "/etc/passwd".text = '' 775 ${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: { uid, group, shell }: let 776 g = config.boot.initrd.systemd.groups.${group}; 777 in "${n}:x:${toString uid}:${toString g.gid}::/var/empty:${shell}") config.boot.initrd.systemd.users)} 778 ''; 779 "/etc/group".text = '' 780 ${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: { gid }: "${n}:x:${toString gid}:") config.boot.initrd.systemd.groups)} 781 ''; 782 "/etc/shells".text = lib.concatStringsSep "\n" (lib.unique (lib.mapAttrsToList (_: u: u.shell) config.boot.initrd.systemd.users)) + "\n"; 783 }; 784 785 storePaths = [ "${pkgs.shadow}/bin/nologin" ]; 786 787 users = { 788 root = { shell = lib.mkDefault "/bin/bash"; }; 789 nobody = {}; 790 }; 791 792 groups = { 793 root = {}; 794 nogroup = {}; 795 systemd-journal = {}; 796 tty = {}; 797 dialout = {}; 798 kmem = {}; 799 input = {}; 800 video = {}; 801 render = {}; 802 sgx = {}; 803 audio = {}; 804 video = {}; 805 lp = {}; 806 disk = {}; 807 cdrom = {}; 808 tape = {}; 809 kvm = {}; 810 }; 811 }; 812 813 assertions = [ 814 { assertion = !cfg.enforceIdUniqueness || (uidsAreUnique && gidsAreUnique); 815 message = "UIDs and GIDs must be unique!"; 816 } 817 { assertion = !cfg.enforceIdUniqueness || (sdInitrdUidsAreUnique && sdInitrdGidsAreUnique); 818 message = "systemd initrd UIDs and GIDs must be unique!"; 819 } 820 { assertion = usersWithoutExistingGroup == {}; 821 message = 822 let 823 errUsers = lib.attrNames usersWithoutExistingGroup; 824 missingGroups = lib.unique (lib.mapAttrsToList (n: u: u.group) usersWithoutExistingGroup); 825 mkConfigHint = group: "users.groups.${group} = {};"; 826 in '' 827 The following users have a primary group that is undefined: ${lib.concatStringsSep " " errUsers} 828 Hint: Add this to your NixOS configuration: 829 ${lib.concatStringsSep "\n " (map mkConfigHint missingGroups)} 830 ''; 831 } 832 { # If mutableUsers is false, to prevent users creating a 833 # configuration that locks them out of the system, ensure that 834 # there is at least one "privileged" account that has a 835 # password or an SSH authorized key. Privileged accounts are 836 # root and users in the wheel group. 837 # The check does not apply when users.disableLoginPossibilityAssertion 838 # The check does not apply when users.mutableUsers 839 assertion = !cfg.mutableUsers -> !cfg.allowNoPasswordLogin -> 840 any id (mapAttrsToList (name: cfg: 841 (name == "root" 842 || cfg.group == "wheel" 843 || elem "wheel" cfg.extraGroups) 844 && 845 (allowsLogin cfg.hashedPassword 846 || cfg.password != null 847 || cfg.hashedPasswordFile != null 848 || cfg.openssh.authorizedKeys.keys != [] 849 || cfg.openssh.authorizedKeys.keyFiles != []) 850 ) cfg.users ++ [ 851 config.security.googleOsLogin.enable 852 ]); 853 message = '' 854 Neither the root account nor any wheel user has a password or SSH authorized key. 855 You must set one to prevent being locked out of your system. 856 If you really want to be locked out of your system, set users.allowNoPasswordLogin = true; 857 However you are most probably better off by setting users.mutableUsers = true; and 858 manually running passwd root to set the root password. 859 ''; 860 } 861 ] ++ flatten (flip mapAttrsToList cfg.users (name: user: 862 [ 863 { 864 assertion = (user.hashedPassword != null) 865 -> (builtins.match ".*:.*" user.hashedPassword == null); 866 message = '' 867 The password hash of user "${user.name}" contains a ":" character. 868 This is invalid and would break the login system because the fields 869 of /etc/shadow (file where hashes are stored) are colon-separated. 870 Please check the value of option `users.users."${user.name}".hashedPassword`.''; 871 } 872 { 873 assertion = let 874 isEffectivelySystemUser = user.isSystemUser || (user.uid != null && user.uid < 1000); 875 in xor isEffectivelySystemUser user.isNormalUser; 876 message = '' 877 Exactly one of users.users.${user.name}.isSystemUser and users.users.${user.name}.isNormalUser must be set. 878 ''; 879 } 880 { 881 assertion = user.group != ""; 882 message = '' 883 users.users.${user.name}.group is unset. This used to default to 884 nogroup, but this is unsafe. For example you can create a group 885 for this user with: 886 users.users.${user.name}.group = "${user.name}"; 887 users.groups.${user.name} = {}; 888 ''; 889 } 890 ] ++ (map (shell: { 891 assertion = !user.ignoreShellProgramCheck -> (user.shell == pkgs.${shell}) -> (config.programs.${shell}.enable == true); 892 message = '' 893 users.users.${user.name}.shell is set to ${shell}, but 894 programs.${shell}.enable is not true. This will cause the ${shell} 895 shell to lack the basic nix directories in its PATH and might make 896 logging in as that user impossible. You can fix it with: 897 programs.${shell}.enable = true; 898 899 If you know what you're doing and you are fine with the behavior, 900 set users.users.${user.name}.ignoreShellProgramCheck = true; 901 instead. 902 ''; 903 }) [ 904 "fish" 905 "xonsh" 906 "zsh" 907 ]) 908 )); 909 910 warnings = 911 flip concatMap (attrValues cfg.users) (user: let 912 unambiguousPasswordConfiguration = 1 >= length (filter (x: x != null) ([ 913 user.hashedPassword 914 user.hashedPasswordFile 915 user.password 916 ] ++ optionals cfg.mutableUsers [ 917 # For immutable users, initialHashedPassword is set to hashedPassword, 918 # so using these options would always trigger the assertion. 919 user.initialHashedPassword 920 user.initialPassword 921 ])); 922 in optional (!unambiguousPasswordConfiguration) '' 923 The user '${user.name}' has multiple of the options 924 `hashedPassword`, `password`, `hashedPasswordFile`, `initialPassword` 925 & `initialHashedPassword` set to a non-null value. 926 The options silently discard others by the order of precedence 927 given above which can lead to surprising results. To resolve this warning, 928 set at most one of the options above to a non-`null` value. 929 '') 930 ++ builtins.filter (x: x != null) ( 931 flip mapAttrsToList cfg.users (_: user: 932 # This regex matches a subset of the Modular Crypto Format (MCF)[1] 933 # informal standard. Since this depends largely on the OS or the 934 # specific implementation of crypt(3) we only support the (sane) 935 # schemes implemented by glibc and BSDs. In particular the original 936 # DES hash is excluded since, having no structure, it would validate 937 # common mistakes like typing the plaintext password. 938 # 939 # [1]: https://en.wikipedia.org/wiki/Crypt_(C) 940 let 941 sep = "\\$"; 942 base64 = "[a-zA-Z0-9./]+"; 943 id = cryptSchemeIdPatternGroup; 944 name = "[a-z0-9-]+"; 945 value = "[a-zA-Z0-9/+.-]+"; 946 options = "${name}(=${value})?(,${name}=${value})*"; 947 scheme = "${id}(${sep}${options})?"; 948 content = "${base64}${sep}${base64}(${sep}${base64})?"; 949 mcf = "^${sep}${scheme}${sep}${content}$"; 950 in 951 if (allowsLogin user.hashedPassword 952 && user.hashedPassword != "" # login without password 953 && builtins.match mcf user.hashedPassword == null) 954 then '' 955 The password hash of user "${user.name}" may be invalid. You must set a 956 valid hash or the user will be locked out of their account. Please 957 check the value of option `users.users."${user.name}".hashedPassword`.'' 958 else null) 959 ++ flip mapAttrsToList cfg.users (name: user: 960 if user.passwordFile != null then 961 ''The option `users.users."${name}".passwordFile' has been renamed '' + 962 ''to `users.users."${name}".hashedPasswordFile'.'' 963 else null) 964 ); 965 }; 966 967}