Skip to content

fix: ignore file ownership for copy from context #29

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

Merged
merged 18 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkg/commands/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func (a *AddCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.Bui
chmod = fs.FileMode(0o600)
}

uid, gid, err := util.GetUserGroup(a.cmd.Chown, replacementEnvs)
uid, gid, err := util.GetUserGroup(a.cmd.Chown, replacementEnvs, "")
if err != nil {
return errors.Wrap(err, "getting user group from chown")
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/commands/copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func (c *CopyCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.Bu
}

replacementEnvs := buildArgs.ReplacementEnvs(config.Env)
uid, gid, err := getUserGroup(c.cmd.Chown, replacementEnvs)
uid, gid, err := getUserGroup(c.cmd.Chown, replacementEnvs, c.From())
logrus.Debugf("found uid %v and gid %v for chown string %v", uid, gid, c.cmd.Chown)
if err != nil {
return errors.Wrap(err, "getting user group from chown")
Expand Down
4 changes: 2 additions & 2 deletions pkg/commands/copy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1006,7 +1006,7 @@ func TestCopyCommand_ExecuteCommand_Extended(t *testing.T) {
uid := os.Getuid()
gid := os.Getgid()

getUserGroup = func(userStr string, _ []string) (int64, int64, error) {
getUserGroup = func(userStr string, _ []string, _ string) (int64, int64, error) {
return int64(uid), int64(gid), nil
}

Expand Down Expand Up @@ -1051,7 +1051,7 @@ func TestCopyCommand_ExecuteCommand_Extended(t *testing.T) {
original := getUserGroup
defer func() { getUserGroup = original }()

getUserGroup = func(userStr string, _ []string) (int64, int64, error) {
getUserGroup = func(userStr string, _ []string, _ string) (int64, int64, error) {
return 12345, 12345, nil
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/commands/workdir.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func (w *WorkdirCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile

if config.User != "" {
logrus.Debugf("Fetching uid and gid for USER '%s'", config.User)
uid, gid, err = util.GetUserGroup(config.User, replacementEnvs)
uid, gid, err = util.GetUserGroup(config.User, replacementEnvs, "")
if err != nil {
return errors.Wrapf(err, "identifying uid and gid for user %s", config.User)
}
Expand Down
19 changes: 15 additions & 4 deletions pkg/executor/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,11 @@ type stageBuilder struct {
snapshotter snapShotter
layerCache cache.LayerCache
pushLayerToCache cachePusher
isCacheProbe bool
}

// newStageBuilder returns a new type stageBuilder which contains all the information required to build the stage
func newStageBuilder(args *dockerfile.BuildArgs, opts *config.KanikoOptions, stage config.KanikoStage, crossStageDeps map[int][]string, dcm map[string]string, sid map[string]string, stageNameToIdx map[string]string, fileContext util.FileContext) (*stageBuilder, error) {
func newStageBuilder(args *dockerfile.BuildArgs, opts *config.KanikoOptions, stage config.KanikoStage, crossStageDeps map[int][]string, dcm map[string]string, sid map[string]string, stageNameToIdx map[string]string, fileContext util.FileContext, isCacheProbe bool) (*stageBuilder, error) {
sourceImage, err := image_util.RetrieveSourceImage(stage, opts)
if err != nil {
return nil, err
Expand Down Expand Up @@ -138,6 +139,7 @@ func newStageBuilder(args *dockerfile.BuildArgs, opts *config.KanikoOptions, sta
stageIdxToDigest: sid,
layerCache: newLayerCache(opts),
pushLayerToCache: pushLayerToCache,
isCacheProbe: isCacheProbe,
}

for _, cmd := range s.stage.Commands {
Expand Down Expand Up @@ -227,11 +229,18 @@ func (s *stageBuilder) populateCompositeKey(command commands.DockerCommand, file
}
}

var addPathOptions []AddPathOption
if f, ok := command.(interface{ From() string }); ok {
if f.From() == "" && s.isCacheProbe {
addPathOptions = append(addPathOptions, IgnoreOwnerAndGroup())
}
}

// Add the next command to the cache key.
compositeKey.AddKey(command.String())

for _, f := range files {
if err := compositeKey.AddPath(f, s.fileContext); err != nil {
if err := compositeKey.AddPath(f, s.fileContext, addPathOptions...); err != nil {
return compositeKey, err
}
}
Expand Down Expand Up @@ -842,7 +851,8 @@ func DoBuild(opts *config.KanikoOptions) (v1.Image, error) {
digestToCacheKey,
stageIdxToDigest,
stageNameToIdx,
fileContext)
fileContext,
false)

logrus.Infof("Building stage '%v' [idx: '%v', base-idx: '%v']",
stage.BaseName, stage.Index, stage.BaseImageIndex)
Expand Down Expand Up @@ -992,7 +1002,8 @@ func DoCacheProbe(opts *config.KanikoOptions) (v1.Image, error) {
digestToCacheKey,
stageIdxToDigest,
stageNameToIdx,
fileContext)
fileContext,
true)
if err != nil {
return nil, err
}
Expand Down
26 changes: 21 additions & 5 deletions pkg/executor/composite_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,31 @@ func (s *CompositeCache) Hash() (string, error) {
return util.SHA256(strings.NewReader(s.Key()))
}

func (s *CompositeCache) AddPath(p string, context util.FileContext) error {
type addPathOptions struct {
ignoreOwnerAndGroup bool
}

type AddPathOption func(*addPathOptions)

func IgnoreOwnerAndGroup() func(*addPathOptions) {
return func(o *addPathOptions) {
o.ignoreOwnerAndGroup = true
}
}

func (s *CompositeCache) AddPath(p string, context util.FileContext, fopts ...AddPathOption) error {
var opts addPathOptions
for _, f := range fopts {
f(&opts)
}
sha := sha256.New()
fi, err := filesystem.FS.Lstat(p)
if err != nil {
return errors.Wrap(err, "could not add path")
}

if fi.Mode().IsDir() {
empty, k, err := hashDir(p, context)
empty, k, err := hashDir(opts.ignoreOwnerAndGroup, p, context)
if err != nil {
return err
}
Expand All @@ -79,7 +95,7 @@ func (s *CompositeCache) AddPath(p string, context util.FileContext) error {
if context.ExcludesFile(p) {
return nil
}
fh, err := util.CacheHasher()(p)
fh, err := util.CacheHasher(opts.ignoreOwnerAndGroup)(p)
if err != nil {
return err
}
Expand All @@ -92,7 +108,7 @@ func (s *CompositeCache) AddPath(p string, context util.FileContext) error {
}

// HashDir returns a hash of the directory.
func hashDir(p string, context util.FileContext) (bool, string, error) {
func hashDir(ignoreOwnerAndGroup bool, p string, context util.FileContext) (bool, string, error) {
sha := sha256.New()
empty := true
if err := filesystem.Walk(p, func(path string, fi os.FileInfo, err error) error {
Expand All @@ -104,7 +120,7 @@ func hashDir(p string, context util.FileContext) (bool, string, error) {
return nil
}

fileHash, err := util.CacheHasher()(path)
fileHash, err := util.CacheHasher(ignoreOwnerAndGroup)(path)
if err != nil {
return err
}
Expand Down
12 changes: 10 additions & 2 deletions pkg/util/command_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -354,9 +354,17 @@ Loop:
return nil
}

func GetUserGroup(chownStr string, env []string) (int64, int64, error) {
func GetUserGroup(chownStr string, env []string, from string) (int64, int64, error) {
// All files and directories copied from the build context are created with a
// UID and GID of 0 unless the optional --chown flag specifies a given
// username, groupname, or UID/GID combination to request specific ownership
// of the copied content.
if chownStr == "" {
return DoNotChangeUID, DoNotChangeGID, nil
if from == "" {
return 0, 0, nil
} else {
return DoNotChangeUID, DoNotChangeGID, nil
}
}

chown, err := ResolveEnvironmentReplacement(chownStr, env, false)
Expand Down
13 changes: 12 additions & 1 deletion pkg/util/command_util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,7 @@ func TestGetUserGroup(t *testing.T) {
description string
chown string
env []string
from string
mockIDGetter func(userStr string, groupStr string) (uint32, uint32, error)
// needed, in case uid is a valid number, but group is a name
mockGroupIDGetter func(groupStr string) (*user.Group, error)
Expand Down Expand Up @@ -571,6 +572,16 @@ func TestGetUserGroup(t *testing.T) {
mockIDGetter: func(string, string) (uint32, uint32, error) {
return 0, 0, fmt.Errorf("should not be called")
},
expectedU: 0,
expectedG: 0,
},
{
description: "empty chown and non-empty from",
chown: "",
from: "foo",
mockIDGetter: func(string, string) (uint32, uint32, error) {
return 100, 1000, nil
},
expectedU: -1,
expectedG: -1,
},
Expand All @@ -582,7 +593,7 @@ func TestGetUserGroup(t *testing.T) {
getUIDAndGIDFunc = originalIDGetter
}()
getUIDAndGIDFunc = tc.mockIDGetter
uid, gid, err := GetUserGroup(tc.chown, tc.env)
uid, gid, err := GetUserGroup(tc.chown, tc.env, tc.from)
testutil.CheckErrorAndDeepEqual(t, tc.shdErr, err, uid, tc.expectedU)
testutil.CheckErrorAndDeepEqual(t, tc.shdErr, err, gid, tc.expectedG)
})
Expand Down
17 changes: 2 additions & 15 deletions pkg/util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@ import (
"io"
"math"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"syscall"
"time"
Expand Down Expand Up @@ -96,7 +94,7 @@ type CacheHasherFileInfoSum interface {
}

// CacheHasher takes into account everything the regular hasher does except for mtime
func CacheHasher() func(string) (string, error) {
func CacheHasher(ignoreOwnerAndGroup bool) func(string) (string, error) {
hasher := func(p string) (string, error) {
h := md5.New()
fi, err := filesystem.FS.Lstat(p)
Expand All @@ -114,18 +112,7 @@ func CacheHasher() func(string) (string, error) {

h.Write([]byte(fi.Mode().String()))

// Cian: this is a disgusting hack, but it removes the need for the
// envbuilder binary to be owned by root when doing a cache probe.
// We want to ignore UID and GID changes for the envbuilder binary
// specifically. When building and pushing an image using the envbuilder
// image, the embedded envbuilder binary will most likely be owned by
// root:root. However, when performing a cache probe operation, it is more
// likely that the file will be owned by the UID/GID that is running
// envbuilder, which in this case is not guaranteed to be root.
// Let's just pretend that it is, cross our fingers, and hope for the best.
lyingAboutOwnership := !fi.IsDir() &&
strings.HasSuffix(filepath.Clean(filepath.Dir(p)), ".envbuilder.tmp")
if lyingAboutOwnership {
if ignoreOwnerAndGroup {
h.Write([]byte(strconv.FormatUint(uint64(0), 36)))
h.Write([]byte(","))
h.Write([]byte(strconv.FormatUint(uint64(0), 36)))
Expand Down
Loading