1import ./make-test.nix ({ pkgs, latestKernel ? false, ... }:
2
3let
4 oathSnakeoilSecret = "cdd4083ef8ff1fa9178c6d46bfb1a3";
5
6 # With HOTP mode the password is calculated based on a counter of
7 # how many passwords have been made. In this env, we'll always be on
8 # the 0th counter, so the password is static.
9 #
10 # Generated in nix-shell -p oathToolkit
11 # via: oathtool -v -d6 -w10 cdd4083ef8ff1fa9178c6d46bfb1a3
12 # and picking a the first 4:
13 oathSnakeOilPassword1 = "143349";
14 oathSnakeOilPassword2 = "801753";
15 oathSnakeOilPassword3 = "019933";
16 oathSnakeOilPassword4 = "403895";
17
18 alicePassword = "foobar";
19 # Generated via: mkpasswd -m sha-512 and passing in "foobar"
20 hashedAlicePassword = "$6$MsMrE1q.1HrCgTS$Vq2e/uILzYjSN836TobAyN9xh9oi7EmCmucnZID25qgPoibkw8qTCugiAPnn4eCGvn1A.7oEBFJaaGUaJsQQY.";
21
22in
23{
24 name = "pam-oath-login";
25
26 machine =
27 { config, pkgs, lib, ... }:
28 {
29 security.pam.oath = {
30 enable = true;
31 };
32
33 users.extraUsers.alice = {
34 isNormalUser = true;
35 name = "alice";
36 uid = 1000;
37 hashedPassword = hashedAlicePassword;
38 extraGroups = [ "wheel" ];
39 createHome = true;
40 home = "/home/alice";
41 };
42
43
44 systemd.services.setupOathSnakeoilFile = {
45 wantedBy = [ "default.target" ];
46 before = [ "default.target" ];
47 unitConfig = {
48 type = "oneshot";
49 RemainAfterExit = true;
50 };
51 script = ''
52 touch /etc/users.oath
53 chmod 600 /etc/users.oath
54 chown root /etc/users.oath
55 echo "HOTP/E/6 alice - ${oathSnakeoilSecret}" > /etc/users.oath
56 '';
57 };
58 };
59
60 testScript =
61 ''
62 $machine->waitForUnit('multi-user.target');
63 $machine->waitUntilSucceeds("pgrep -f 'agetty.*tty1'");
64 $machine->screenshot("postboot");
65
66
67 subtest "Invalid password", sub {
68 $machine->fail("pgrep -f 'agetty.*tty2'");
69 $machine->sendKeys("alt-f2");
70 $machine->waitUntilSucceeds("[ \$(fgconsole) = 2 ]");
71 $machine->waitForUnit('getty@tty2.service');
72 $machine->waitUntilSucceeds("pgrep -f 'agetty.*tty2'");
73
74 $machine->waitUntilTTYMatches(2, "login: ");
75 $machine->sendChars("alice\n");
76 $machine->waitUntilTTYMatches(2, "login: alice");
77 $machine->waitUntilSucceeds("pgrep login");
78
79 $machine->waitUntilTTYMatches(2, "One-time password");
80 $machine->sendChars("${oathSnakeOilPassword1}\n");
81 $machine->waitUntilTTYMatches(2, "Password: ");
82 $machine->sendChars("blorg\n");
83 $machine->waitUntilTTYMatches(2, "Login incorrect");
84 };
85
86 subtest "Invalid oath token", sub {
87 $machine->fail("pgrep -f 'agetty.*tty3'");
88 $machine->sendKeys("alt-f3");
89 $machine->waitUntilSucceeds("[ \$(fgconsole) = 3 ]");
90 $machine->waitForUnit('getty@tty3.service');
91 $machine->waitUntilSucceeds("pgrep -f 'agetty.*tty3'");
92
93 $machine->waitUntilTTYMatches(3, "login: ");
94 $machine->sendChars("alice\n");
95 $machine->waitUntilTTYMatches(3, "login: alice");
96 $machine->waitUntilSucceeds("pgrep login");
97 $machine->waitUntilTTYMatches(3, "One-time password");
98 $machine->sendChars("000000\n");
99 $machine->waitUntilTTYMatches(3, "Login incorrect");
100 $machine->waitUntilTTYMatches(3, "login:");
101 };
102
103 subtest "Happy path (both passwords are mandatory to get us in)", sub {
104 $machine->fail("pgrep -f 'agetty.*tty4'");
105 $machine->sendKeys("alt-f4");
106 $machine->waitUntilSucceeds("[ \$(fgconsole) = 4 ]");
107 $machine->waitForUnit('getty@tty4.service');
108 $machine->waitUntilSucceeds("pgrep -f 'agetty.*tty4'");
109
110 $machine->waitUntilTTYMatches(4, "login: ");
111 $machine->sendChars("alice\n");
112 $machine->waitUntilTTYMatches(4, "login: alice");
113 $machine->waitUntilSucceeds("pgrep login");
114 $machine->waitUntilTTYMatches(4, "One-time password");
115 $machine->sendChars("${oathSnakeOilPassword2}\n");
116 $machine->waitUntilTTYMatches(4, "Password: ");
117 $machine->sendChars("${alicePassword}\n");
118
119 $machine->waitUntilSucceeds("pgrep -u alice bash");
120 $machine->sendChars("touch done4\n");
121 $machine->waitForFile("/home/alice/done4");
122 };
123
124 '';
125
126})