163 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			163 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			Go
		
	
	
	
| /*
 | |
| Copyright 2020 Google LLC
 | |
| 
 | |
| 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 filesystem
 | |
| 
 | |
| import (
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 
 | |
| 	"github.com/GoogleContainerTools/kaniko/pkg/util"
 | |
| 	"github.com/pkg/errors"
 | |
| 	"github.com/sirupsen/logrus"
 | |
| )
 | |
| 
 | |
| // ResolvePaths takes a slice of file paths and a slice of whitelist entries. It resolve each
 | |
| // file path according to a set of rules and then returns a slice of resolved paths or error.
 | |
| // File paths are resolved according to the following rules:
 | |
| // * If path is whitelisted, skip it.
 | |
| // * If path is a symlink, resolve it's ancestor link and add it to the output set.
 | |
| // * If path is a symlink, resolve it's target. If the target is not whitelisted add it to the
 | |
| // output set.
 | |
| // * Add all ancestors of each path to the output set.
 | |
| func ResolvePaths(paths []string, wl []util.WhitelistEntry) (pathsToAdd []string, err error) {
 | |
| 	logrus.Info("Resolving paths")
 | |
| 
 | |
| 	fileSet := make(map[string]bool)
 | |
| 
 | |
| 	for _, f := range paths {
 | |
| 		// If the given path is part of the whitelist ignore it
 | |
| 		if util.IsInProvidedWhitelist(f, wl) {
 | |
| 			logrus.Debugf("path %s is whitelisted, ignoring it", f)
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		link, e := resolveSymlinkAncestor(f)
 | |
| 		if e != nil {
 | |
| 			err = e
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		if f != link {
 | |
| 			logrus.Tracef("updated link %s to %s", f, link)
 | |
| 		}
 | |
| 
 | |
| 		if !fileSet[link] {
 | |
| 			pathsToAdd = append(pathsToAdd, link)
 | |
| 		}
 | |
| 		fileSet[link] = true
 | |
| 
 | |
| 		var evaled string
 | |
| 
 | |
| 		// If the path is a symlink we need to also consider the target of that
 | |
| 		// link
 | |
| 		evaled, err = filepath.EvalSymlinks(f)
 | |
| 		if err != nil {
 | |
| 			if !os.IsNotExist(err) {
 | |
| 				logrus.Errorf("couldn't eval %s with link %s", f, link)
 | |
| 				return
 | |
| 			}
 | |
| 
 | |
| 			logrus.Debugf("symlink path %s, target does not exist", f)
 | |
| 		}
 | |
| 
 | |
| 		// If the given path is a symlink and the target is part of the whitelist
 | |
| 		// ignore the target
 | |
| 		if util.IsInProvidedWhitelist(evaled, wl) {
 | |
| 			logrus.Debugf("path %s is whitelisted, ignoring it", evaled)
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		if !fileSet[evaled] {
 | |
| 			pathsToAdd = append(pathsToAdd, evaled)
 | |
| 		}
 | |
| 		fileSet[evaled] = true
 | |
| 	}
 | |
| 
 | |
| 	// Also add parent directories to keep the permission of them correctly.
 | |
| 	pathsToAdd = filesWithParentDirs(pathsToAdd)
 | |
| 
 | |
| 	return
 | |
| }
 | |
| 
 | |
| // filesWithParentDirs returns every ancestor path for each provided file path.
 | |
| // I.E. /foo/bar/baz/boom.txt => [/, /foo, /foo/bar, /foo/bar/baz, /foo/bar/baz/boom.txt]
 | |
| func filesWithParentDirs(files []string) []string {
 | |
| 	filesSet := map[string]bool{}
 | |
| 
 | |
| 	for _, file := range files {
 | |
| 		file = filepath.Clean(file)
 | |
| 		filesSet[file] = true
 | |
| 
 | |
| 		for _, dir := range util.ParentDirectories(file) {
 | |
| 			dir = filepath.Clean(dir)
 | |
| 			filesSet[dir] = true
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	newFiles := []string{}
 | |
| 	for file := range filesSet {
 | |
| 		newFiles = append(newFiles, file)
 | |
| 	}
 | |
| 
 | |
| 	return newFiles
 | |
| }
 | |
| 
 | |
| // resolveSymlinkAncestor returns the ancestor link of the provided symlink path or returns the
 | |
| // the path if it is not a link. The ancestor link is the filenode whose type is a Symlink.
 | |
| // E.G /baz/boom/bar.txt links to /usr/bin/bar.txt but /baz/boom/bar.txt itself is not a link.
 | |
| // Instead /bar/boom is actually a link to /usr/bin. In this case resolveSymlinkAncestor would
 | |
| // return /bar/boom.
 | |
| func resolveSymlinkAncestor(path string) (string, error) {
 | |
| 	if !filepath.IsAbs(path) {
 | |
| 		return "", errors.New("dest path must be abs")
 | |
| 	}
 | |
| 
 | |
| 	last := ""
 | |
| 	newPath := filepath.Clean(path)
 | |
| 
 | |
| loop:
 | |
| 	for newPath != "/" {
 | |
| 		fi, err := os.Lstat(newPath)
 | |
| 		if err != nil {
 | |
| 			return "", errors.Wrap(err, "failed to lstat")
 | |
| 		}
 | |
| 
 | |
| 		if util.IsSymlink(fi) {
 | |
| 			last = filepath.Base(newPath)
 | |
| 			newPath = filepath.Dir(newPath)
 | |
| 		} else {
 | |
| 			// Even if the filenode pointed to by newPath is a regular file,
 | |
| 			// one of its ancestors could be a symlink. We call filepath.EvalSymlinks
 | |
| 			// to test whether there are any links in the path. If the output of
 | |
| 			// EvalSymlinks is different than the input we know one of the nodes in the
 | |
| 			// the path is a link.
 | |
| 			target, err := filepath.EvalSymlinks(newPath)
 | |
| 			if err != nil {
 | |
| 				return "", err
 | |
| 			}
 | |
| 			if target != newPath {
 | |
| 				last = filepath.Base(newPath)
 | |
| 				newPath = filepath.Dir(newPath)
 | |
| 			} else {
 | |
| 				break loop
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	newPath = filepath.Join(newPath, last)
 | |
| 	return filepath.Clean(newPath), nil
 | |
| }
 |