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