1import ./make-test.nix ({ ... }:
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
16 alicePassword = "foobar";
17 # Generated via: mkpasswd -m sha-512 and passing in "foobar"
18 hashedAlicePassword = "$6$MsMrE1q.1HrCgTS$Vq2e/uILzYjSN836TobAyN9xh9oi7EmCmucnZID25qgPoibkw8qTCugiAPnn4eCGvn1A.7oEBFJaaGUaJsQQY.";
19
20in
21{
22 name = "pam-oath-login";
23
24 machine =
25 { ... }:
26 {
27 security.pam.oath = {
28 enable = true;
29 };
30
31 users.users.alice = {
32 isNormalUser = true;
33 name = "alice";
34 uid = 1000;
35 hashedPassword = hashedAlicePassword;
36 extraGroups = [ "wheel" ];
37 createHome = true;
38 home = "/home/alice";
39 };
40
41
42 systemd.services.setupOathSnakeoilFile = {
43 wantedBy = [ "default.target" ];
44 before = [ "default.target" ];
45 unitConfig = {
46 type = "oneshot";
47 RemainAfterExit = true;
48 };
49 script = ''
50 touch /etc/users.oath
51 chmod 600 /etc/users.oath
52 chown root /etc/users.oath
53 echo "HOTP/E/6 alice - ${oathSnakeoilSecret}" > /etc/users.oath
54 '';
55 };
56 };
57
58 testScript =
59 ''
60 $machine->waitForUnit('multi-user.target');
61 $machine->waitUntilSucceeds("pgrep -f 'agetty.*tty1'");
62 $machine->screenshot("postboot");
63
64
65 subtest "Invalid password", sub {
66 $machine->fail("pgrep -f 'agetty.*tty2'");
67 $machine->sendKeys("alt-f2");
68 $machine->waitUntilSucceeds("[ \$(fgconsole) = 2 ]");
69 $machine->waitForUnit('getty@tty2.service');
70 $machine->waitUntilSucceeds("pgrep -f 'agetty.*tty2'");
71
72 $machine->waitUntilTTYMatches(2, "login: ");
73 $machine->sendChars("alice\n");
74 $machine->waitUntilTTYMatches(2, "login: alice");
75 $machine->waitUntilSucceeds("pgrep login");
76
77 $machine->waitUntilTTYMatches(2, "One-time password");
78 $machine->sendChars("${oathSnakeOilPassword1}\n");
79 $machine->waitUntilTTYMatches(2, "Password: ");
80 $machine->sendChars("blorg\n");
81 $machine->waitUntilTTYMatches(2, "Login incorrect");
82 };
83
84 subtest "Invalid oath token", sub {
85 $machine->fail("pgrep -f 'agetty.*tty3'");
86 $machine->sendKeys("alt-f3");
87 $machine->waitUntilSucceeds("[ \$(fgconsole) = 3 ]");
88 $machine->waitForUnit('getty@tty3.service');
89 $machine->waitUntilSucceeds("pgrep -f 'agetty.*tty3'");
90
91 $machine->waitUntilTTYMatches(3, "login: ");
92 $machine->sendChars("alice\n");
93 $machine->waitUntilTTYMatches(3, "login: alice");
94 $machine->waitUntilSucceeds("pgrep login");
95 $machine->waitUntilTTYMatches(3, "One-time password");
96 $machine->sendChars("000000\n");
97 $machine->waitUntilTTYMatches(3, "Login incorrect");
98 $machine->waitUntilTTYMatches(3, "login:");
99 };
100
101 subtest "Happy path (both passwords are mandatory to get us in)", sub {
102 $machine->fail("pgrep -f 'agetty.*tty4'");
103 $machine->sendKeys("alt-f4");
104 $machine->waitUntilSucceeds("[ \$(fgconsole) = 4 ]");
105 $machine->waitForUnit('getty@tty4.service');
106 $machine->waitUntilSucceeds("pgrep -f 'agetty.*tty4'");
107
108 $machine->waitUntilTTYMatches(4, "login: ");
109 $machine->sendChars("alice\n");
110 $machine->waitUntilTTYMatches(4, "login: alice");
111 $machine->waitUntilSucceeds("pgrep login");
112 $machine->waitUntilTTYMatches(4, "One-time password");
113 $machine->sendChars("${oathSnakeOilPassword2}\n");
114 $machine->waitUntilTTYMatches(4, "Password: ");
115 $machine->sendChars("${alicePassword}\n");
116
117 $machine->waitUntilSucceeds("pgrep -u alice bash");
118 $machine->sendChars("touch done4\n");
119 $machine->waitForFile("/home/alice/done4");
120 };
121
122 '';
123
124})