...

Source file src/k8s.io/kubernetes/pkg/volume/volume_linux_test.go

Documentation: k8s.io/kubernetes/pkg/volume

     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  }
   472  

View as plain text