lol

nixos/tests: Add two new tests for password option override ordering

This commit adds two new tests to show that the ordering of password
overrides documentation in nixos/modules/config/user-groups.nix is
correct. The override behavior differs depending on whether a system
has systemd-sysusers enabled, so there are two tests.

+250
+2
nixos/tests/all-tests.nix
··· 770 770 pantheon = handleTest ./pantheon.nix {}; 771 771 paperless = handleTest ./paperless.nix {}; 772 772 parsedmarc = handleTest ./parsedmarc {}; 773 + password-option-override-ordering = handleTest ./password-option-override-ordering.nix {}; 773 774 pdns-recursor = handleTest ./pdns-recursor.nix {}; 774 775 peerflix = handleTest ./peerflix.nix {}; 775 776 peering-manager = handleTest ./web-apps/peering-manager.nix {}; ··· 1013 1014 systemd-sysupdate = runTest ./systemd-sysupdate.nix; 1014 1015 systemd-sysusers-mutable = runTest ./systemd-sysusers-mutable.nix; 1015 1016 systemd-sysusers-immutable = runTest ./systemd-sysusers-immutable.nix; 1017 + systemd-sysusers-password-option-override-ordering = runTest ./systemd-sysusers-password-option-override-ordering.nix; 1016 1018 systemd-timesyncd = handleTest ./systemd-timesyncd.nix {}; 1017 1019 systemd-timesyncd-nscd-dnssec = handleTest ./systemd-timesyncd-nscd-dnssec.nix {}; 1018 1020 systemd-user-linger = handleTest ./systemd-user-linger.nix {};
+171
nixos/tests/password-option-override-ordering.nix
··· 1 + let 2 + password1 = "foobar"; 3 + password2 = "helloworld"; 4 + hashed_bcrypt = "$2b$05$8xIEflrk2RxQtcVXbGIxs.Vl0x7dF1/JSv3cyX6JJt0npzkTCWvxK"; # fnord 5 + hashed_yeshash = "$y$j9T$d8Z4EAf8P1SvM/aDFbxMS0$VnTXMp/Hnc7QdCBEaLTq5ZFOAFo2/PM0/xEAFuOE88."; # fnord 6 + hashed_sha512crypt = "$6$ymzs8WINZ5wGwQcV$VC2S0cQiX8NVukOLymysTPn4v1zJoJp3NGyhnqyv/dAf4NWZsBWYveQcj6gEJr4ZUjRBRjM0Pj1L8TCQ8hUUp0"; # meow 7 + in 8 + 9 + import ./make-test-python.nix ( 10 + { pkgs, ... }: 11 + { 12 + name = "password-option-override-ordering"; 13 + meta = with pkgs.lib.maintainers; { 14 + maintainers = [ fidgetingbits ]; 15 + }; 16 + 17 + nodes = 18 + let 19 + # The following users are expected to have the same behavior between immutable and mutable systems 20 + # NOTE: Below given A -> B it implies B overrides A . Each entry below builds off the next 21 + users = { 22 + # mutable true/false: initialHashedPassword -> hashedPassword 23 + fran = { 24 + isNormalUser = true; 25 + initialHashedPassword = hashed_yeshash; 26 + hashedPassword = hashed_sha512crypt; 27 + }; 28 + 29 + # mutable false: initialHashedPassword -> hashedPassword -> initialPassword 30 + # mutable true: initialHashedPassword -> initialPassword -> hashedPassword 31 + greg = { 32 + isNormalUser = true; 33 + hashedPassword = hashed_sha512crypt; 34 + initialPassword = password1; 35 + }; 36 + 37 + # mutable false: initialHashedPassword -> hashedPassword -> initialPassword -> password 38 + # mutable true: initialHashedPassword -> initialPassword -> hashedPassword -> password 39 + egon = { 40 + isNormalUser = true; 41 + initialPassword = password2; 42 + password = password1; 43 + }; 44 + 45 + # mutable true/false: hashedPassword -> password 46 + # NOTE: minor duplication of test above, but to verify no initialXXX use is consistent 47 + alice = { 48 + isNormalUser = true; 49 + hashedPassword = hashed_sha512crypt; 50 + password = password1; 51 + }; 52 + 53 + # mutable false: initialHashedPassword -> hashedPassword -> initialPassword -> password -> hashedPasswordFile 54 + # mutable true: initialHashedPassword -> initialPassword -> hashedPassword -> password -> hashedPasswordFile 55 + bob = { 56 + isNormalUser = true; 57 + hashedPassword = hashed_sha512crypt; 58 + password = password1; 59 + hashedPasswordFile = (pkgs.writeText "hashed_bcrypt" hashed_bcrypt).outPath; # Expect override of everything above 60 + }; 61 + 62 + # Show hashedPassword -> password -> hashedPasswordFile -> initialPassword is false 63 + # to explicitly show the following lib.trace warning in users-groups.nix (which was 64 + # the wording prior to PR 310484) is in fact wrong: 65 + # ``` 66 + # The user 'root' has multiple of the options 67 + # `hashedPassword`, `password`, `hashedPasswordFile`, `initialPassword` 68 + # & `initialHashedPassword` set to a non-null value. 69 + # The options silently discard others by the order of precedence 70 + # given above which can lead to surprising results. To resolve this warning, 71 + # set at most one of the options above to a non-`null` value. 72 + # ``` 73 + cat = { 74 + isNormalUser = true; 75 + hashedPassword = hashed_sha512crypt; 76 + password = password1; 77 + hashedPasswordFile = (pkgs.writeText "hashed_bcrypt" hashed_bcrypt).outPath; 78 + initialPassword = password2; # lib.trace message implies this overrides everything above 79 + }; 80 + 81 + # Show hashedPassword -> password -> hashedPasswordFile -> initialHashedPassword is false 82 + # to also explicitly show the lib.trace explained above (see cat user) is wrong 83 + dan = { 84 + isNormalUser = true; 85 + hashedPassword = hashed_sha512crypt; 86 + initialPassword = password2; 87 + password = password1; 88 + hashedPasswordFile = (pkgs.writeText "hashed_bcrypt" hashed_bcrypt).outPath; 89 + initialHashedPassword = hashed_yeshash; # lib.trace message implies this overrides everything above 90 + }; 91 + }; 92 + 93 + mkTestMachine = mutable: { 94 + environment.systemPackages = [ pkgs.shadow ]; 95 + users = { 96 + mutableUsers = mutable; 97 + inherit users; 98 + }; 99 + }; 100 + in 101 + { 102 + immutable = mkTestMachine false; 103 + mutable = mkTestMachine true; 104 + }; 105 + 106 + testScript = '' 107 + import crypt 108 + 109 + def assert_password_match(machine, username, password): 110 + shadow_entry = machine.succeed(f"getent shadow {username}") 111 + print(shadow_entry) 112 + hash = shadow_entry.split(":")[1] 113 + seed = "$".join(hash.split("$")[:-1]) 114 + assert crypt.crypt(password, seed) == hash, f"{username} user password does not match" 115 + 116 + with subtest("alice user has correct password"): 117 + for machine in machines: 118 + assert_password_match(machine, "alice", "${password1}") 119 + assert "${hashed_sha512crypt}" not in machine.succeed("getent shadow alice"), f"{machine}: alice user password is not correct" 120 + 121 + with subtest("bob user has correct password"): 122 + for machine in machines: 123 + print(machine.succeed("getent shadow bob")) 124 + assert "${hashed_bcrypt}" in machine.succeed("getent shadow bob"), f"{machine}: bob user password is not correct" 125 + 126 + with subtest("cat user has correct password"): 127 + for machine in machines: 128 + print(machine.succeed("getent shadow cat")) 129 + assert "${hashed_bcrypt}" in machine.succeed("getent shadow cat"), f"{machine}: cat user password is not correct" 130 + 131 + with subtest("dan user has correct password"): 132 + for machine in machines: 133 + print(machine.succeed("getent shadow dan")) 134 + assert "${hashed_bcrypt}" in machine.succeed("getent shadow dan"), f"{machine}: dan user password is not correct" 135 + 136 + with subtest("greg user has correct password"): 137 + print(mutable.succeed("getent shadow greg")) 138 + assert "${hashed_sha512crypt}" in mutable.succeed("getent shadow greg"), "greg user password is not correct" 139 + 140 + assert_password_match(immutable, "greg", "${password1}") 141 + assert "${hashed_sha512crypt}" not in immutable.succeed("getent shadow greg"), "greg user password is not correct" 142 + 143 + for machine in machines: 144 + machine.wait_for_unit("multi-user.target") 145 + machine.wait_until_succeeds("pgrep -f 'agetty.*tty1'") 146 + 147 + def check_login(machine: Machine, tty_number: str, username: str, password: str): 148 + machine.send_key(f"alt-f{tty_number}") 149 + machine.wait_until_succeeds(f"[ $(fgconsole) = {tty_number} ]") 150 + machine.wait_for_unit(f"getty@tty{tty_number}.service") 151 + machine.wait_until_succeeds(f"pgrep -f 'agetty.*tty{tty_number}'") 152 + machine.wait_until_tty_matches(tty_number, "login: ") 153 + machine.send_chars(f"{username}\n") 154 + machine.wait_until_tty_matches(tty_number, f"login: {username}") 155 + machine.wait_until_succeeds("pgrep login") 156 + machine.wait_until_tty_matches(tty_number, "Password: ") 157 + machine.send_chars(f"{password}\n") 158 + machine.send_chars(f"whoami > /tmp/{tty_number}\n") 159 + machine.wait_for_file(f"/tmp/{tty_number}") 160 + assert username in machine.succeed(f"cat /tmp/{tty_number}"), f"{machine}: {username} password is not correct" 161 + 162 + with subtest("Test initialPassword override"): 163 + for machine in machines: 164 + check_login(machine, "2", "egon", "${password1}") 165 + 166 + with subtest("Test initialHashedPassword override"): 167 + for machine in machines: 168 + check_login(machine, "3", "fran", "meow") 169 + ''; 170 + } 171 + )
+77
nixos/tests/systemd-sysusers-password-option-override-ordering.nix
··· 1 + { 2 + lib, 3 + pkgs ? import ../.., 4 + ... 5 + }: 6 + let 7 + password = "test"; 8 + password1 = "test1"; 9 + hashedPassword = "$y$j9T$wLgKY231.8j.ciV2MfEXe1$P0k5j3bCwHgnwW0Ive3w4knrgpiA4TzhCYLAnHvDZ51"; # test 10 + hashedPassword1 = "$y$j9T$s8TyQJtNImvobhGM5Nlez0$3E8/O8EVGuA4sr1OQmrzi8GrRcy/AEhj454JjAn72A2"; # test 11 + hashed_sha512crypt = "$6$ymzs8WINZ5wGwQcV$VC2S0cQiX8NVukOLymysTPn4v1zJoJp3NGyhnqyv/dAf4NWZsBWYveQcj6gEJr4ZUjRBRjM0Pj1L8TCQ8hUUp0"; # meow 12 + 13 + hashedPasswordFile = pkgs.writeText "hashed-password" hashedPassword1; 14 + in 15 + { 16 + name = "systemd-sysusers-password-option-override-ordering"; 17 + 18 + meta.maintainers = with lib.maintainers; [ fidgetingbits ]; 19 + 20 + nodes.machine = { 21 + systemd.sysusers.enable = true; 22 + system.etc.overlay.enable = true; 23 + boot.initrd.systemd.enable = true; 24 + 25 + users.mutableUsers = true; 26 + 27 + # NOTE: Below given A -> B it implies B overrides A . Each entry below builds off the next 28 + 29 + users.users.root = { 30 + hashedPasswordFile = lib.mkForce null; 31 + initialHashedPassword = password; 32 + }; 33 + 34 + users.groups.test = { }; 35 + 36 + # initialPassword -> initialHashedPassword 37 + users.users.alice = { 38 + isSystemUser = true; 39 + group = "test"; 40 + initialPassword = password; 41 + initialHashedPassword = hashedPassword; 42 + }; 43 + 44 + # initialPassword -> initialHashedPassword -> hashedPasswordFile 45 + users.users.bob = { 46 + isSystemUser = true; 47 + group = "test"; 48 + initialPassword = password; 49 + initialHashedPassword = hashedPassword; 50 + hashedPasswordFile = hashedPasswordFile.outPath; 51 + }; 52 + }; 53 + 54 + testScript = '' 55 + machine.wait_for_unit("systemd-sysusers.service") 56 + 57 + with subtest("systemd-sysusers.service contains the credentials"): 58 + sysusers_service = machine.succeed("systemctl cat systemd-sysusers.service") 59 + print(sysusers_service) 60 + assert "SetCredential=passwd.plaintext-password.alice:${password}" in sysusers_service 61 + 62 + with subtest("Correct mode on the password files"): 63 + assert machine.succeed("stat -c '%a' /etc/passwd") == "644\n" 64 + assert machine.succeed("stat -c '%a' /etc/group") == "644\n" 65 + assert machine.succeed("stat -c '%a' /etc/shadow") == "0\n" 66 + assert machine.succeed("stat -c '%a' /etc/gshadow") == "0\n" 67 + 68 + with subtest("alice user has correct password"): 69 + print(machine.succeed("getent shadow alice")) 70 + assert "${hashedPassword}" in machine.succeed("getent shadow alice"), "alice user password is not correct" 71 + 72 + with subtest("bob user has new password after switching to new generation"): 73 + print(machine.succeed("getent passwd bob")) 74 + print(machine.succeed("getent shadow bob")) 75 + assert "${hashedPassword1}" in machine.succeed("getent shadow bob"), "bob user password is not correct" 76 + ''; 77 + }