k8s.io/kubernetes@v1.29.3/pkg/volume/volume_linux.go (about) 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 }