k8s.io/kubernetes@v1.29.3/pkg/volume/volume_linux_test.go (about) 1 //go:build linux 2 // +build linux 3 4 /* 5 Copyright 2020 The Kubernetes Authors. 6 7 Licensed under the Apache License, Version 2.0 (the "License"); 8 you may not use this file except in compliance with the License. 9 You may obtain a copy of the License at 10 11 http://www.apache.org/licenses/LICENSE-2.0 12 13 Unless required by applicable law or agreed to in writing, software 14 distributed under the License is distributed on an "AS IS" BASIS, 15 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 See the License for the specific language governing permissions and 17 limitations under the License. 18 */ 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 // change mode of root folder to be right 198 err = os.Chmod(path, info.Mode()|expectedMask) 199 if err != nil { 200 return err 201 } 202 203 // create a subdirectory with invalid permissions 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 /*readOnly*/) 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 // change mode of root folder to be right 230 err = os.Chmod(path, info.Mode()|expectedMask) 231 if err != nil { 232 return err 233 } 234 235 // create a subdirectory with invalid permissions 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 /*readOnly*/) 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 // change mode of root folder to be right 257 err := os.Chmod(path, 0770) 258 if err != nil { 259 return err 260 } 261 262 // create a subdirectory with invalid permissions 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 /*readOnly*/) 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"} // SetVolumeOwnership() must rely on tmpDir 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 // verifyDirectoryPermission checks if given path has directory permissions 319 // that is expected by k8s. If returns true if it does otherwise false 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 // verifyFileOwner checks if given path is owned by uid and gid. 455 // It returns true if it is otherwise false. 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 }