...

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

Documentation: k8s.io/kubernetes/pkg/volume

     1  //go:build linux
     2  // +build linux
     3  
     4  /*
     5  Copyright 2016 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  	"path/filepath"
    24  	"syscall"
    25  
    26  	"os"
    27  	"time"
    28  
    29  	v1 "k8s.io/api/core/v1"
    30  	"k8s.io/klog/v2"
    31  	"k8s.io/kubernetes/pkg/volume/util/types"
    32  )
    33  
    34  const (
    35  	rwMask   = os.FileMode(0660)
    36  	roMask   = os.FileMode(0440)
    37  	execMask = os.FileMode(0110)
    38  )
    39  
    40  // SetVolumeOwnership modifies the given volume to be owned by
    41  // fsGroup, and sets SetGid so that newly created files are owned by
    42  // fsGroup. If fsGroup is nil nothing is done.
    43  func SetVolumeOwnership(mounter Mounter, dir string, fsGroup *int64, fsGroupChangePolicy *v1.PodFSGroupChangePolicy, completeFunc func(types.CompleteFuncParam)) error {
    44  	if fsGroup == nil {
    45  		return nil
    46  	}
    47  
    48  	timer := time.AfterFunc(30*time.Second, func() {
    49  		klog.Warningf("Setting volume ownership for %s and fsGroup set. If the volume has a lot of files then setting volume ownership could be slow, see https://github.com/kubernetes/kubernetes/issues/69699", dir)
    50  	})
    51  	defer timer.Stop()
    52  
    53  	if skipPermissionChange(mounter, dir, fsGroup, fsGroupChangePolicy) {
    54  		klog.V(3).InfoS("Skipping permission and ownership change for volume", "path", dir)
    55  		return nil
    56  	}
    57  
    58  	err := walkDeep(dir, func(path string, info os.FileInfo, err error) error {
    59  		if err != nil {
    60  			return err
    61  		}
    62  		return changeFilePermission(path, fsGroup, mounter.GetAttributes().ReadOnly, info)
    63  	})
    64  	if completeFunc != nil {
    65  		completeFunc(types.CompleteFuncParam{
    66  			Err: &err,
    67  		})
    68  	}
    69  	return err
    70  }
    71  
    72  func changeFilePermission(filename string, fsGroup *int64, readonly bool, info os.FileInfo) error {
    73  	err := os.Lchown(filename, -1, int(*fsGroup))
    74  	if err != nil {
    75  		klog.ErrorS(err, "Lchown failed", "path", filename)
    76  	}
    77  
    78  	// chmod passes through to the underlying file for symlinks.
    79  	// Symlinks have a mode of 777 but this really doesn't mean anything.
    80  	// The permissions of the underlying file are what matter.
    81  	// However, if one reads the mode of a symlink then chmods the symlink
    82  	// with that mode, it changes the mode of the underlying file, overridden
    83  	// the defaultMode and permissions initialized by the volume plugin, which
    84  	// is not what we want; thus, we skip chmod for symlinks.
    85  	if info.Mode()&os.ModeSymlink != 0 {
    86  		return nil
    87  	}
    88  
    89  	mask := rwMask
    90  	if readonly {
    91  		mask = roMask
    92  	}
    93  
    94  	if info.IsDir() {
    95  		mask |= os.ModeSetgid
    96  		mask |= execMask
    97  	}
    98  
    99  	err = os.Chmod(filename, info.Mode()|mask)
   100  	if err != nil {
   101  		klog.ErrorS(err, "chmod failed", "path", filename)
   102  	}
   103  
   104  	return nil
   105  }
   106  
   107  func skipPermissionChange(mounter Mounter, dir string, fsGroup *int64, fsGroupChangePolicy *v1.PodFSGroupChangePolicy) bool {
   108  	if fsGroupChangePolicy == nil || *fsGroupChangePolicy != v1.FSGroupChangeOnRootMismatch {
   109  		klog.V(4).InfoS("Perform recursive ownership change for directory", "path", dir)
   110  		return false
   111  	}
   112  	return !requiresPermissionChange(dir, fsGroup, mounter.GetAttributes().ReadOnly)
   113  }
   114  
   115  func requiresPermissionChange(rootDir string, fsGroup *int64, readonly bool) bool {
   116  	fsInfo, err := os.Stat(rootDir)
   117  	if err != nil {
   118  		klog.ErrorS(err, "Performing recursive ownership change on rootDir because reading permissions of root volume failed", "path", rootDir)
   119  		return true
   120  	}
   121  	stat, ok := fsInfo.Sys().(*syscall.Stat_t)
   122  	if !ok || stat == nil {
   123  		klog.ErrorS(nil, "Performing recursive ownership change on rootDir because reading permissions of root volume failed", "path", rootDir)
   124  		return true
   125  	}
   126  
   127  	if int(stat.Gid) != int(*fsGroup) {
   128  		klog.V(4).InfoS("Expected group ownership of volume did not match with Gid", "path", rootDir, "GID", stat.Gid)
   129  		return true
   130  	}
   131  	unixPerms := rwMask
   132  
   133  	if readonly {
   134  		unixPerms = roMask
   135  	}
   136  
   137  	// if rootDir is not a directory then we should apply permission change anyways
   138  	if !fsInfo.IsDir() {
   139  		return true
   140  	}
   141  	unixPerms |= execMask
   142  	filePerm := fsInfo.Mode().Perm()
   143  
   144  	// We need to check if actual permissions of root directory is a superset of permissions required by unixPerms.
   145  	// This is done by checking if permission bits expected in unixPerms is set in actual permissions of the directory.
   146  	// We use bitwise AND operation to check set bits. For example:
   147  	//     unixPerms: 770, filePerms: 775 : 770&775 = 770 (perms on directory is a superset)
   148  	//     unixPerms: 770, filePerms: 770 : 770&770 = 770 (perms on directory is a superset)
   149  	//     unixPerms: 770, filePerms: 750 : 770&750 = 750 (perms on directory is NOT a superset)
   150  	// We also need to check if setgid bits are set in permissions of the directory.
   151  	if (unixPerms&filePerm != unixPerms) || (fsInfo.Mode()&os.ModeSetgid == 0) {
   152  		klog.V(4).InfoS("Performing recursive ownership change on rootDir because of mismatching mode", "path", rootDir)
   153  		return true
   154  	}
   155  	return false
   156  }
   157  
   158  // readDirNames reads the directory named by dirname and returns
   159  // a list of directory entries.
   160  // We are not using filepath.readDirNames because we do not want to sort files found in a directory before changing
   161  // permissions for performance reasons.
   162  func readDirNames(dirname string) ([]string, error) {
   163  	f, err := os.Open(dirname)
   164  	if err != nil {
   165  		return nil, err
   166  	}
   167  	names, err := f.Readdirnames(-1)
   168  	f.Close()
   169  	if err != nil {
   170  		return nil, err
   171  	}
   172  	return names, nil
   173  }
   174  
   175  // walkDeep can be used to traverse directories and has two minor differences
   176  // from filepath.Walk:
   177  //   - List of files/dirs is not sorted for performance reasons
   178  //   - callback walkFunc is invoked on root directory after visiting children dirs and files
   179  func walkDeep(root string, walkFunc filepath.WalkFunc) error {
   180  	info, err := os.Lstat(root)
   181  	if err != nil {
   182  		return walkFunc(root, nil, err)
   183  	}
   184  	return walk(root, info, walkFunc)
   185  }
   186  
   187  func walk(path string, info os.FileInfo, walkFunc filepath.WalkFunc) error {
   188  	if !info.IsDir() {
   189  		return walkFunc(path, info, nil)
   190  	}
   191  	names, err := readDirNames(path)
   192  	if err != nil {
   193  		return err
   194  	}
   195  	for _, name := range names {
   196  		filename := filepath.Join(path, name)
   197  		fileInfo, err := os.Lstat(filename)
   198  		if err != nil {
   199  			if err := walkFunc(filename, fileInfo, err); err != nil {
   200  				return err
   201  			}
   202  		} else {
   203  			err = walk(filename, fileInfo, walkFunc)
   204  			if err != nil {
   205  				return err
   206  			}
   207  		}
   208  	}
   209  	return walkFunc(path, info, nil)
   210  }
   211  

View as plain text