1
2
3
4
19
20 package volume
21
22 import (
23 "fmt"
24 "os"
25 "path/filepath"
26 "syscall"
27 "testing"
28
29 v1 "k8s.io/api/core/v1"
30 utiltesting "k8s.io/client-go/util/testing"
31 )
32
33 type localFakeMounter struct {
34 path string
35 attributes Attributes
36 }
37
38 func (l *localFakeMounter) GetPath() string {
39 return l.path
40 }
41
42 func (l *localFakeMounter) GetAttributes() Attributes {
43 return l.attributes
44 }
45
46 func (l *localFakeMounter) SetUp(mounterArgs MounterArgs) error {
47 return nil
48 }
49
50 func (l *localFakeMounter) SetUpAt(dir string, mounterArgs MounterArgs) error {
51 return nil
52 }
53
54 func (l *localFakeMounter) GetMetrics() (*Metrics, error) {
55 return nil, nil
56 }
57
58 func TestSkipPermissionChange(t *testing.T) {
59 always := v1.FSGroupChangeAlways
60 onrootMismatch := v1.FSGroupChangeOnRootMismatch
61 tests := []struct {
62 description string
63 fsGroupChangePolicy *v1.PodFSGroupChangePolicy
64 gidOwnerMatch bool
65 permissionMatch bool
66 sgidMatch bool
67 skipPermssion bool
68 }{
69 {
70 description: "skippermission=false, policy=nil",
71 skipPermssion: false,
72 },
73 {
74 description: "skippermission=false, policy=always",
75 fsGroupChangePolicy: &always,
76 skipPermssion: false,
77 },
78 {
79 description: "skippermission=false, policy=always, gidmatch=true",
80 fsGroupChangePolicy: &always,
81 skipPermssion: false,
82 gidOwnerMatch: true,
83 },
84 {
85 description: "skippermission=false, policy=nil, gidmatch=true",
86 fsGroupChangePolicy: nil,
87 skipPermssion: false,
88 gidOwnerMatch: true,
89 },
90 {
91 description: "skippermission=false, policy=onrootmismatch, gidmatch=false",
92 fsGroupChangePolicy: &onrootMismatch,
93 gidOwnerMatch: false,
94 skipPermssion: false,
95 },
96 {
97 description: "skippermission=false, policy=onrootmismatch, gidmatch=true, permmatch=false",
98 fsGroupChangePolicy: &onrootMismatch,
99 gidOwnerMatch: true,
100 permissionMatch: false,
101 skipPermssion: false,
102 },
103 {
104 description: "skippermission=false, policy=onrootmismatch, gidmatch=true, permmatch=true",
105 fsGroupChangePolicy: &onrootMismatch,
106 gidOwnerMatch: true,
107 permissionMatch: true,
108 skipPermssion: false,
109 },
110 {
111 description: "skippermission=false, policy=onrootmismatch, gidmatch=true, permmatch=true, sgidmatch=true",
112 fsGroupChangePolicy: &onrootMismatch,
113 gidOwnerMatch: true,
114 permissionMatch: true,
115 sgidMatch: true,
116 skipPermssion: true,
117 },
118 }
119
120 for _, test := range tests {
121 t.Run(test.description, func(t *testing.T) {
122 tmpDir, err := utiltesting.MkTmpdir("volume_linux_test")
123 if err != nil {
124 t.Fatalf("error creating temp dir: %v", err)
125 }
126
127 defer os.RemoveAll(tmpDir)
128
129 info, err := os.Lstat(tmpDir)
130 if err != nil {
131 t.Fatalf("error reading permission of tmpdir: %v", err)
132 }
133
134 stat, ok := info.Sys().(*syscall.Stat_t)
135 if !ok || stat == nil {
136 t.Fatalf("error reading permission stats for tmpdir: %s", tmpDir)
137 }
138
139 gid := stat.Gid
140
141 var expectedGid int64
142
143 if test.gidOwnerMatch {
144 expectedGid = int64(gid)
145 } else {
146 expectedGid = int64(gid + 3000)
147 }
148
149 mask := rwMask
150
151 if test.permissionMatch {
152 mask |= execMask
153
154 }
155 if test.sgidMatch {
156 mask |= os.ModeSetgid
157 mask = info.Mode() | mask
158 } else {
159 nosgidPerm := info.Mode() &^ os.ModeSetgid
160 mask = nosgidPerm | mask
161 }
162
163 err = os.Chmod(tmpDir, mask)
164 if err != nil {
165 t.Errorf("Chmod failed on %v: %v", tmpDir, err)
166 }
167
168 mounter := &localFakeMounter{path: tmpDir}
169 ok = skipPermissionChange(mounter, tmpDir, &expectedGid, test.fsGroupChangePolicy)
170 if ok != test.skipPermssion {
171 t.Errorf("for %s expected skipPermission to be %v got %v", test.description, test.skipPermssion, ok)
172 }
173
174 })
175 }
176 }
177
178 func TestSetVolumeOwnershipMode(t *testing.T) {
179 always := v1.FSGroupChangeAlways
180 onrootMismatch := v1.FSGroupChangeOnRootMismatch
181 expectedMask := rwMask | os.ModeSetgid | execMask
182
183 tests := []struct {
184 description string
185 fsGroupChangePolicy *v1.PodFSGroupChangePolicy
186 setupFunc func(path string) error
187 assertFunc func(path string) error
188 }{
189 {
190 description: "featuregate=on, fsgroupchangepolicy=always",
191 fsGroupChangePolicy: &always,
192 setupFunc: func(path string) error {
193 info, err := os.Lstat(path)
194 if err != nil {
195 return err
196 }
197
198 err = os.Chmod(path, info.Mode()|expectedMask)
199 if err != nil {
200 return err
201 }
202
203
204 rogueDir := filepath.Join(path, "roguedir")
205 nosgidPerm := info.Mode() &^ os.ModeSetgid
206 err = os.Mkdir(rogueDir, nosgidPerm)
207 if err != nil {
208 return err
209 }
210 return nil
211 },
212 assertFunc: func(path string) error {
213 rogueDir := filepath.Join(path, "roguedir")
214 hasCorrectPermissions := verifyDirectoryPermission(rogueDir, false )
215 if !hasCorrectPermissions {
216 return fmt.Errorf("invalid permissions on %s", rogueDir)
217 }
218 return nil
219 },
220 },
221 {
222 description: "featuregate=on, fsgroupchangepolicy=onrootmismatch,rootdir=validperm",
223 fsGroupChangePolicy: &onrootMismatch,
224 setupFunc: func(path string) error {
225 info, err := os.Lstat(path)
226 if err != nil {
227 return err
228 }
229
230 err = os.Chmod(path, info.Mode()|expectedMask)
231 if err != nil {
232 return err
233 }
234
235
236 rogueDir := filepath.Join(path, "roguedir")
237 err = os.Mkdir(rogueDir, rwMask)
238 if err != nil {
239 return err
240 }
241 return nil
242 },
243 assertFunc: func(path string) error {
244 rogueDir := filepath.Join(path, "roguedir")
245 hasCorrectPermissions := verifyDirectoryPermission(rogueDir, false )
246 if hasCorrectPermissions {
247 return fmt.Errorf("invalid permissions on %s", rogueDir)
248 }
249 return nil
250 },
251 },
252 {
253 description: "featuregate=on, fsgroupchangepolicy=onrootmismatch,rootdir=invalidperm",
254 fsGroupChangePolicy: &onrootMismatch,
255 setupFunc: func(path string) error {
256
257 err := os.Chmod(path, 0770)
258 if err != nil {
259 return err
260 }
261
262
263 rogueDir := filepath.Join(path, "roguedir")
264 err = os.Mkdir(rogueDir, rwMask)
265 if err != nil {
266 return err
267 }
268 return nil
269 },
270 assertFunc: func(path string) error {
271 rogueDir := filepath.Join(path, "roguedir")
272 hasCorrectPermissions := verifyDirectoryPermission(rogueDir, false )
273 if !hasCorrectPermissions {
274 return fmt.Errorf("invalid permissions on %s", rogueDir)
275 }
276 return nil
277 },
278 },
279 }
280
281 for _, test := range tests {
282 t.Run(test.description, func(t *testing.T) {
283 tmpDir, err := utiltesting.MkTmpdir("volume_linux_ownership")
284 if err != nil {
285 t.Fatalf("error creating temp dir: %v", err)
286 }
287
288 defer os.RemoveAll(tmpDir)
289 info, err := os.Lstat(tmpDir)
290 if err != nil {
291 t.Fatalf("error reading permission of tmpdir: %v", err)
292 }
293
294 stat, ok := info.Sys().(*syscall.Stat_t)
295 if !ok || stat == nil {
296 t.Fatalf("error reading permission stats for tmpdir: %s", tmpDir)
297 }
298
299 var expectedGid int64 = int64(stat.Gid)
300 err = test.setupFunc(tmpDir)
301 if err != nil {
302 t.Errorf("for %s error running setup with: %v", test.description, err)
303 }
304
305 mounter := &localFakeMounter{path: "FAKE_DIR_DOESNT_EXIST"}
306 err = SetVolumeOwnership(mounter, tmpDir, &expectedGid, test.fsGroupChangePolicy, nil)
307 if err != nil {
308 t.Errorf("for %s error changing ownership with: %v", test.description, err)
309 }
310 err = test.assertFunc(tmpDir)
311 if err != nil {
312 t.Errorf("for %s error verifying permissions with: %v", test.description, err)
313 }
314 })
315 }
316 }
317
318
319
320 func verifyDirectoryPermission(path string, readonly bool) bool {
321 info, err := os.Lstat(path)
322 if err != nil {
323 return false
324 }
325 stat, ok := info.Sys().(*syscall.Stat_t)
326 if !ok || stat == nil {
327 return false
328 }
329 unixPerms := rwMask
330
331 if readonly {
332 unixPerms = roMask
333 }
334
335 unixPerms |= execMask
336 filePerm := info.Mode().Perm()
337 if (unixPerms&filePerm == unixPerms) && (info.Mode()&os.ModeSetgid != 0) {
338 return true
339 }
340 return false
341 }
342
343 func TestSetVolumeOwnershipOwner(t *testing.T) {
344 fsGroup := int64(3000)
345 currentUid := os.Geteuid()
346 if currentUid != 0 {
347 t.Skip("running as non-root")
348 }
349 currentGid := os.Getgid()
350
351 tests := []struct {
352 description string
353 fsGroup *int64
354 setupFunc func(path string) error
355 assertFunc func(path string) error
356 }{
357 {
358 description: "fsGroup=nil",
359 fsGroup: nil,
360 setupFunc: func(path string) error {
361 filename := filepath.Join(path, "file.txt")
362 file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0755)
363 if err != nil {
364 return err
365 }
366 file.Close()
367 return nil
368 },
369 assertFunc: func(path string) error {
370 filename := filepath.Join(path, "file.txt")
371 if !verifyFileOwner(filename, currentUid, currentGid) {
372 return fmt.Errorf("invalid owner on %s", filename)
373 }
374 return nil
375 },
376 },
377 {
378 description: "*fsGroup=3000",
379 fsGroup: &fsGroup,
380 setupFunc: func(path string) error {
381 filename := filepath.Join(path, "file.txt")
382 file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0755)
383 if err != nil {
384 return err
385 }
386 file.Close()
387 return nil
388 },
389 assertFunc: func(path string) error {
390 filename := filepath.Join(path, "file.txt")
391 if !verifyFileOwner(filename, currentUid, int(fsGroup)) {
392 return fmt.Errorf("invalid owner on %s", filename)
393 }
394 return nil
395 },
396 },
397 {
398 description: "symlink",
399 fsGroup: &fsGroup,
400 setupFunc: func(path string) error {
401 filename := filepath.Join(path, "file.txt")
402 file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0755)
403 if err != nil {
404 return err
405 }
406 file.Close()
407
408 symname := filepath.Join(path, "file_link.txt")
409 err = os.Symlink(filename, symname)
410 if err != nil {
411 return err
412 }
413
414 return nil
415 },
416 assertFunc: func(path string) error {
417 symname := filepath.Join(path, "file_link.txt")
418 if !verifyFileOwner(symname, currentUid, int(fsGroup)) {
419 return fmt.Errorf("invalid owner on %s", symname)
420 }
421 return nil
422 },
423 },
424 }
425
426 for _, test := range tests {
427 t.Run(test.description, func(t *testing.T) {
428 tmpDir, err := utiltesting.MkTmpdir("volume_linux_ownership")
429 if err != nil {
430 t.Fatalf("error creating temp dir: %v", err)
431 }
432
433 defer os.RemoveAll(tmpDir)
434
435 err = test.setupFunc(tmpDir)
436 if err != nil {
437 t.Errorf("for %s error running setup with: %v", test.description, err)
438 }
439
440 mounter := &localFakeMounter{path: tmpDir}
441 always := v1.FSGroupChangeAlways
442 err = SetVolumeOwnership(mounter, tmpDir, test.fsGroup, &always, nil)
443 if err != nil {
444 t.Errorf("for %s error changing ownership with: %v", test.description, err)
445 }
446 err = test.assertFunc(tmpDir)
447 if err != nil {
448 t.Errorf("for %s error verifying permissions with: %v", test.description, err)
449 }
450 })
451 }
452 }
453
454
455
456 func verifyFileOwner(path string, uid, gid int) bool {
457 info, err := os.Lstat(path)
458 if err != nil {
459 return false
460 }
461 stat, ok := info.Sys().(*syscall.Stat_t)
462 if !ok || stat == nil {
463 return false
464 }
465
466 if int(stat.Uid) != uid || int(stat.Gid) != gid {
467 return false
468 }
469
470 return true
471 }
472
View as plain text