2021-12-08 19:20:47 +05:30
//go:build linux
2019-05-31 15:15:11 +05:30
// +build linux
/ *
Copyright 2016 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 (
2025-04-28 22:16:28 +00:00
"context"
"fmt"
2019-05-31 15:15:11 +05:30
"path/filepath"
2025-04-28 22:16:28 +00:00
"strings"
2019-05-31 15:15:11 +05:30
"syscall"
"os"
2020-12-17 17:58:29 +05:30
"time"
2019-05-31 15:15:11 +05:30
2020-04-14 12:34:33 +05:30
v1 "k8s.io/api/core/v1"
2025-04-28 22:16:28 +00:00
"k8s.io/client-go/tools/record"
2020-12-17 17:58:29 +05:30
"k8s.io/klog/v2"
2025-04-28 22:16:28 +00:00
"k8s.io/kubernetes/pkg/kubelet/events"
2021-06-25 10:29:51 +05:30
"k8s.io/kubernetes/pkg/volume/util/types"
2019-05-31 15:15:11 +05:30
)
const (
rwMask = os . FileMode ( 0660 )
roMask = os . FileMode ( 0440 )
execMask = os . FileMode ( 0110 )
)
2025-04-28 22:16:28 +00:00
var (
// function that will be used for changing file permissions on linux
// mainly stored here as a variable so as it can replaced in tests
filePermissionChangeFunc = changeFilePermission
progressReportDuration = 60 * time . Second
firstEventReportDuration = 30 * time . Second
)
// NewVolumeOwnership returns an interface that can be used to recursively change volume permissions and ownership
func NewVolumeOwnership ( mounter Mounter , dir string , fsGroup * int64 , fsGroupChangePolicy * v1 . PodFSGroupChangePolicy , completeFunc func ( types . CompleteFuncParam ) ) * VolumeOwnership {
vo := & VolumeOwnership {
mounter : mounter ,
dir : dir ,
fsGroup : fsGroup ,
fsGroupChangePolicy : fsGroupChangePolicy ,
completionCallback : completeFunc ,
}
vo . fileCounter . Store ( 0 )
return vo
}
func ( vo * VolumeOwnership ) AddProgressNotifier ( pod * v1 . Pod , recorder record . EventRecorder ) * VolumeOwnership {
vo . pod = pod
vo . recorder = recorder
return vo
}
func ( vo * VolumeOwnership ) ChangePermissions ( ) error {
if vo . fsGroup == nil {
2019-05-31 15:15:11 +05:30
return nil
}
2025-04-28 22:16:28 +00:00
if skipPermissionChange ( vo . mounter , vo . dir , vo . fsGroup , vo . fsGroupChangePolicy ) {
klog . V ( 3 ) . InfoS ( "Skipping permission and ownership change for volume" , "path" , vo . dir )
return nil
}
ctx , cancel := context . WithCancel ( context . Background ( ) )
defer cancel ( )
timer := time . AfterFunc ( firstEventReportDuration , func ( ) {
vo . initiateProgressMonitor ( ctx )
2020-12-17 17:58:29 +05:30
} )
defer timer . Stop ( )
2020-01-14 16:08:55 +05:30
2025-04-28 22:16:28 +00:00
return vo . changePermissionsRecursively ( )
}
func ( vo * VolumeOwnership ) initiateProgressMonitor ( ctx context . Context ) {
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" , vo . dir )
if vo . pod != nil {
go vo . monitorProgress ( ctx )
2020-04-14 12:34:33 +05:30
}
2025-04-28 22:16:28 +00:00
}
2020-04-14 12:34:33 +05:30
2025-04-28 22:16:28 +00:00
func ( vo * VolumeOwnership ) changePermissionsRecursively ( ) error {
err := walkDeep ( vo . dir , func ( path string , info os . FileInfo , err error ) error {
2019-05-31 15:15:11 +05:30
if err != nil {
return err
}
2025-04-28 22:16:28 +00:00
vo . fileCounter . Add ( 1 )
return filePermissionChangeFunc ( path , vo . fsGroup , vo . mounter . GetAttributes ( ) . ReadOnly , info )
2020-04-14 12:34:33 +05:30
} )
2025-04-28 22:16:28 +00:00
if vo . completionCallback != nil {
vo . completionCallback ( types . CompleteFuncParam {
2021-06-25 10:29:51 +05:30
Err : & err ,
} )
2020-12-17 17:58:29 +05:30
}
return err
2020-04-14 12:34:33 +05:30
}
2019-05-31 15:15:11 +05:30
2025-04-28 22:16:28 +00:00
func ( vo * VolumeOwnership ) monitorProgress ( ctx context . Context ) {
dirName := getDirnameToReport ( vo . dir , string ( vo . pod . UID ) )
msg := fmt . Sprintf ( "Setting volume ownership for %s is taking longer than expected, consider using OnRootMismatch - https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#configure-volume-permission-and-ownership-change-policy-for-pods" , dirName )
vo . recorder . Event ( vo . pod , v1 . EventTypeWarning , events . VolumePermissionChangeInProgress , msg )
ticker := time . NewTicker ( progressReportDuration )
defer ticker . Stop ( )
for {
select {
case <- ctx . Done ( ) :
return
case <- ticker . C :
vo . logWarning ( )
}
}
}
// report everything after podUID in dir string, including podUID
func getDirnameToReport ( dir , podUID string ) string {
podUIDIndex := strings . Index ( dir , podUID )
if podUIDIndex == - 1 {
return dir
}
return dir [ podUIDIndex : ]
}
func ( vo * VolumeOwnership ) logWarning ( ) {
dirName := getDirnameToReport ( vo . dir , string ( vo . pod . UID ) )
msg := fmt . Sprintf ( "Setting volume ownership for %s, processed %d files." , dirName , vo . fileCounter . Load ( ) )
klog . Warning ( msg )
vo . recorder . Event ( vo . pod , v1 . EventTypeWarning , events . VolumePermissionChangeInProgress , msg )
}
2020-04-14 12:34:33 +05:30
func changeFilePermission ( filename string , fsGroup * int64 , readonly bool , info os . FileInfo ) error {
2021-06-25 10:29:51 +05:30
err := os . Lchown ( filename , - 1 , int ( * fsGroup ) )
if err != nil {
2021-08-09 12:49:24 +05:30
klog . ErrorS ( err , "Lchown failed" , "path" , filename )
2021-06-25 10:29:51 +05:30
}
// chmod passes through to the underlying file for symlinks.
2020-04-14 12:34:33 +05:30
// Symlinks have a mode of 777 but this really doesn't mean anything.
// The permissions of the underlying file are what matter.
// However, if one reads the mode of a symlink then chmods the symlink
// with that mode, it changes the mode of the underlying file, overridden
// the defaultMode and permissions initialized by the volume plugin, which
2021-06-25 10:29:51 +05:30
// is not what we want; thus, we skip chmod for symlinks.
2020-04-14 12:34:33 +05:30
if info . Mode ( ) & os . ModeSymlink != 0 {
return nil
}
2019-05-31 15:15:11 +05:30
2020-04-14 12:34:33 +05:30
mask := rwMask
if readonly {
mask = roMask
}
if info . IsDir ( ) {
mask |= os . ModeSetgid
mask |= execMask
}
2019-05-31 15:15:11 +05:30
2020-04-14 12:34:33 +05:30
err = os . Chmod ( filename , info . Mode ( ) | mask )
if err != nil {
2022-05-05 08:17:06 +05:30
klog . ErrorS ( err , "chmod failed" , "path" , filename )
2020-04-14 12:34:33 +05:30
}
return nil
}
2023-05-29 21:03:29 +00:00
func skipPermissionChange ( mounter Mounter , dir string , fsGroup * int64 , fsGroupChangePolicy * v1 . PodFSGroupChangePolicy ) bool {
2020-04-14 12:34:33 +05:30
if fsGroupChangePolicy == nil || * fsGroupChangePolicy != v1 . FSGroupChangeOnRootMismatch {
2021-08-09 12:49:24 +05:30
klog . V ( 4 ) . InfoS ( "Perform recursive ownership change for directory" , "path" , dir )
2020-04-14 12:34:33 +05:30
return false
}
2023-05-29 21:03:29 +00:00
return ! requiresPermissionChange ( dir , fsGroup , mounter . GetAttributes ( ) . ReadOnly )
2020-04-14 12:34:33 +05:30
}
func requiresPermissionChange ( rootDir string , fsGroup * int64 , readonly bool ) bool {
fsInfo , err := os . Stat ( rootDir )
if err != nil {
2021-08-09 12:49:24 +05:30
klog . ErrorS ( err , "Performing recursive ownership change on rootDir because reading permissions of root volume failed" , "path" , rootDir )
2020-04-14 12:34:33 +05:30
return true
}
stat , ok := fsInfo . Sys ( ) . ( * syscall . Stat_t )
if ! ok || stat == nil {
2021-08-09 12:49:24 +05:30
klog . ErrorS ( nil , "Performing recursive ownership change on rootDir because reading permissions of root volume failed" , "path" , rootDir )
2020-04-14 12:34:33 +05:30
return true
}
if int ( stat . Gid ) != int ( * fsGroup ) {
2021-08-09 12:49:24 +05:30
klog . V ( 4 ) . InfoS ( "Expected group ownership of volume did not match with Gid" , "path" , rootDir , "GID" , stat . Gid )
2020-04-14 12:34:33 +05:30
return true
}
unixPerms := rwMask
if readonly {
unixPerms = roMask
}
// if rootDir is not a directory then we should apply permission change anyways
if ! fsInfo . IsDir ( ) {
return true
}
unixPerms |= execMask
filePerm := fsInfo . Mode ( ) . Perm ( )
// We need to check if actual permissions of root directory is a superset of permissions required by unixPerms.
// This is done by checking if permission bits expected in unixPerms is set in actual permissions of the directory.
// We use bitwise AND operation to check set bits. For example:
// unixPerms: 770, filePerms: 775 : 770&775 = 770 (perms on directory is a superset)
// unixPerms: 770, filePerms: 770 : 770&770 = 770 (perms on directory is a superset)
// unixPerms: 770, filePerms: 750 : 770&750 = 750 (perms on directory is NOT a superset)
// We also need to check if setgid bits are set in permissions of the directory.
if ( unixPerms & filePerm != unixPerms ) || ( fsInfo . Mode ( ) & os . ModeSetgid == 0 ) {
2021-08-09 12:49:24 +05:30
klog . V ( 4 ) . InfoS ( "Performing recursive ownership change on rootDir because of mismatching mode" , "path" , rootDir )
2020-04-14 12:34:33 +05:30
return true
}
return false
}
// readDirNames reads the directory named by dirname and returns
// a list of directory entries.
// We are not using filepath.readDirNames because we do not want to sort files found in a directory before changing
// permissions for performance reasons.
func readDirNames ( dirname string ) ( [ ] string , error ) {
f , err := os . Open ( dirname )
if err != nil {
return nil , err
}
names , err := f . Readdirnames ( - 1 )
f . Close ( )
if err != nil {
return nil , err
}
return names , nil
}
// walkDeep can be used to traverse directories and has two minor differences
// from filepath.Walk:
// - List of files/dirs is not sorted for performance reasons
// - callback walkFunc is invoked on root directory after visiting children dirs and files
func walkDeep ( root string , walkFunc filepath . WalkFunc ) error {
info , err := os . Lstat ( root )
if err != nil {
return walkFunc ( root , nil , err )
}
return walk ( root , info , walkFunc )
}
func walk ( path string , info os . FileInfo , walkFunc filepath . WalkFunc ) error {
if ! info . IsDir ( ) {
return walkFunc ( path , info , nil )
}
names , err := readDirNames ( path )
if err != nil {
return err
}
for _ , name := range names {
filename := filepath . Join ( path , name )
fileInfo , err := os . Lstat ( filename )
2019-05-31 15:15:11 +05:30
if err != nil {
2020-04-14 12:34:33 +05:30
if err := walkFunc ( filename , fileInfo , err ) ; err != nil {
return err
}
} else {
err = walk ( filename , fileInfo , walkFunc )
if err != nil {
return err
}
2019-05-31 15:15:11 +05:30
}
2020-04-14 12:34:33 +05:30
}
return walkFunc ( path , info , nil )
2019-05-31 15:15:11 +05:30
}