From 280c008f815dbef8eaeeb70aaa230611dd804710 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Wed, 23 Feb 2022 23:09:46 -0800 Subject: [PATCH 1/2] bake: make named contexts relative to remote bake input Signed-off-by: Tonis Tiigi --- bake/bake.go | 30 +++++++++++++++++++++++++++++- bake/bake_test.go | 12 ++++++------ build/build.go | 24 +++++++++++++++++++----- commands/build.go | 6 +++--- 4 files changed, 57 insertions(+), 15 deletions(-) diff --git a/bake/bake.go b/bake/bake.go index 114cc3a8..e703860e 100644 --- a/bake/bake.go +++ b/bake/bake.go @@ -715,6 +715,21 @@ func updateContext(t *build.Inputs, inp *Input) { if inp == nil || inp.State == nil { return } + + for k, v := range t.NamedContexts { + if v.Path == "." { + t.NamedContexts[k] = build.NamedContext{Path: inp.URL} + } + if strings.HasPrefix(v.Path, "cwd://") || strings.HasPrefix(v.Path, "target:") || strings.HasPrefix(v.Path, "docker-image:") { + continue + } + if IsRemoteURL(v.Path) { + continue + } + st := llb.Scratch().File(llb.Copy(*inp.State, v.Path, "/"), llb.WithCustomNamef("set context %s to %s", k, v.Path)) + t.NamedContexts[k] = build.NamedContext{State: &st} + } + if t.ContextPath == "." { t.ContextPath = inp.URL return @@ -769,7 +784,7 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) { bi := build.Inputs{ ContextPath: contextPath, DockerfilePath: dockerfilePath, - NamedContexts: t.Contexts, + NamedContexts: toNamedContexts(t.Contexts), } if t.DockerfileInline != nil { bi.DockerfileInline = *t.DockerfileInline @@ -778,6 +793,11 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) { if strings.HasPrefix(bi.ContextPath, "cwd://") { bi.ContextPath = path.Clean(strings.TrimPrefix(bi.ContextPath, "cwd://")) } + for k, v := range bi.NamedContexts { + if strings.HasPrefix(v.Path, "cwd://") { + bi.NamedContexts[k] = build.NamedContext{Path: path.Clean(strings.TrimPrefix(v.Path, "cwd://"))} + } + } t.Context = &bi.ContextPath @@ -903,3 +923,11 @@ func sliceEqual(s1, s2 []string) bool { } return true } + +func toNamedContexts(m map[string]string) map[string]build.NamedContext { + m2 := make(map[string]build.NamedContext, len(m)) + for k, v := range m { + m2[k] = build.NamedContext{Path: v} + } + return m2 +} diff --git a/bake/bake_test.go b/bake/bake_test.go index 0df5440f..9864e85b 100644 --- a/bake/bake_test.go +++ b/bake/bake_test.go @@ -386,8 +386,8 @@ func TestReadContexts(t *testing.T) { ctxs := bo["app"].Inputs.NamedContexts require.Equal(t, 2, len(ctxs)) - require.Equal(t, "baz", ctxs["foo"]) - require.Equal(t, "def", ctxs["abc"]) + require.Equal(t, "baz", ctxs["foo"].Path) + require.Equal(t, "def", ctxs["abc"].Path) m, _, err = ReadTargets(ctx, []File{fp}, []string{"app"}, []string{"app.contexts.foo=bay", "base.contexts.ghi=jkl"}, nil) require.NoError(t, err) @@ -402,9 +402,9 @@ func TestReadContexts(t *testing.T) { ctxs = bo["app"].Inputs.NamedContexts require.Equal(t, 3, len(ctxs)) - require.Equal(t, "bay", ctxs["foo"]) - require.Equal(t, "def", ctxs["abc"]) - require.Equal(t, "jkl", ctxs["ghi"]) + require.Equal(t, "bay", ctxs["foo"].Path) + require.Equal(t, "def", ctxs["abc"].Path) + require.Equal(t, "jkl", ctxs["ghi"].Path) // test resetting base values m, _, err = ReadTargets(ctx, []File{fp}, []string{"app"}, []string{"app.contexts.foo="}, nil) @@ -419,7 +419,7 @@ func TestReadContexts(t *testing.T) { ctxs = bo["app"].Inputs.NamedContexts require.Equal(t, 1, len(ctxs)) - require.Equal(t, "def", ctxs["abc"]) + require.Equal(t, "def", ctxs["abc"].Path) } func TestReadContextFromTargetUnknown(t *testing.T) { diff --git a/build/build.go b/build/build.go index eb194f59..b6da2fa5 100644 --- a/build/build.go +++ b/build/build.go @@ -84,7 +84,12 @@ type Inputs struct { InStream io.Reader ContextState *llb.State DockerfileInline string - NamedContexts map[string]string + NamedContexts map[string]NamedContext +} + +type NamedContext struct { + Path string + State *llb.State } type DriverInfo struct { @@ -1160,11 +1165,20 @@ func LoadInputs(ctx context.Context, d driver.Driver, inp Inputs, pw progress.Wr for k, v := range inp.NamedContexts { target.FrontendAttrs["frontend.caps"] = "moby.buildkit.frontend.contexts+forward" - if urlutil.IsGitURL(v) || urlutil.IsURL(v) || strings.HasPrefix(v, "docker-image://") || strings.HasPrefix(v, "target:") { - target.FrontendAttrs["context:"+k] = v + if v.State != nil { + target.FrontendAttrs["context:"+k] = "input:" + k + if target.FrontendInputs == nil { + target.FrontendInputs = make(map[string]llb.State) + } + target.FrontendInputs[k] = *v.State + continue + } + + if urlutil.IsGitURL(v.Path) || urlutil.IsURL(v.Path) || strings.HasPrefix(v.Path, "docker-image://") || strings.HasPrefix(v.Path, "target:") { + target.FrontendAttrs["context:"+k] = v.Path continue } - st, err := os.Stat(v) + st, err := os.Stat(v.Path) if err != nil { return nil, errors.Wrapf(err, "failed to get build context %v", k) } @@ -1175,7 +1189,7 @@ func LoadInputs(ctx context.Context, d driver.Driver, inp Inputs, pw progress.Wr if k == "context" || k == "dockerfile" { localName = "_" + k // underscore to avoid collisions } - target.LocalDirs[localName] = v + target.LocalDirs[localName] = v.Path target.FrontendAttrs["context:"+k] = "local:" + localName } diff --git a/commands/build.go b/commands/build.go index 2b00c2c7..c8259ec1 100644 --- a/commands/build.go +++ b/commands/build.go @@ -480,11 +480,11 @@ func listToMap(values []string, defaultEnv bool) map[string]string { return result } -func parseContextNames(values []string) (map[string]string, error) { +func parseContextNames(values []string) (map[string]build.NamedContext, error) { if len(values) == 0 { return nil, nil } - result := make(map[string]string, len(values)) + result := make(map[string]build.NamedContext, len(values)) for _, value := range values { kv := strings.SplitN(value, "=", 2) if len(kv) != 2 { @@ -495,7 +495,7 @@ func parseContextNames(values []string) (map[string]string, error) { return nil, errors.Wrapf(err, "invalid context name %s", kv[0]) } name := strings.TrimSuffix(reference.FamiliarString(named), ":latest") - result[name] = kv[1] + result[name] = build.NamedContext{Path: kv[1]} } return result, nil } From 91e550b7154e85cd6dde6c7008e8c3bf69e2b733 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Thu, 24 Feb 2022 00:05:42 -0800 Subject: [PATCH 2/2] bake: add path validation for remote bake invocations This is a stopgap before proper entitlements support is implemented. Signed-off-by: Tonis Tiigi --- bake/bake.go | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/bake/bake.go b/bake/bake.go index e703860e..82d7d921 100644 --- a/bake/bake.go +++ b/bake/bake.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "os" "path" + "path/filepath" "regexp" "sort" "strconv" @@ -744,6 +745,59 @@ func updateContext(t *build.Inputs, inp *Input) { t.ContextState = &st } +// validateContextsEntitlements is a basic check to ensure contexts do not +// escape local directories when loaded from remote sources. This is to be +// replaced with proper entitlements support in the future. +func validateContextsEntitlements(t build.Inputs, inp *Input) error { + if inp == nil || inp.State == nil { + return nil + } + if v, ok := os.LookupEnv("BAKE_ALLOW_REMOTE_FS_ACCESS"); ok { + if vv, _ := strconv.ParseBool(v); vv { + return nil + } + } + if t.ContextState == nil { + if err := checkPath(t.ContextPath); err != nil { + return err + } + } + for _, v := range t.NamedContexts { + if v.State != nil { + continue + } + if err := checkPath(v.Path); err != nil { + return err + } + } + return nil +} + +func checkPath(p string) error { + if IsRemoteURL(p) || strings.HasPrefix(p, "target:") || strings.HasPrefix(p, "docker-image:") { + return nil + } + p, err := filepath.EvalSymlinks(p) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return err + } + wd, err := os.Getwd() + if err != nil { + return err + } + rel, err := filepath.Rel(wd, p) + if err != nil { + return err + } + if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) { + return errors.Errorf("path %s is outside of the working directory, please set BAKE_ALLOW_REMOTE_FS_ACCESS=1", p) + } + return nil +} + func toBuildOpt(t *Target, inp *Input) (*build.Options, error) { if v := t.Context; v != nil && *v == "-" { return nil, errors.Errorf("context from stdin not allowed in bake") @@ -799,6 +853,10 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) { } } + if err := validateContextsEntitlements(bi, inp); err != nil { + return nil, err + } + t.Context = &bi.ContextPath bo := &build.Options{