mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-31 05:40:42 +00:00 
			
		
		
		
	Former TestSetVolumeOwnership only checks the mode of the files. This commit adds a new TestSetVolumeOwnershipOwner that checks the ownership of the files.
		
			
				
	
	
		
			483 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			483 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // +build linux
 | |
| 
 | |
| /*
 | |
| Copyright 2020 The Kubernetes Authors.
 | |
| 
 | |
| Licensed under the Apache License, Version 2.0 (the "License");
 | |
| you may not use this file except in compliance with the License.
 | |
| You may obtain a copy of the License at
 | |
| 
 | |
|     http://www.apache.org/licenses/LICENSE-2.0
 | |
| 
 | |
| Unless required by applicable law or agreed to in writing, software
 | |
| distributed under the License is distributed on an "AS IS" BASIS,
 | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| See the License for the specific language governing permissions and
 | |
| limitations under the License.
 | |
| */
 | |
| 
 | |
| package volume
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"syscall"
 | |
| 	"testing"
 | |
| 
 | |
| 	v1 "k8s.io/api/core/v1"
 | |
| 	utilfeature "k8s.io/apiserver/pkg/util/feature"
 | |
| 	utiltesting "k8s.io/client-go/util/testing"
 | |
| 	featuregatetesting "k8s.io/component-base/featuregate/testing"
 | |
| 	"k8s.io/kubernetes/pkg/features"
 | |
| )
 | |
| 
 | |
| type localFakeMounter struct {
 | |
| 	path       string
 | |
| 	attributes Attributes
 | |
| }
 | |
| 
 | |
| func (l *localFakeMounter) GetPath() string {
 | |
| 	return l.path
 | |
| }
 | |
| 
 | |
| func (l *localFakeMounter) GetAttributes() Attributes {
 | |
| 	return l.attributes
 | |
| }
 | |
| 
 | |
