1# Some tests to ensure doas is working properly.
2{ lib, ... }:
3{
4 name = "doas";
5 meta.maintainers = with lib.maintainers; [ cole-h ];
6
7 nodes.machine =
8 { ... }:
9 {
10 users.groups = {
11 foobar = { };
12 barfoo = { };
13 baz = {
14 gid = 1337;
15 };
16 };
17 users.users = {
18 test0 = {
19 isNormalUser = true;
20 extraGroups = [ "wheel" ];
21 };
22 test1 = {
23 isNormalUser = true;
24 };
25 test2 = {
26 isNormalUser = true;
27 extraGroups = [ "foobar" ];
28 };
29 test3 = {
30 isNormalUser = true;
31 extraGroups = [ "barfoo" ];
32 };
33 test4 = {
34 isNormalUser = true;
35 extraGroups = [ "baz" ];
36 };
37 test5 = {
38 isNormalUser = true;
39 };
40 test6 = {
41 isNormalUser = true;
42 };
43 test7 = {
44 isNormalUser = true;
45 };
46 };
47
48 security.doas = {
49 enable = true;
50 wheelNeedsPassword = false;
51
52 extraRules = [
53 {
54 users = [ "test1" ];
55 groups = [ "foobar" ];
56 }
57 {
58 users = [ "test2" ];
59 noPass = true;
60 setEnv = [
61 "CORRECT"
62 "HORSE=BATTERY"
63 ];
64 }
65 {
66 groups = [
67 "barfoo"
68 1337
69 ];
70 noPass = true;
71 }
72 {
73 users = [ "test5" ];
74 noPass = true;
75 keepEnv = true;
76 runAs = "test1";
77 }
78 {
79 users = [ "test6" ];
80 noPass = true;
81 keepEnv = true;
82 setEnv = [ "-STAPLE" ];
83 }
84 {
85 users = [ "test7" ];
86 noPass = true;
87 setEnv = [ "-SSH_AUTH_SOCK" ];
88 }
89 ];
90 };
91 };
92
93 testScript = ''
94 with subtest("users in wheel group should have passwordless doas"):
95 machine.succeed('su - test0 -c "doas -u root true"')
96
97 with subtest("test1 user should not be able to use doas without password"):
98 machine.fail('su - test1 -c "doas -n -u root true"')
99
100 with subtest("test2 user should be able to keep some env"):
101 if "CORRECT=1" not in machine.succeed('su - test2 -c "CORRECT=1 doas env"'):
102 raise Exception("failed to keep CORRECT")
103
104 if "HORSE=BATTERY" not in machine.succeed('su - test2 -c "doas env"'):
105 raise Exception("failed to setenv HORSE=BATTERY")
106
107 with subtest("users in group 'barfoo' shouldn't require password"):
108 machine.succeed("doas -u test3 doas -n -u root true")
109
110 with subtest("users in group 'baz' (GID 1337) shouldn't require password"):
111 machine.succeed("doas -u test4 doas -n -u root echo true")
112
113 with subtest("test5 user should be able to run commands under test1"):
114 machine.succeed("doas -u test5 doas -n -u test1 true")
115
116 with subtest("test5 user should not be able to run commands under root"):
117 machine.fail("doas -u test5 doas -n -u root true")
118
119 with subtest("test6 user should be able to keepenv"):
120 envs = ["BATTERY=HORSE", "CORRECT=false"]
121 out = machine.succeed(
122 'su - test6 -c "BATTERY=HORSE CORRECT=false STAPLE=Tr0ub4dor doas env"'
123 )
124
125 if not all(env in out for env in envs):
126 raise Exception("failed to keep BATTERY or CORRECT")
127 if "STAPLE=Tr0ub4dor" in out:
128 raise Exception("failed to exclude STAPLE")
129
130 with subtest("test7 should not have access to SSH_AUTH_SOCK"):
131 if "SSH_AUTH_SOCK=HOLEY" in machine.succeed(
132 'su - test7 -c "SSH_AUTH_SOCK=HOLEY doas env"'
133 ):
134 raise Exception("failed to exclude SSH_AUTH_SOCK")
135
136 # Test that the doas setuid wrapper precedes the unwrapped version in PATH after
137 # calling doas.
138 # The PATH set by doas is defined in
139 # ../../pkgs/tools/security/doas/0001-add-NixOS-specific-dirs-to-safe-PATH.patch
140 with subtest("recursive calls to doas from subprocesses should succeed"):
141 machine.succeed('doas -u test0 sh -c "doas -u test0 true"')
142
143 with subtest("test0 should inherit TERMINFO_DIRS from the user environment"):
144 dirs = machine.succeed(
145 "su - test0 -c 'doas -u root $SHELL -c \"echo \$TERMINFO_DIRS\"'"
146 )
147
148 if not "test0" in dirs:
149 raise Exception(f"user profile TERMINFO_DIRS is not preserved: {dirs}")
150 '';
151}