Skip to content

Commit

Permalink
Correct password option docs and add related tests
Browse files Browse the repository at this point in the history
  • Loading branch information
fidgetingbits committed Nov 14, 2024
1 parent ae8c3f2 commit b572dd2
Show file tree
Hide file tree
Showing 4 changed files with 290 additions and 7 deletions.
24 changes: 17 additions & 7 deletions nixos/modules/config/users-groups.nix
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ let
The options {option}`hashedPassword`,
{option}`password` and {option}`hashedPasswordFile`
controls what password is set for the user.
{option}`hashedPassword` overrides both
{option}`password` and {option}`hashedPasswordFile`.
{option}`password` overrides {option}`hashedPasswordFile`.
{option}`hashedPasswordFile` overrides both
{option}`password` and {option}`hashedPassword`.
{option}`hashedPassword` overrides {option}`password`.
If none of these three options are set, no password is assigned to
the user, and the user will not be able to do password logins.
If the option {option}`users.mutableUsers` is true, the
Expand All @@ -69,6 +69,10 @@ let
{option}`users.mutableUsers` is false, you cannot change
user passwords, they will always be set according to the password
options.
NOTE: one exception is if [](#opt-systemd.sysusers.enable) is true. In this case
one of `initialPassword`, `initialHashedPassword`, or `hashedPasswordFile`
must be set. The order of overriding is the order of three listed above.
'';

hashedPasswordDescription = ''
Expand Down Expand Up @@ -330,7 +334,8 @@ let
equivalent to setting the {option}`hashedPassword` option.
Note that the {option}`hashedPassword` option will override
this option if both are set.
this option if both are set. One exception to this rule is if
[](#opt-systemd.sysusers.enable) is true.
${hashedPasswordDescription}
'';
Expand All @@ -352,7 +357,8 @@ let
promptly.
Note that the {option}`password` option will override this
option if both are set.
option if both are set. One exception to this rule is if
[](#opt-systemd.sysusers.enable) is true.
'';
};

Expand Down Expand Up @@ -960,12 +966,16 @@ in {
(filter (x: x != null) (map (flip getAttr user) passwordOptions));
in optional (!unambiguousPasswordConfiguration) ''
The user '${user.name}' has multiple of the options
`hashedPassword`, `password`, `hashedPasswordFile`, `initialPassword`
& `initialHashedPassword` set to a non-null value.
`initialHashedPassword`, `hashedPassword`, `initialPassword`, `password`
& `hashedPasswordFile` set to a non-null value.
The options silently discard others by the order of precedence
given above which can lead to surprising results. To resolve this warning,
set at most one of the options above to a non-`null` value.
NOTE: one exception is if [](#opt-systemd.sysusers.enable) is true. In this case
one of `initialPassword`, `initialHashedPassword`, or `hashedPasswordFile`
must be set. The order of overriding is the order of three listed above.
The values of these options are:
${concatMapStringsSep
"\n"
Expand Down
2 changes: 2 additions & 0 deletions nixos/tests/all-tests.nix
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,7 @@ in {
pantheon = handleTest ./pantheon.nix {};
paperless = handleTest ./paperless.nix {};
parsedmarc = handleTest ./parsedmarc {};
password-option-override-ordering = handleTest ./password-option-override-ordering.nix {};
pdns-recursor = handleTest ./pdns-recursor.nix {};
peerflix = handleTest ./peerflix.nix {};
peering-manager = handleTest ./web-apps/peering-manager.nix {};
Expand Down Expand Up @@ -1013,6 +1014,7 @@ in {
systemd-sysupdate = runTest ./systemd-sysupdate.nix;
systemd-sysusers-mutable = runTest ./systemd-sysusers-mutable.nix;
systemd-sysusers-immutable = runTest ./systemd-sysusers-immutable.nix;
systemd-sysusers-password-option-override-ordering = runTest ./systemd-sysusers-password-option-override-ordering.nix;
systemd-timesyncd = handleTest ./systemd-timesyncd.nix {};
systemd-timesyncd-nscd-dnssec = handleTest ./systemd-timesyncd-nscd-dnssec.nix {};
systemd-user-linger = handleTest ./systemd-user-linger.nix {};
Expand Down
151 changes: 151 additions & 0 deletions nixos/tests/password-option-override-ordering.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
let
password1 = "foobar";
password2 = "helloworld";
password3 = "bazqux";
password4 = "asdf123";
hashed_bcrypt = "$2b$05$8xIEflrk2RxQtcVXbGIxs.Vl0x7dF1/JSv3cyX6JJt0npzkTCWvxK"; # fnord
hashed_yeshash = "$y$j9T$d8Z4EAf8P1SvM/aDFbxMS0$VnTXMp/Hnc7QdCBEaLTq5ZFOAFo2/PM0/xEAFuOE88."; # fnord
hashed_sha512crypt = "$6$ymzs8WINZ5wGwQcV$VC2S0cQiX8NVukOLymysTPn4v1zJoJp3NGyhnqyv/dAf4NWZsBWYveQcj6gEJr4ZUjRBRjM0Pj1L8TCQ8hUUp0"; # meow
in
import ./make-test-python.nix (
{ pkgs, ... }:
{
name = "password-option-override-ordering";
meta = with pkgs.lib.maintainers; {
maintainers = [ fidgetingbits ];
};

nodes.machine =
{ pkgs, ... }:
{
environment.systemPackages = [ pkgs.shadow ];

users = {
mutableUsers = false;

# NOTE: Below given A -> B it implies B overrides A . Each entry below builds off the next

# initialHashedPassword -> hashedPassword
users.fran = {
isNormalUser = true;
initialHashedPassword = hashed_yeshash;
hashedPassword = hashed_sha512crypt;
};

# initialHashedPassword -> hashedPassword -> initialPassword
users.greg = {
isNormalUser = true;
hashedPassword = hashed_sha512crypt;
initialPassword = password1; # Expect override of above
};

# initialHashedPassword -> hashedPassword -> initialPassword -> password
users.egon = {
isNormalUser = true;
initialPassword = password2;
password = password1;
};

# initialHashedPassword -> hashedPassword -> initialPassword -> password
# NOTE: duplication, but to verify no initialXXX use is consistent
users.alice = {
isNormalUser = true;
hashedPassword = hashed_sha512crypt;
password = password1;
};

# initialHashedPassword -> hashedPassword -> initialPassword -> password -> hashedPasswordFile
users.bob = {
isNormalUser = true;
hashedPassword = hashed_sha512crypt;
password = password1;
hashedPasswordFile = (pkgs.writeText "hashed_bcrypt" hashed_bcrypt).outPath; # Expect override of everything above
};

# Show hashedPassword -> password -> hashedPasswordFile -> initialPassword is false
# to explicitly show the following lib.trace warning in users-groups.nix is wrong:
# ```
# The user 'root' has multiple of the options
# `hashedPassword`, `password`, `hashedPasswordFile`, `initialPassword`
# & `initialHashedPassword` set to a non-null value.
# The options silently discard others by the order of precedence
# given above which can lead to surprising results. To resolve this warning,
# set at most one of the options above to a non-`null` value.
# ```
users.cat = {
isNormalUser = true;
hashedPassword = hashed_sha512crypt;
password = password1;
hashedPasswordFile = (pkgs.writeText "hashed_bcrypt" hashed_bcrypt).outPath;
initialPassword = password2; # lib.trace message implies this overrides everything above
};

# Show hashedPassword -> password -> hashedPasswordFile -> initialHashedPassword is false
# to explicitly show the lib.trace shown above is wrong
users.dan = {
isNormalUser = true;
hashedPassword = hashed_sha512crypt;
initialPassword = password2;
password = password1;
hashedPasswordFile = (pkgs.writeText "hashed_bcrypt" hashed_bcrypt).outPath;
initialHashedPassword = hashed_yeshash; # lib.trace message implies this overrides everything above
};
};
};

testScript = ''
with subtest("alice user has correct password"):
print(machine.succeed("getent passwd alice"))
assert "${hashed_sha512crypt}" not in machine.succeed("getent shadow alice"), "alice user password is not correct"
with subtest("bob user has correct password"):
print(machine.succeed("getent passwd bob"))
assert "${hashed_bcrypt}" in machine.succeed("getent shadow bob"), "bob user password is not correct"
with subtest("cat user has correct password"):
print(machine.succeed("getent passwd cat"))
assert "${hashed_bcrypt}" in machine.succeed("getent shadow cat"), "cat user password is not correct"
with subtest("dan user has correct password"):
print(machine.succeed("getent passwd dan"))
assert "${hashed_bcrypt}" in machine.succeed("getent shadow dan"), "dan user password is not correct"
with subtest("greg user has correct password"):
print(machine.succeed("getent passwd greg"))
assert "${hashed_sha512crypt}" not in machine.succeed("getent shadow greg"), "greg user password is not correct"
machine.wait_for_unit("multi-user.target")
machine.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
with subtest("Test initialPassword override"):
machine.send_key("alt-f2")
machine.wait_until_succeeds("[ $(fgconsole) = 2 ]")
machine.wait_for_unit("[email protected]")
machine.wait_until_succeeds("pgrep -f 'agetty.*tty2'")
machine.wait_until_tty_matches("2", "login: ")
machine.send_chars("egon\n")
machine.wait_until_tty_matches("2", "login: egon")
machine.wait_until_succeeds("pgrep login")
machine.sleep(2)
machine.send_chars("${password1}\n")
machine.send_chars("whoami > /tmp/1\n")
machine.wait_for_file("/tmp/1")
assert "egon" in machine.succeed("cat /tmp/1")
with subtest("Test initialHashedPassword override"):
machine.send_key("alt-f3")
machine.wait_until_succeeds("[ $(fgconsole) = 3 ]")
machine.wait_for_unit("[email protected]")
machine.wait_until_succeeds("pgrep -f 'agetty.*tty3'")
machine.wait_until_tty_matches("3", "login: ")
machine.send_chars("fran\n")
machine.wait_until_tty_matches("3", "login: fran")
machine.wait_until_succeeds("pgrep login")
machine.sleep(2)
machine.send_chars("meow\n")
machine.send_chars("whoami > /tmp/3\n")
machine.wait_for_file("/tmp/3")
assert "fran" in machine.succeed("cat /tmp/3")
'';
}
)
120 changes: 120 additions & 0 deletions nixos/tests/systemd-sysusers-password-option-override-ordering.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
{
lib,
pkgs ? import ../..,
...
}:
let
password = "test";
password1 = "test1";
hashedPassword = "$y$j9T$wLgKY231.8j.ciV2MfEXe1$P0k5j3bCwHgnwW0Ive3w4knrgpiA4TzhCYLAnHvDZ51"; # test
hashedPassword1 = "$y$j9T$s8TyQJtNImvobhGM5Nlez0$3E8/O8EVGuA4sr1OQmrzi8GrRcy/AEhj454JjAn72A2"; # test
hashed_sha512crypt = "$6$ymzs8WINZ5wGwQcV$VC2S0cQiX8NVukOLymysTPn4v1zJoJp3NGyhnqyv/dAf4NWZsBWYveQcj6gEJr4ZUjRBRjM0Pj1L8TCQ8hUUp0"; # meow

hashedPasswordFile = pkgs.writeText "hashed-password" hashedPassword1;
in
{
name = "systemd-sysusers-password-option-override-ordering";

meta.maintainers = with lib.maintainers; [ fidgetingbits ];

nodes.machine = {
systemd.sysusers.enable = true;
system.etc.overlay.enable = true;
boot.initrd.systemd.enable = true;

users.mutableUsers = true;

# NOTE: Below given A -> B it implies B overrides A . Each entry below builds off the next

users.users.root = {
hashedPasswordFile = lib.mkForce null;
initialHashedPassword = password;
};

# initialPassword -> initialHashedPassword
users.users.alice = {
isNormalUser = true;
initialPassword = password;
initialHashedPassword = hashedPassword;
};

# initialPassword -> initialHashedPassword -> hashedPasswordFile
users.users.bob = {
isNormalUser = true;
initialPassword = password;
initialHashedPassword = hashedPassword;
hashedPasswordFile = hashedPasswordFile.outPath;
};

# Show that initialPassword -> password is not true for systemd-sysusers
users.users.cat = {
isNormalUser = true;
initialPassword = password;
password = password1; # We expect this not to override
};
# Show that initialPassword -> password is not true for systemd-sysusers
users.users.dan = {
isNormalUser = true;
initialHashedPassword = hashedPassword;
hashedPassword = hashed_sha512crypt; # We expect this not to override
};
};

testScript = ''
machine.wait_for_unit("systemd-sysusers.service")
with subtest("systemd-sysusers.service contains the credentials"):
sysusers_service = machine.succeed("systemctl cat systemd-sysusers.service")
print(sysusers_service)
assert "SetCredential=passwd.plaintext-password.alice:${password}" in sysusers_service
with subtest("Correct mode on the password files"):
assert machine.succeed("stat -c '%a' /etc/passwd") == "644\n"
assert machine.succeed("stat -c '%a' /etc/group") == "644\n"
assert machine.succeed("stat -c '%a' /etc/shadow") == "0\n"
assert machine.succeed("stat -c '%a' /etc/gshadow") == "0\n"
with subtest("alice user has correct password"):
print(machine.succeed("getent passwd alice"))
assert "${hashedPassword}" in machine.succeed("getent shadow alice"), "alice user password is not correct"
with subtest("bob user has new password after switching to new generation"):
print(machine.succeed("getent passwd bob"))
print(machine.succeed("getent shadow bob"))
assert "${hashedPassword1}" in machine.succeed("getent shadow bob"), "bob user password is not correct"
machine.wait_for_unit("multi-user.target")
machine.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
with subtest("Test initialPassword override"):
machine.send_key("alt-f2")
machine.wait_until_succeeds("[ $(fgconsole) = 2 ]")
machine.wait_for_unit("[email protected]")
machine.wait_until_succeeds("pgrep -f 'agetty.*tty2'")
machine.wait_until_tty_matches("2", "login: ")
machine.send_chars("cat\n")
machine.wait_until_tty_matches("2", "login: cat")
machine.wait_until_succeeds("pgrep login")
machine.sleep(2)
machine.send_chars("${password}\n")
machine.send_chars("whoami > /tmp/1\n")
machine.wait_for_file("/tmp/1")
assert "cat" in machine.succeed("cat /tmp/1")
with subtest("Test initialHashedPassword override"):
machine.send_key("alt-f3")
machine.wait_until_succeeds("[ $(fgconsole) = 3 ]")
machine.wait_for_unit("[email protected]")
machine.wait_until_succeeds("pgrep -f 'agetty.*tty3'")
machine.wait_until_tty_matches("3", "login: ")
machine.send_chars("dan\n")
machine.wait_until_tty_matches("3", "login: dan")
machine.wait_until_succeeds("pgrep login")
machine.sleep(2)
machine.send_chars("test\n")
machine.send_chars("whoami > /tmp/3\n")
machine.wait_for_file("/tmp/3")
assert "dan" in machine.succeed("cat /tmp/3")
'';
}

0 comments on commit b572dd2

Please sign in to comment.