1let
2 password1 = "foobar";
3 password2 = "helloworld";
4 password3 = "bazqux";
5 password4 = "asdf123";
6 hashed_bcrypt = "$2b$05$8xIEflrk2RxQtcVXbGIxs.Vl0x7dF1/JSv3cyX6JJt0npzkTCWvxK"; # fnord
7 hashed_yeshash = "$y$j9T$d8Z4EAf8P1SvM/aDFbxMS0$VnTXMp/Hnc7QdCBEaLTq5ZFOAFo2/PM0/xEAFuOE88."; # fnord
8 hashed_sha512crypt = "$6$ymzs8WINZ5wGwQcV$VC2S0cQiX8NVukOLymysTPn4v1zJoJp3NGyhnqyv/dAf4NWZsBWYveQcj6gEJr4ZUjRBRjM0Pj1L8TCQ8hUUp0"; # meow
9in
10{ pkgs, ... }:
11{
12 name = "shadow";
13 meta = with pkgs.lib.maintainers; {
14 maintainers = [ nequissimus ];
15 };
16
17 nodes.shadow =
18 { pkgs, ... }:
19 {
20 environment.systemPackages = [ pkgs.shadow ];
21
22 users = {
23 mutableUsers = true;
24 users.emma = {
25 isNormalUser = true;
26 password = password1;
27 shell = pkgs.bash;
28 };
29 users.layla = {
30 isNormalUser = true;
31 password = password2;
32 shell = pkgs.shadow;
33 };
34 users.ash = {
35 isNormalUser = true;
36 password = password4;
37 shell = pkgs.bash;
38 };
39 users.berta = {
40 isNormalUser = true;
41 hashedPasswordFile = (pkgs.writeText "hashed_bcrypt" hashed_bcrypt).outPath;
42 shell = pkgs.bash;
43 };
44 users.yesim = {
45 isNormalUser = true;
46 hashedPassword = hashed_yeshash;
47 shell = pkgs.bash;
48 };
49 users.leo = {
50 isNormalUser = true;
51 initialHashedPassword = "!";
52 hashedPassword = hashed_sha512crypt; # should take precedence over initialHashedPassword
53 shell = pkgs.bash;
54 };
55 };
56 };
57
58 testScript = ''
59 shadow.wait_for_unit("multi-user.target")
60 shadow.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
61
62 with subtest("Normal login"):
63 shadow.send_key("alt-f2")
64 shadow.wait_until_succeeds("[ $(fgconsole) = 2 ]")
65 shadow.wait_for_unit("getty@tty2.service")
66 shadow.wait_until_succeeds("pgrep -f 'agetty.*tty2'")
67 shadow.wait_until_tty_matches("2", "login: ")
68 shadow.send_chars("emma\n")
69 shadow.wait_until_tty_matches("2", "login: emma")
70 shadow.wait_until_succeeds("pgrep login")
71 shadow.sleep(2)
72 shadow.send_chars("${password1}\n")
73 shadow.send_chars("whoami > /tmp/1\n")
74 shadow.wait_for_file("/tmp/1")
75 assert "emma" in shadow.succeed("cat /tmp/1")
76
77 with subtest("Switch user"):
78 shadow.send_chars("su - ash\n")
79 shadow.sleep(2)
80 shadow.send_chars("${password4}\n")
81 shadow.sleep(2)
82 shadow.send_chars("whoami > /tmp/3\n")
83 shadow.wait_for_file("/tmp/3")
84 assert "ash" in shadow.succeed("cat /tmp/3")
85
86 with subtest("Change password"):
87 shadow.send_key("alt-f3")
88 shadow.wait_until_succeeds("[ $(fgconsole) = 3 ]")
89 shadow.wait_for_unit("getty@tty3.service")
90 shadow.wait_until_succeeds("pgrep -f 'agetty.*tty3'")
91 shadow.wait_until_tty_matches("3", "login: ")
92 shadow.send_chars("emma\n")
93 shadow.wait_until_tty_matches("3", "login: emma")
94 shadow.wait_until_succeeds("pgrep login")
95 shadow.sleep(2)
96 shadow.send_chars("${password1}\n")
97 shadow.send_chars("passwd\n")
98 shadow.sleep(2)
99 shadow.send_chars("${password1}\n")
100 shadow.sleep(2)
101 shadow.send_chars("${password3}\n")
102 shadow.sleep(2)
103 shadow.send_chars("${password3}\n")
104 shadow.sleep(2)
105 shadow.send_key("alt-f4")
106 shadow.wait_until_succeeds("[ $(fgconsole) = 4 ]")
107 shadow.wait_for_unit("getty@tty4.service")
108 shadow.wait_until_succeeds("pgrep -f 'agetty.*tty4'")
109 shadow.wait_until_tty_matches("4", "login: ")
110 shadow.send_chars("emma\n")
111 shadow.wait_until_tty_matches("4", "login: emma")
112 shadow.wait_until_succeeds("pgrep login")
113 shadow.sleep(2)
114 shadow.send_chars("${password1}\n")
115 shadow.wait_until_tty_matches("4", "Login incorrect")
116 shadow.wait_until_tty_matches("4", "login:")
117 shadow.send_chars("emma\n")
118 shadow.wait_until_tty_matches("4", "login: emma")
119 shadow.wait_until_succeeds("pgrep login")
120 shadow.sleep(2)
121 shadow.send_chars("${password3}\n")
122 shadow.send_chars("whoami > /tmp/2\n")
123 shadow.wait_for_file("/tmp/2")
124 assert "emma" in shadow.succeed("cat /tmp/2")
125
126 with subtest("Groups"):
127 assert "foobar" not in shadow.succeed("groups emma")
128 shadow.succeed("groupadd foobar")
129 shadow.succeed("usermod -a -G foobar emma")
130 assert "foobar" in shadow.succeed("groups emma")
131
132 with subtest("nologin shell"):
133 shadow.send_key("alt-f5")
134 shadow.wait_until_succeeds("[ $(fgconsole) = 5 ]")
135 shadow.wait_for_unit("getty@tty5.service")
136 shadow.wait_until_succeeds("pgrep -f 'agetty.*tty5'")
137 shadow.wait_until_tty_matches("5", "login: ")
138 shadow.send_chars("layla\n")
139 shadow.wait_until_tty_matches("5", "login: layla")
140 shadow.wait_until_succeeds("pgrep login")
141 shadow.send_chars("${password2}\n")
142 shadow.wait_until_tty_matches("5", "login:")
143
144 with subtest("check alternate password hashes"):
145 shadow.send_key("alt-f6")
146 shadow.wait_until_succeeds("[ $(fgconsole) = 6 ]")
147 for u in ["berta", "yesim"]:
148 shadow.wait_for_unit("getty@tty6.service")
149 shadow.wait_until_succeeds("pgrep -f 'agetty.*tty6'")
150 shadow.wait_until_tty_matches("6", "login: ")
151 shadow.send_chars(f"{u}\n")
152 shadow.wait_until_tty_matches("6", f"login: {u}")
153 shadow.wait_until_succeeds("pgrep login")
154 shadow.sleep(2)
155 shadow.send_chars("fnord\n")
156 shadow.send_chars(f"whoami > /tmp/{u}\n")
157 shadow.wait_for_file(f"/tmp/{u}")
158 print(shadow.succeed(f"cat /tmp/{u}"))
159 assert u in shadow.succeed(f"cat /tmp/{u}")
160 shadow.send_chars("logout\n")
161
162 with subtest("Ensure hashedPassword does not get overridden by initialHashedPassword"):
163 shadow.send_key("alt-f6")
164 shadow.wait_until_succeeds("[ $(fgconsole) = 6 ]")
165 shadow.wait_for_unit("getty@tty6.service")
166 shadow.wait_until_succeeds("pgrep -f 'agetty.*tty6'")
167 shadow.wait_until_tty_matches("6", "login: ")
168 shadow.send_chars("leo\n")
169 shadow.wait_until_tty_matches("6", "login: leo")
170 shadow.wait_until_succeeds("pgrep login")
171 shadow.sleep(2)
172 shadow.send_chars("meow\n")
173 shadow.send_chars("whoami > /tmp/leo\n")
174 shadow.wait_for_file("/tmp/leo")
175 assert "leo" in shadow.succeed("cat /tmp/leo")
176 shadow.send_chars("logout\n")
177 '';
178}