1
2
3
4 package security
5
6 import (
7 "fmt"
8 "os"
9 "os/exec"
10 "path/filepath"
11 "regexp"
12 "testing"
13 )
14
15 const (
16 vmAccountName = `NT VIRTUAL MACHINE\\Virtual Machines`
17 vmAccountSID = "S-1-5-83-0"
18 )
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 func TestGrantVmGroupAccessDefault(t *testing.T) {
38 f1Path := filepath.Join(t.TempDir(), "gvmgafile")
39 f, err := os.Create(f1Path)
40 if err != nil {
41 t.Fatal(err)
42 }
43 defer func() {
44 _ = f.Close()
45 _ = os.Remove(f1Path)
46 }()
47
48 dir2 := t.TempDir()
49 f2Path := filepath.Join(dir2, "find.txt")
50 find, err := os.Create(f2Path)
51 if err != nil {
52 t.Fatal(err)
53 }
54 defer func() {
55 _ = find.Close()
56 _ = os.Remove(f2Path)
57 }()
58
59 if err := GrantVmGroupAccess(f1Path); err != nil {
60 t.Fatal(err)
61 }
62
63 if err := GrantVmGroupAccess(dir2); err != nil {
64 t.Fatal(err)
65 }
66
67 verifyVMAccountDACLs(t,
68 f1Path,
69 []string{`(R)`},
70 )
71
72
73
74
75
76
77
78
79
80
81
82 verifyVMAccountDACLs(t,
83 dir2,
84 []string{`(R)`, `(OI)(CI)(IO)(GR)`},
85 )
86
87 verifyVMAccountDACLs(t,
88 f2Path,
89 []string{`(I)(R)`},
90 )
91 }
92
93 func TestGrantVMGroupAccess_File_DesiredPermissions(t *testing.T) {
94 type config struct {
95 name string
96 desiredAccess accessMask
97 expectedPermissions []string
98 }
99
100 for _, cfg := range []config{
101 {
102 name: "Read",
103 desiredAccess: AccessMaskRead,
104 expectedPermissions: []string{`(R)`},
105 },
106 {
107 name: "Write",
108 desiredAccess: AccessMaskWrite,
109 expectedPermissions: []string{`(W,Rc)`},
110 },
111 {
112 name: "Execute",
113 desiredAccess: AccessMaskExecute,
114 expectedPermissions: []string{`(Rc,S,X,RA)`},
115 },
116 {
117 name: "ReadWrite",
118 desiredAccess: AccessMaskRead | AccessMaskWrite,
119 expectedPermissions: []string{`(R,W)`},
120 },
121 {
122 name: "ReadExecute",
123 desiredAccess: AccessMaskRead | AccessMaskExecute,
124 expectedPermissions: []string{`(RX)`},
125 },
126 {
127 name: "WriteExecute",
128 desiredAccess: AccessMaskWrite | AccessMaskExecute,
129 expectedPermissions: []string{`(W,Rc,X,RA)`},
130 },
131 {
132 name: "ReadWriteExecute",
133 desiredAccess: AccessMaskRead | AccessMaskWrite | AccessMaskExecute,
134 expectedPermissions: []string{`(RX,W)`},
135 },
136 {
137 name: "All",
138 desiredAccess: AccessMaskAll,
139 expectedPermissions: []string{`(F)`},
140 },
141 } {
142 t.Run(cfg.name, func(t *testing.T) {
143 dir := t.TempDir()
144 fd, err := os.Create(filepath.Join(dir, "test.txt"))
145 if err != nil {
146 t.Fatalf("failed to create temporary file: %s", err)
147 }
148 defer func() {
149 _ = fd.Close()
150 _ = os.Remove(fd.Name())
151 }()
152
153 if err := GrantVmGroupAccessWithMask(fd.Name(), cfg.desiredAccess); err != nil {
154 t.Fatal(err)
155 }
156 verifyVMAccountDACLs(t, fd.Name(), cfg.expectedPermissions)
157 })
158 }
159 }
160
161 func TestGrantVMGroupAccess_Directory_Permissions(t *testing.T) {
162 type config struct {
163 name string
164 access accessMask
165 filePermissions []string
166 dirPermissions []string
167 }
168
169 for _, cfg := range []config{
170 {
171 name: "Read",
172 access: AccessMaskRead,
173 filePermissions: []string{`(I)(R)`},
174 dirPermissions: []string{`(R)`, `(OI)(CI)(IO)(GR)`},
175 },
176 {
177 name: "Write",
178 access: AccessMaskWrite,
179 filePermissions: []string{`(I)(W,Rc)`},
180 dirPermissions: []string{`(W,Rc)`, `(OI)(CI)(IO)(GW)`},
181 },
182 {
183 name: "Execute",
184 access: AccessMaskExecute,
185 filePermissions: []string{`(I)(Rc,S,X,RA)`},
186 dirPermissions: []string{`(Rc,S,X,RA)`, `(OI)(CI)(IO)(GE)`},
187 },
188 {
189 name: "ReadWrite",
190 access: AccessMaskRead | AccessMaskWrite,
191 filePermissions: []string{`(I)(R,W)`},
192 dirPermissions: []string{`(R,W)`, `(OI)(CI)(IO)(GR,GW)`},
193 },
194 {
195 name: "ReadExecute",
196 access: AccessMaskRead | AccessMaskExecute,
197 filePermissions: []string{`(I)(RX)`},
198 dirPermissions: []string{`(RX)`, `(OI)(CI)(IO)(GR,GE)`},
199 },
200 {
201 name: "WriteExecute",
202 access: AccessMaskWrite | AccessMaskExecute,
203 filePermissions: []string{`(I)(W,Rc,X,RA)`},
204 dirPermissions: []string{`(W,Rc,X,RA)`, `(OI)(CI)(IO)(GW,GE)`},
205 },
206 {
207 name: "ReadWriteExecute",
208 access: AccessMaskRead | AccessMaskWrite | AccessMaskExecute,
209 filePermissions: []string{`(I)(RX,W)`},
210 dirPermissions: []string{`(RX,W)`, `(OI)(CI)(IO)(GR,GW,GE)`},
211 },
212 {
213 name: "All",
214 access: AccessMaskAll,
215 filePermissions: []string{`(I)(F)`},
216 dirPermissions: []string{`(F)`, `(OI)(CI)(IO)(F)`},
217 }} {
218 t.Run(cfg.name, func(t *testing.T) {
219 dir := t.TempDir()
220 fd, err := os.Create(filepath.Join(dir, "test.txt"))
221 if err != nil {
222 t.Fatalf("failed to create temporary file: %s", err)
223 }
224 defer func() {
225 _ = fd.Close()
226 _ = os.Remove(fd.Name())
227 }()
228
229 if err := GrantVmGroupAccessWithMask(dir, cfg.access); err != nil {
230 t.Fatal(err)
231 }
232 verifyVMAccountDACLs(t, dir, cfg.dirPermissions)
233 verifyVMAccountDACLs(t, fd.Name(), cfg.filePermissions)
234 })
235 }
236 }
237
238 func TestGrantVmGroupAccess_Invalid_AccessMask(t *testing.T) {
239 t.Helper()
240 for _, access := range []accessMask{
241 0,
242 1,
243 0x02000001,
244 } {
245 t.Run(fmt.Sprintf("AccessMask_0x%x", access), func(t *testing.T) {
246 dir := t.TempDir()
247 fd, err := os.Create(filepath.Join(dir, "test.txt"))
248 if err != nil {
249 t.Fatalf("failed to create temporary file: %s", err)
250 }
251 defer func() {
252 _ = fd.Close()
253 _ = os.Remove(fd.Name())
254 }()
255
256 if err := GrantVmGroupAccessWithMask(fd.Name(), access); err == nil {
257 t.Fatalf("expected an error for mask: %x", access)
258 }
259 })
260 }
261 }
262
263 func verifyVMAccountDACLs(t *testing.T, name string, permissions []string) {
264 t.Helper()
265 cmd := exec.Command("icacls", name)
266 outb, err := cmd.CombinedOutput()
267 if err != nil {
268 t.Fatal(err)
269 }
270 out := string(outb)
271
272 for _, p := range permissions {
273
274 p = regexp.QuoteMeta(p)
275
276 nameToCheck := vmAccountName + ":" + p
277 sidToCheck := vmAccountSID + ":" + p
278
279 rxName := regexp.MustCompile(nameToCheck)
280 rxSID := regexp.MustCompile(sidToCheck)
281
282 matchesName := rxName.FindAllStringIndex(out, -1)
283 matchesSID := rxSID.FindAllStringIndex(out, -1)
284
285 if len(matchesName) != 1 && len(matchesSID) != 1 {
286 t.Fatalf("expected one match for %s or %s\n%s\n", nameToCheck, sidToCheck, out)
287 }
288 }
289 }
290
View as plain text