-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix handling of the volume directive #334
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
FROM gcr.io/google-appengine/debian9@sha256:1d6a9a6d106bd795098f60f4abb7083626354fa6735e81743c7f8cfca11259f0 | ||
RUN mkdir /foo | ||
RUN echo "hello" > /foo/hey | ||
VOLUME /foo/bar /tmp | ||
VOLUME /foo/bar /tmp /qux/quux | ||
ENV VOL /baz/bat | ||
VOLUME ["${VOL}"] | ||
RUN echo "hello again" > /tmp/hey |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
FROM gcr.io/google-appengine/debian9@sha256:1d6a9a6d106bd795098f60f4abb7083626354fa6735e81743c7f8cfca11259f0 | ||
VOLUME /foo1 | ||
RUN echo "hello" > /foo1/hello | ||
WORKDIR /foo1/bar | ||
ADD context/foo /foo1/foo | ||
COPY context/foo /foo1/foo2 | ||
RUN mkdir /bar1 | ||
VOLUME /foo2 | ||
VOLUME /foo3 | ||
RUN echo "bar2" | ||
VOLUME /foo4 | ||
RUN mkdir /bar3 | ||
VOLUME /foo5 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,7 +25,6 @@ import ( | |
"path/filepath" | ||
"syscall" | ||
|
||
"github.com/GoogleContainerTools/kaniko/pkg/constants" | ||
"github.com/GoogleContainerTools/kaniko/pkg/util" | ||
"github.com/sirupsen/logrus" | ||
) | ||
|
@@ -84,21 +83,6 @@ func (s *Snapshotter) TakeSnapshotFS() ([]byte, error) { | |
return contents, err | ||
} | ||
|
||
func shouldSnapshot(file string, snapshottedFiles map[string]bool) (bool, error) { | ||
if val, ok := snapshottedFiles[file]; ok && val { | ||
return false, nil | ||
} | ||
whitelisted, err := util.CheckWhitelist(file) | ||
if err != nil { | ||
return false, fmt.Errorf("Error checking for %s in whitelist: %s", file, err) | ||
} | ||
if whitelisted && !isBuildFile(file) { | ||
logrus.Infof("Not adding %s to layer, as it's whitelisted", file) | ||
return false, nil | ||
} | ||
return true, nil | ||
} | ||
|
||
// snapshotFiles creates a snapshot (tar) and adds the specified files. | ||
// It will not add files which are whitelisted. | ||
func (s *Snapshotter) snapshotFiles(f io.Writer, files []string) (bool, error) { | ||
|
@@ -123,11 +107,7 @@ func (s *Snapshotter) snapshotFiles(f io.Writer, files []string) (bool, error) { | |
} | ||
for _, file := range parentDirs { | ||
file = filepath.Clean(file) | ||
shouldSnapshot, err := shouldSnapshot(file, snapshottedFiles) | ||
if err != nil { | ||
return false, fmt.Errorf("Error checking if parent dir %s can be snapshotted: %s", file, err) | ||
} | ||
if !shouldSnapshot { | ||
if val, ok := snapshottedFiles[file]; ok && val { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why did you remove shouldSnapshot() and isBuildFile()? I think those functions are necessary so that kaniko is able to build itself. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For the specific files cases (ADD, COPY, WORKDIR) that call The key thing to consider about this change is, is it ok to not check the whitelist at all for specific files cases, directives ADD, COPY and WORKDIR? (VOLUME is included too, but only if it's snapshot with another specific file case and not a system-wide snapshot.) Up to now, Kaniko has prevented adding whitelisted files and directories with those directives. It doesn't seem to be necessary to me. The main purpose of the whitelist seems to be to ignore files and directories when there is no way to know if the file was mounted or came from the base image. If a Dockerfile is explicitly trying to add files and directories with ADD, COPY and WORKDIR, I don't think there is a reason to prevent that. Let me know if you think I've missed something! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @peter-evans that makes sense, thanks for explaining! |
||
continue | ||
} | ||
snapshottedFiles[file] = true | ||
|
@@ -148,35 +128,22 @@ func (s *Snapshotter) snapshotFiles(f io.Writer, files []string) (bool, error) { | |
// Next add the files themselves to the tar | ||
for _, file := range files { | ||
file = filepath.Clean(file) | ||
shouldSnapshot, err := shouldSnapshot(file, snapshottedFiles) | ||
if err != nil { | ||
return false, fmt.Errorf("Error checking if file %s can be snapshotted: %s", file, err) | ||
} | ||
if !shouldSnapshot { | ||
if val, ok := snapshottedFiles[file]; ok && val { | ||
continue | ||
} | ||
snapshottedFiles[file] = true | ||
|
||
if err = s.l.Add(file); err != nil { | ||
if err := s.l.Add(file); err != nil { | ||
return false, fmt.Errorf("Unable to add file %s to layered map: %s", file, err) | ||
} | ||
if err = t.AddFileToTar(file); err != nil { | ||
if err := t.AddFileToTar(file); err != nil { | ||
return false, fmt.Errorf("Error adding file %s to tar: %s", file, err) | ||
} | ||
filesAdded = true | ||
} | ||
return filesAdded, nil | ||
} | ||
|
||
func isBuildFile(file string) bool { | ||
for _, buildFile := range constants.KanikoBuildFiles { | ||
if file == buildFile { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
// shapShotFS creates a snapshot (tar) of all files in the system which are not | ||
// whitelisted and which have changed. | ||
func (s *Snapshotter) snapShotFS(f io.Writer) (bool, error) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,17 +34,30 @@ import ( | |
"github.com/sirupsen/logrus" | ||
) | ||
|
||
var whitelist = []string{ | ||
"/kaniko", | ||
// /var/run is a special case. It's common to mount in /var/run/docker.sock or something similar | ||
// which leads to a special mount on the /var/run/docker.sock file itself, but the directory to exist | ||
// in the image with no way to tell if it came from the base image or not. | ||
"/var/run", | ||
// similarly, we whitelist /etc/mtab, since there is no way to know if the file was mounted or came | ||
// from the base image | ||
"/etc/mtab", | ||
type WhitelistEntry struct { | ||
Path string | ||
PrefixMatchOnly bool | ||
} | ||
|
||
var whitelist = []WhitelistEntry{ | ||
{ | ||
Path: "/kaniko", | ||
PrefixMatchOnly: false, | ||
}, | ||
{ | ||
// /var/run is a special case. It's common to mount in /var/run/docker.sock or something similar | ||
// which leads to a special mount on the /var/run/docker.sock file itself, but the directory to exist | ||
// in the image with no way to tell if it came from the base image or not. | ||
Path: "/var/run", | ||
PrefixMatchOnly: false, | ||
}, | ||
{ | ||
// similarly, we whitelist /etc/mtab, since there is no way to know if the file was mounted or came | ||
// from the base image | ||
Path: "/etc/mtab", | ||
PrefixMatchOnly: false, | ||
}, | ||
} | ||
var volumeWhitelist = []string{} | ||
|
||
// GetFSFromImage extracts the layers of img to root | ||
// It returns a list of all files extracted | ||
|
@@ -136,13 +149,13 @@ func DeleteFilesystem() error { | |
func ChildDirInWhitelist(path, directory string) bool { | ||
for _, d := range constants.KanikoBuildFiles { | ||
dirPath := filepath.Join(directory, d) | ||
if HasFilepathPrefix(dirPath, path) { | ||
if HasFilepathPrefix(dirPath, path, false) { | ||
return true | ||
} | ||
} | ||
for _, d := range whitelist { | ||
dirPath := filepath.Join(directory, d) | ||
if HasFilepathPrefix(dirPath, path) { | ||
dirPath := filepath.Join(directory, d.Path) | ||
if HasFilepathPrefix(dirPath, path, d.PrefixMatchOnly) { | ||
return true | ||
} | ||
} | ||
|
@@ -266,7 +279,7 @@ func CheckWhitelist(path string) (bool, error) { | |
return false, err | ||
} | ||
for _, wl := range whitelist { | ||
if HasFilepathPrefix(abs, wl) { | ||
if HasFilepathPrefix(abs, wl.Path, wl.PrefixMatchOnly) { | ||
return true, nil | ||
} | ||
} | ||
|
@@ -278,7 +291,7 @@ func checkWhitelistRoot(root string) bool { | |
return false | ||
} | ||
for _, wl := range whitelist { | ||
if HasFilepathPrefix(root, wl) { | ||
if HasFilepathPrefix(root, wl.Path, wl.PrefixMatchOnly) { | ||
return true | ||
} | ||
} | ||
|
@@ -291,7 +304,7 @@ func checkWhitelistRoot(root string) bool { | |
// (1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11) | ||
// Where (5) is the mount point relative to the process's root | ||
// From: https://www.kernel.org/doc/Documentation/filesystems/proc.txt | ||
func fileSystemWhitelist(path string) ([]string, error) { | ||
func fileSystemWhitelist(path string) ([]WhitelistEntry, error) { | ||
f, err := os.Open(path) | ||
if err != nil { | ||
return nil, err | ||
|
@@ -314,7 +327,10 @@ func fileSystemWhitelist(path string) ([]string, error) { | |
} | ||
if lineArr[4] != constants.RootDir { | ||
logrus.Debugf("Appending %s from line: %s", lineArr[4], line) | ||
whitelist = append(whitelist, lineArr[4]) | ||
whitelist = append(whitelist, WhitelistEntry{ | ||
Path: lineArr[4], | ||
PrefixMatchOnly: false, | ||
}) | ||
} | ||
if err == io.EOF { | ||
logrus.Debugf("Reached end of file %s", path) | ||
|
@@ -337,7 +353,7 @@ func RelativeFiles(fp string, root string) ([]string, error) { | |
if err != nil { | ||
return err | ||
} | ||
if whitelisted && !HasFilepathPrefix(path, root) { | ||
if whitelisted && !HasFilepathPrefix(path, root, false) { | ||
return nil | ||
} | ||
if err != nil { | ||
|
@@ -400,22 +416,15 @@ func CreateFile(path string, reader io.Reader, perm os.FileMode, uid uint32, gid | |
return dest.Chown(int(uid), int(gid)) | ||
} | ||
|
||
// AddPathToVolumeWhitelist adds the given path to the volume whitelist | ||
// It will get snapshotted when the VOLUME command is run then ignored | ||
// for subsequent commands. | ||
func AddPathToVolumeWhitelist(path string) error { | ||
logrus.Infof("adding %s to volume whitelist", path) | ||
volumeWhitelist = append(volumeWhitelist, path) | ||
return nil | ||
} | ||
|
||
// MoveVolumeWhitelistToWhitelist copies over all directories that were volume mounted | ||
// in this step to be whitelisted for all subsequent docker commands. | ||
func MoveVolumeWhitelistToWhitelist() error { | ||
if len(volumeWhitelist) > 0 { | ||
whitelist = append(whitelist, volumeWhitelist...) | ||
volumeWhitelist = []string{} | ||
} | ||
// AddVolumePathToWhitelist adds the given path to the whitelist with | ||
// PrefixMatchOnly set to true. Snapshotting will ignore paths prefixed | ||
// with the volume, but the volume itself will not be ignored. | ||
func AddVolumePathToWhitelist(path string) error { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In what case would we need to whitelist volumes? I'm not totally sure we need to whitelist them at all. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, volumes do need to be whitelisted. This is because any file-system changes inside a volume should not be added to the snapshot. Consider this example:
After the volume There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After I wrote the above comment I went back to double check. Turns out it's more complicated still. See the following example built with Docker and the container-diff analysis.
File-system changes within volumes are whitelisted during system-wide snapshots. None of the At the moment, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I removed checking the whitelist for |
||
logrus.Infof("adding volume %s to whitelist", path) | ||
whitelist = append(whitelist, WhitelistEntry{ | ||
Path: path, | ||
PrefixMatchOnly: true, | ||
}) | ||
return nil | ||
} | ||
|
||
|
@@ -515,7 +524,7 @@ func CopyFile(src, dest string) error { | |
} | ||
|
||
// HasFilepathPrefix checks if the given file path begins with prefix | ||
func HasFilepathPrefix(path, prefix string) bool { | ||
func HasFilepathPrefix(path, prefix string, prefixMatchOnly bool) bool { | ||
path = filepath.Clean(path) | ||
prefix = filepath.Clean(prefix) | ||
pathArray := strings.Split(path, "/") | ||
|
@@ -524,6 +533,9 @@ func HasFilepathPrefix(path, prefix string) bool { | |
if len(pathArray) < len(prefixArray) { | ||
return false | ||
} | ||
if prefixMatchOnly && len(pathArray) == len(prefixArray) { | ||
return false | ||
} | ||
for index := range prefixArray { | ||
if prefixArray[index] == pathArray[index] { | ||
continue | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a comment about this change. This is a bug I found where it is not respecting multiple volumes created by the same
VOLUME
directive.The
Dockerfile_test_volume
test did not catch it becausetmp
is already a directory and sov.snapshotFiles
did not get overridden. I added an extra volume for the directive to create to provide test coverage.