| func (l *localFakeMounter) CanMount() error {
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (l *localFakeMounter) SetUp(mounterArgs MounterArgs) error {
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (l *localFakeMounter) SetUpAt(dir string, mounterArgs MounterArgs) error {
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (l *localFakeMounter) GetMetrics() (*Metrics, error) {
 | |
| 	return nil, nil
 | |
| }
 | |
| 
 | |
| func TestSkipPermissionChange(t *testing.T) {
 | |
| 	always := v1.FSGroupChangeAlways
 | |
| 	onrootMismatch := v1.FSGroupChangeOnRootMismatch
 | |
| 	tests := []struct {
 | |
| 		description         string
 | |
| 		fsGroupChangePolicy *v1.PodFSGroupChangePolicy
 | |
| 		gidOwnerMatch       bool
 | |
| 		permissionMatch     bool
 | |
| 		sgidMatch           bool
 | |
| 		skipPermssion       bool
 | |
| 	}{
 | |
| 		{
 | |
| 			description:   "skippermission=false, policy=nil",
 | |
| 			skipPermssion: false,
 | |
| 		},
 | |
| 		{
 | |
| 			description:         "skippermission=false, policy=always",
 | |
| 			fsGroupChangePolicy: &always,
 | |
| 			skipPermssion:       false,
 | |
| 		},
 | |
| 		{
 | |
| 			description:         "skippermission=false, policy=always, gidmatch=true",
 | |
| 			fsGroupChangePolicy: &always,
 | |
| 			skipPermssion:       false,
 | |
| 			gidOwnerMatch:       true,
 | |
| 		},
 | |
| 		{
 | |
| 			description:         "skippermission=false, policy=nil, gidmatch=true",
 | |
| 			fsGroupChangePolicy: nil,
 | |
| 			skipPermssion:       false,
 | |
| 			gidOwnerMatch:       true,
 | |
| 		},
 | |
| 		{
 | |
| 			description:         "skippermission=false, policy=onrootmismatch, gidmatch=false",
 | |
| 			fsGroupChangePolicy: &onrootMismatch,
 | |
| 			gidOwnerMatch:       false,
 | |
| 			skipPermssion:       false,
 | |
| 		},
 | |
| 		{
 | |
| 			description:         "skippermission=false, policy=onrootmismatch, gidmatch=true, permmatch=false",
 | |
| 			fsGroupChangePolicy: &onrootMismatch,
 | |
| 			gidOwnerMatch:       true,
 | |
| 			permissionMatch:     false,
 | |
| 			skipPermssion:       false,
 | |
| 		},
 | |
| 		{
 | |
| 			description:         "skippermission=false, policy=onrootmismatch, gidmatch=true, permmatch=true",
 | |
| 			fsGroupChangePolicy: &onrootMismatch,
 | |
| 			gidOwnerMatch:       true,
 | |
| 			permissionMatch:     true,
 | |
| 			skipPermssion:       false,
 | |
| 		},
 | |
| 		{
 | |
| 			description:         "skippermission=false, policy=onrootmismatch, gidmatch=true, permmatch=true, sgidmatch=true",
 | |
| 			fsGroupChangePolicy: &onrootMismatch,
 | |
| 			gidOwnerMatch:       true,
 | |
| 			permissionMatch:     true,
 | |
| 			sgidMatch:           true,
 | |
| 			skipPermssion:       true,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, test := range tests {
 | |
| 		t.Run(test.description, func(t *testing.T) {
 | |
| 			tmpDir, err := utiltesting.MkTmpdir("volume_linux_test")
 | |
| 			if err != nil {
 | |
| 				t.Fatalf("error creating temp dir: %v", err)
 | |
| 			}
 | |
| 
 | |
| 			defer os.RemoveAll(tmpDir)
 | |
| 
 | |
| 			info, err := os.Lstat(tmpDir)
 | |
| 			if err != nil {
 | |
| 				t.Fatalf("error reading permission of tmpdir: %v", err)
 | |
| 			}
 | |
| 
 | |
| 			stat, ok := info.Sys().(*syscall.Stat_t)
 | |
| 			if !ok || stat == nil {
 | |
| 				t.Fatalf("error reading permission stats for tmpdir: %s", tmpDir)
 | |
| 			}
 | |
| 
 | |
| 			gid := stat.Gid
 | |
| 
 | |
| 			var expectedGid int64
 | |
| 
 | |
| 			if test.gidOwnerMatch {
 | |
| 				expectedGid = int64(gid)
 | |
| 			} else {
 | |
| 				expectedGid = int64(gid + 3000)
 | |
| 			}
 | |
| 
 | |
| 			mask := rwMask
 | |
| 
 | |
| 			if test.permissionMatch {
 | |
| 				mask |= execMask
 | |
| 
 | |
| 			}
 | |
| 			if test.sgidMatch {
 | |
| 				mask |= os.ModeSetgid
 | |
| 				mask = info.Mode() | mask
 | |
| 			} else {
 | |
| 				nosgidPerm := info.Mode() &^ os.ModeSetgid
 | |
| 				mask = nosgidPerm | mask
 | |
| 			}
 | |
| 
 | |
| 			err = os.Chmod(tmpDir, mask)
 | |
| 			if err != nil {
 | |
| 				t.Errorf("Chmod failed on %v: %v", tmpDir, err)
 | |
| 			}
 | |
| 
 | |
| 			mounter := &localFakeMounter{path: tmpDir}
 | |
| 			ok = skipPermissionChange(mounter, &expectedGid, test.fsGroupChangePolicy)
 | |
| 			if ok != test.skipPermssion {
 | |
| 				t.Errorf("for %s expected skipPermission to be %v got %v", test.description, test.skipPermssion, ok)
 | |
| 			}
 | |
| 
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestSetVolumeOwnershipMode(t *testing.T) {
 | |
| 	always := v1.FSGroupChangeAlways
 | |
| 	onrootMismatch := v1.FSGroupChangeOnRootMismatch
 | |
| 	expectedMask := rwMask | os.ModeSetgid | execMask
 | |
| 
 | |
| 	tests := []struct {
 | |
| 		description         string
 | |
| 		fsGroupChangePolicy *v1.PodFSGroupChangePolicy
 | |
| 		setupFunc           func(path string) error
 | |
| 		assertFunc          func(path string) error
 | |
| 		featureGate         bool
 | |
| 	}{
 | |
| 		{
 | |
| 			description:         "featuregate=on, fsgroupchangepolicy=always",
 | |
| 			fsGroupChangePolicy: &always,
 | |
| 			featureGate:         true,
 | |
| 			setupFunc: func(path string) error {
 | |
| 				info, err := os.Lstat(path)
 | |
| 				if err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 				// change mode of root folder to be right
 | |
| 				err = os.Chmod(path, info.Mode()|expectedMask)
 | |
| 				if err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 
 | |
| 				// create a subdirectory with invalid permissions
 | |
| 				rogueDir := filepath.Join(path, "roguedir")
 | |
| 				nosgidPerm := info.Mode() &^ os.ModeSetgid
 | |
| 				err = os.Mkdir(rogueDir, nosgidPerm)
 | |
| 				if err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 				return nil
 | |
| 			},
 | |
| 			assertFunc: func(path string) error {
 | |
| 				rogueDir := filepath.Join(path, "roguedir")
 | |
| 				hasCorrectPermissions := verifyDirectoryPermission(rogueDir, false /*readOnly*/)
 | |
| 				if !hasCorrectPermissions {
 | |
| 					return fmt.Errorf("invalid permissions on %s", rogueDir)
 | |
| 				}
 | |
| 				return nil
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			description:         "featuregate=on, fsgroupchangepolicy=onrootmismatch,rootdir=validperm",
 | |
| 			fsGroupChangePolicy: &onrootMismatch,
 | |
| 			featureGate:         true,
 | |
| 			setupFunc: func(path string) error {
 | |
| 				info, err := os.Lstat(path)
 | |
| 				if err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 				// change mode of root folder to be right
 | |
| 				err = os.Chmod(path, info.Mode()|expectedMask)
 | |
| 				if err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 
 | |
| 				// create a subdirectory with invalid permissions
 | |
| 				rogueDir := filepath.Join(path, "roguedir")
 | |
| 				err = os.Mkdir(rogueDir, rwMask)
 | |
| 				if err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 				return nil
 | |
| 			},
 | |
| 			assertFunc: func(path string) error {
 | |
| 				rogueDir := filepath.Join(path, "roguedir")
 | |
| 				hasCorrectPermissions := verifyDirectoryPermission(rogueDir, false /*readOnly*/)
 | |
| 				if hasCorrectPermissions {
 | |
| 					return fmt.Errorf("invalid permissions on %s", rogueDir)
 | |
| 				}
 | |
| 				return nil
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			description:         "featuregate=on, fsgroupchangepolicy=onrootmismatch,rootdir=invalidperm",
 | |
| 			fsGroupChangePolicy: &onrootMismatch,
 | |
| 			featureGate:         true,
 | |
| 			setupFunc: func(path string) error {
 | |
| 				// change mode of root folder to be right
 | |
| 				err := os.Chmod(path, 0770)
 | |
| 				if err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 
 | |
| 				// create a subdirectory with invalid permissions
 | |
| 				rogueDir := filepath.Join(path, "roguedir")
 | |
| 				err = os.Mkdir(rogueDir, rwMask)
 | |
| 				if err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 				return nil
 | |
| 			},
 | |
| 			assertFunc: func(path string) error {
 | |
| 				rogueDir := filepath.Join(path, "roguedir")
 | |
| 				hasCorrectPermissions := verifyDirectoryPermission(rogueDir, false /*readOnly*/)
 | |
| 				if !hasCorrectPermissions {
 | |
| 					return fmt.Errorf("invalid permissions on %s", rogueDir)
 | |
| 				}
 | |
| 				return nil
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, test := range tests {
 | |
| 		t.Run(test.description, func(t *testing.T) {
 | |
| 			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ConfigurableFSGroupPolicy, test.featureGate)()
 | |
| 			tmpDir, err := utiltesting.MkTmpdir("volume_linux_ownership")
 | |
| 			if err != nil {
 | |
| 				t.Fatalf("error creating temp dir: %v", err)
 | |
| 			}
 | |
| 
 | |
| 			defer os.RemoveAll(tmpDir)
 | |
| 			info, err := os.Lstat(tmpDir)
 | |
| 			if err != nil {
 | |
| 				t.Fatalf("error reading permission of tmpdir: %v", err)
 | |
| 			}
 | |
| 
 | |
| 			stat, ok := info.Sys().(*syscall.Stat_t)
 | |
| 			if !ok || stat == nil {
 | |
| 				t.Fatalf("error reading permission stats for tmpdir: %s", tmpDir)
 | |
| 			}
 | |
| 
 | |
| 			var expectedGid int64 = int64(stat.Gid)
 | |
| 			err = test.setupFunc(tmpDir)
 | |
| 			if err != nil {
 | |
| 				t.Errorf("for %s error running setup with: %v", test.description, err)
 | |
| 			}
 | |
| 
 | |
| 			mounter := &localFakeMounter{path: tmpDir}
 | |
| 			err = SetVolumeOwnership(mounter, &expectedGid, test.fsGroupChangePolicy, nil)
 | |
| 			if err != nil {
 | |
| 				t.Errorf("for %s error changing ownership with: %v", test.description, err)
 | |
| 			}
 | |
| 			err = test.assertFunc(tmpDir)
 | |
| 			if err != nil {
 | |
| 				t.Errorf("for %s error verifying permissions with: %v", test.description, err)
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // verifyDirectoryPermission checks if given path has directory permissions
 | |
| // that is expected by k8s. If returns true if it does otherwise false
 | |
| func verifyDirectoryPermission(path string, readonly bool) bool {
 | |
| 	info, err := os.Lstat(path)
 | |
| 	if err != nil {
 | |
| 		return false
 | |
| 	}
 | |
| 	stat, ok := info.Sys().(*syscall.Stat_t)
 | |
| 	if !ok || stat == nil {
 | |
| 		return false
 | |
| 	}
 | |
| 	unixPerms := rwMask
 | |
| 
 | |
| 	if readonly {
 | |
| 		unixPerms = roMask
 | |
| 	}
 | |
| 
 | |
| 	unixPerms |= execMask
 | |
| 	filePerm := info.Mode().Perm()
 | |
| 	if (unixPerms&filePerm == unixPerms) && (info.Mode()&os.ModeSetgid != 0) {
 | |
| 		return true
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| func TestSetVolumeOwnershipOwner(t *testing.T) {
 | |
| 	fsGroup := int64(3000)
 | |
| 	currentUid := os.Getuid()
 | |
| 	if currentUid != 0 {
 | |
| 		t.Skip("running as non-root")
 | |
| 	}
 | |
| 	currentGid := os.Getgid()
 | |
| 
 | |
| 	tests := []struct {
 | |
| 		description string
 | |
| 		fsGroup     *int64
 | |
| 		setupFunc   func(path string) error
 | |
| 		assertFunc  func(path string) error
 | |
| 	}{
 | |
| 		{
 | |
| 			description: "fsGroup=nil",
 | |
| 			fsGroup:     nil,
 | |
| 			setupFunc: func(path string) error {
 | |
| 				filename := filepath.Join(path, "file.txt")
 | |
| 				file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0755)
 | |
| 				if err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 				file.Close()
 | |
| 				return nil
 | |
| 			},
 | |
| 			assertFunc: func(path string) error {
 | |
| 				filename := filepath.Join(path, "file.txt")
 | |
| 				if !verifyFileOwner(filename, currentUid, currentGid) {
 | |
| 					return fmt.Errorf("invalid owner on %s", filename)
 | |
| 				}
 | |
| 				return nil
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			description: "*fsGroup=3000",
 | |
| 			fsGroup:     &fsGroup,
 | |
| 			setupFunc: func(path string) error {
 | |
| 				filename := filepath.Join(path, "file.txt")
 | |
| 				file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0755)
 | |
| 				if err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 				file.Close()
 | |
| 				return nil
 | |
| 			},
 | |
| 			assertFunc: func(path string) error {
 | |
| 				filename := filepath.Join(path, "file.txt")
 | |
| 				if !verifyFileOwner(filename, currentUid, int(fsGroup)) {
 | |
| 					return fmt.Errorf("invalid owner on %s", filename)
 | |
| 				}
 | |
| 				return nil
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			description: "symlink",
 | |
| 			fsGroup:     &fsGroup,
 | |
| 			setupFunc: func(path string) error {
 | |
| 				filename := filepath.Join(path, "file.txt")
 | |
| 				file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0755)
 | |
| 				if err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 				file.Close()
 | |
| 
 | |
| 				symname := filepath.Join(path, "file_link.txt")
 | |
| 				err = os.Symlink(filename, symname)
 | |
| 				if err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 
 | |
| 				return nil
 | |
| 			},
 | |
| 			assertFunc: func(path string) error {
 | |
| 				symname := filepath.Join(path, "file_link.txt")
 | |
| 				if !verifyFileOwner(symname, currentUid, int(fsGroup)) {
 | |
| 					return fmt.Errorf("invalid owner on %s", symname)
 | |
| 				}
 | |
| 				return nil
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, test := range tests {
 | |
| 		t.Run(test.description, func(t *testing.T) {
 | |
| 			tmpDir, err := utiltesting.MkTmpdir("volume_linux_ownership")
 | |
| 			if err != nil {
 | |
| 				t.Fatalf("error creating temp dir: %v", err)
 | |
| 			}
 | |
| 
 | |
| 			defer os.RemoveAll(tmpDir)
 | |
| 
 | |
| 			err = test.setupFunc(tmpDir)
 | |
| 			if err != nil {
 | |
| 				t.Errorf("for %s error running setup with: %v", test.description, err)
 | |
| 			}
 | |
| 
 | |
| 			mounter := &localFakeMounter{path: tmpDir}
 | |
| 			always := v1.FSGroupChangeAlways
 | |
| 			err = SetVolumeOwnership(mounter, test.fsGroup, &always, nil)
 | |
| 			if err != nil {
 | |
| 				t.Errorf("for %s error changing ownership with: %v", test.description, err)
 | |
| 			}
 | |
| 			err = test.assertFunc(tmpDir)
 | |
| 			if err != nil {
 | |
| 				t.Errorf("for %s error verifying permissions with: %v", test.description, err)
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // verifyFileOwner checks if given path is owned by uid and gid.
 | |
| // It returns true if it is otherwise false.
 | |
| func verifyFileOwner(path string, uid, gid int) bool {
 | |
| 	info, err := os.Lstat(path)
 | |
| 	if err != nil {
 | |
| 		return false
 | |
| 	}
 | |
| 	stat, ok := info.Sys().(*syscall.Stat_t)
 | |
| 	if !ok || stat == nil {
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	if int(stat.Uid) != uid || int(stat.Gid) != gid {
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	return true
 | |
| }
 |