mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-11-04 18:13:42 +08:00 
			
		
		
		
	bake: add filesystem entitlements support
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
		@@ -1315,6 +1315,8 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
 | 
				
			|||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						bo.SecretSpecs = secrets
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	secretAttachment, err := controllerapi.CreateSecrets(secrets)
 | 
						secretAttachment, err := controllerapi.CreateSecrets(secrets)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
@@ -1328,6 +1330,8 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
 | 
				
			|||||||
	if len(sshSpecs) == 0 && (buildflags.IsGitSSH(bi.ContextPath) || (inp != nil && buildflags.IsGitSSH(inp.URL))) {
 | 
						if len(sshSpecs) == 0 && (buildflags.IsGitSSH(bi.ContextPath) || (inp != nil && buildflags.IsGitSSH(inp.URL))) {
 | 
				
			||||||
		sshSpecs = append(sshSpecs, &controllerapi.SSH{ID: "default"})
 | 
							sshSpecs = append(sshSpecs, &controllerapi.SSH{ID: "default"})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						bo.SSHSpecs = sshSpecs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	sshAttachment, err := controllerapi.CreateSSH(sshSpecs)
 | 
						sshAttachment, err := controllerapi.CreateSSH(sshSpecs)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,12 +2,17 @@ package bake
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"bufio"
 | 
						"bufio"
 | 
				
			||||||
 | 
						"cmp"
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"io"
 | 
						"io"
 | 
				
			||||||
 | 
						"io/fs"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
	"slices"
 | 
						"slices"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
						"syscall"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/containerd/console"
 | 
						"github.com/containerd/console"
 | 
				
			||||||
	"github.com/docker/buildx/build"
 | 
						"github.com/docker/buildx/build"
 | 
				
			||||||
@@ -67,10 +72,8 @@ func ParseEntitlements(in []string) (EntitlementConf, error) {
 | 
				
			|||||||
				conf.ImagePush = append(conf.ImagePush, v)
 | 
									conf.ImagePush = append(conf.ImagePush, v)
 | 
				
			||||||
				conf.ImageLoad = append(conf.ImageLoad, v)
 | 
									conf.ImageLoad = append(conf.ImageLoad, v)
 | 
				
			||||||
			default:
 | 
								default:
 | 
				
			||||||
				return conf, errors.Errorf("uknown entitlement key %q", k)
 | 
									return conf, errors.Errorf("unknown entitlement key %q", k)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					 | 
				
			||||||
			// TODO: dedupe slices and parent paths
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return conf, nil
 | 
						return conf, nil
 | 
				
			||||||
@@ -101,6 +104,66 @@ func (c EntitlementConf) check(bo build.Options, expected *EntitlementConf) erro
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rwPaths := map[string]struct{}{}
 | 
				
			||||||
 | 
						roPaths := map[string]struct{}{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, out := range bo.Exports {
 | 
				
			||||||
 | 
							if out.Type == "local" {
 | 
				
			||||||
 | 
								if dest, ok := out.Attrs["dest"]; ok {
 | 
				
			||||||
 | 
									rwPaths[dest] = struct{}{}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if out.Type == "tar" {
 | 
				
			||||||
 | 
								if dest, ok := out.Attrs["dest"]; ok && dest != "-" {
 | 
				
			||||||
 | 
									rwPaths[dest] = struct{}{}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, ce := range bo.CacheTo {
 | 
				
			||||||
 | 
							if ce.Type == "local" {
 | 
				
			||||||
 | 
								if dest, ok := ce.Attrs["dest"]; ok {
 | 
				
			||||||
 | 
									rwPaths[dest] = struct{}{}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, ci := range bo.CacheFrom {
 | 
				
			||||||
 | 
							if ci.Type == "local" {
 | 
				
			||||||
 | 
								if src, ok := ci.Attrs["src"]; ok {
 | 
				
			||||||
 | 
									roPaths[src] = struct{}{}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, secret := range bo.SecretSpecs {
 | 
				
			||||||
 | 
							if secret.FilePath != "" {
 | 
				
			||||||
 | 
								roPaths[secret.FilePath] = struct{}{}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, ssh := range bo.SSHSpecs {
 | 
				
			||||||
 | 
							for _, p := range ssh.Paths {
 | 
				
			||||||
 | 
								roPaths[p] = struct{}{}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if len(ssh.Paths) == 0 {
 | 
				
			||||||
 | 
								expected.SSH = true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						expected.FSRead, err = findMissingPaths(c.FSRead, roPaths)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						expected.FSWrite, err = findMissingPaths(c.FSWrite, rwPaths)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -113,32 +176,67 @@ func (c EntitlementConf) Prompt(ctx context.Context, out io.Writer) error {
 | 
				
			|||||||
	var msgs []string
 | 
						var msgs []string
 | 
				
			||||||
	var flags []string
 | 
						var flags []string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// these warnings are currently disabled to give users time to update
 | 
				
			||||||
 | 
						var msgsFS []string
 | 
				
			||||||
 | 
						var flagsFS []string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if c.NetworkHost {
 | 
						if c.NetworkHost {
 | 
				
			||||||
		msgs = append(msgs, " - Running build containers that can access host network")
 | 
							msgs = append(msgs, " - Running build containers that can access host network")
 | 
				
			||||||
		flags = append(flags, "network.host")
 | 
							flags = append(flags, string(EntitlementKeyNetworkHost))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if c.SecurityInsecure {
 | 
						if c.SecurityInsecure {
 | 
				
			||||||
		msgs = append(msgs, " - Running privileged containers that can make system changes")
 | 
							msgs = append(msgs, " - Running privileged containers that can make system changes")
 | 
				
			||||||
		flags = append(flags, "security.insecure")
 | 
							flags = append(flags, string(EntitlementKeySecurityInsecure))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if len(msgs) == 0 {
 | 
						if c.SSH {
 | 
				
			||||||
 | 
							msgsFS = append(msgsFS, " - Forwarding default SSH agent socket")
 | 
				
			||||||
 | 
							flagsFS = append(flagsFS, string(EntitlementKeySSH))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						roPaths, rwPaths, commonPaths := groupSamePaths(c.FSRead, c.FSWrite)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(commonPaths) > 0 {
 | 
				
			||||||
 | 
							for _, p := range commonPaths {
 | 
				
			||||||
 | 
								msgsFS = append(msgsFS, fmt.Sprintf(" - Read and write access to path %s", p))
 | 
				
			||||||
 | 
								flagsFS = append(flagsFS, string(EntitlementKeyFS)+"="+p)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(roPaths) > 0 {
 | 
				
			||||||
 | 
							for _, p := range roPaths {
 | 
				
			||||||
 | 
								msgsFS = append(msgsFS, fmt.Sprintf(" - Read access to path %s", p))
 | 
				
			||||||
 | 
								flagsFS = append(flagsFS, string(EntitlementKeyFSRead)+"="+p)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(rwPaths) > 0 {
 | 
				
			||||||
 | 
							for _, p := range rwPaths {
 | 
				
			||||||
 | 
								msgsFS = append(msgsFS, fmt.Sprintf(" - Write access to path %s", p))
 | 
				
			||||||
 | 
								flagsFS = append(flagsFS, string(EntitlementKeyFSWrite)+"="+p)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(msgs) == 0 && len(msgsFS) == 0 {
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	fmt.Fprintf(out, "Your build is requesting privileges for following possibly insecure capabilities:\n\n")
 | 
						fmt.Fprintf(out, "Your build is requesting privileges for following possibly insecure capabilities:\n\n")
 | 
				
			||||||
	for _, m := range msgs {
 | 
						for _, m := range slices.Concat(msgs, msgsFS) {
 | 
				
			||||||
		fmt.Fprintf(out, "%s\n", m)
 | 
							fmt.Fprintf(out, "%s\n", m)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for i, f := range flags {
 | 
						for i, f := range flags {
 | 
				
			||||||
		flags[i] = "--allow=" + f
 | 
							flags[i] = "--allow=" + f
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						for i, f := range flagsFS {
 | 
				
			||||||
 | 
							flagsFS[i] = "--allow=" + f
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if term {
 | 
						if term {
 | 
				
			||||||
		fmt.Fprintf(out, "\nIn order to not see this message in the future pass %q to grant requested privileges.\n", strings.Join(flags, " "))
 | 
							fmt.Fprintf(out, "\nIn order to not see this message in the future pass %q to grant requested privileges.\n", strings.Join(slices.Concat(flags, flagsFS), " "))
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		fmt.Fprintf(out, "\nPass %q to grant requested privileges.\n", strings.Join(flags, " "))
 | 
							fmt.Fprintf(out, "\nPass %q to grant requested privileges.\n", strings.Join(slices.Concat(flags, flagsFS), " "))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	args := append([]string(nil), os.Args...)
 | 
						args := append([]string(nil), os.Args...)
 | 
				
			||||||
@@ -149,7 +247,24 @@ func (c EntitlementConf) Prompt(ctx context.Context, out io.Writer) error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	if idx != -1 {
 | 
						if idx != -1 {
 | 
				
			||||||
		fmt.Fprintf(out, "\nYour full command with requested privileges:\n\n")
 | 
							fmt.Fprintf(out, "\nYour full command with requested privileges:\n\n")
 | 
				
			||||||
		fmt.Fprintf(out, "%s %s %s\n\n", strings.Join(args[:idx+1], " "), strings.Join(flags, " "), strings.Join(args[idx+1:], " "))
 | 
							fmt.Fprintf(out, "%s %s %s\n\n", strings.Join(args[:idx+1], " "), strings.Join(slices.Concat(flags, flagsFS), " "), strings.Join(args[idx+1:], " "))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fsEntitlementsEnabled := false
 | 
				
			||||||
 | 
						v, fsEntitlementsSet := os.LookupEnv("BUILDX_BAKE_ENTITLEMENTS_FS")
 | 
				
			||||||
 | 
						if fsEntitlementsSet {
 | 
				
			||||||
 | 
							vv, err := strconv.ParseBool(v)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return errors.Wrapf(err, "failed to parse BUILDX_BAKE_ENTITLEMENTS_FS value %q", v)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							fsEntitlementsEnabled = vv
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !fsEntitlementsEnabled && len(msgs) == 0 {
 | 
				
			||||||
 | 
							if !fsEntitlementsSet {
 | 
				
			||||||
 | 
								fmt.Fprintf(out, "This warning will become an error in a future release. To enable filesystem entitlements checks at the moment, set BUILDX_BAKE_ENTITLEMENTS_FS=1 .\n\n")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if term {
 | 
						if term {
 | 
				
			||||||
@@ -173,3 +288,277 @@ func (c EntitlementConf) Prompt(ctx context.Context, out io.Writer) error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	return errors.Errorf("additional privileges requested")
 | 
						return errors.Errorf("additional privileges requested")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func isParentOrEqualPath(p, parent string) bool {
 | 
				
			||||||
 | 
						if p == parent || parent == "/" {
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if strings.HasPrefix(p, parent+string(filepath.Separator)) {
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func findMissingPaths(set []string, paths map[string]struct{}) ([]string, error) {
 | 
				
			||||||
 | 
						paths, err := evaluateToExistingPaths(paths)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						paths, err = dedupPaths(paths)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						set, err = evaluatePaths(set)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						out := make([]string, 0, len(paths))
 | 
				
			||||||
 | 
					loop0:
 | 
				
			||||||
 | 
						for p := range paths {
 | 
				
			||||||
 | 
							for _, c := range set {
 | 
				
			||||||
 | 
								if isParentOrEqualPath(p, c) {
 | 
				
			||||||
 | 
									continue loop0
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							out = append(out, p)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if len(out) == 0 {
 | 
				
			||||||
 | 
							return nil, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						slices.Sort(out)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return out, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func dedupPaths(in map[string]struct{}) (map[string]struct{}, error) {
 | 
				
			||||||
 | 
						wd, err := os.Getwd()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						arr := make([]string, 0, len(in))
 | 
				
			||||||
 | 
						for p := range in {
 | 
				
			||||||
 | 
							arr = append(arr, filepath.Clean(p))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						slices.SortFunc(arr, func(a, b string) int {
 | 
				
			||||||
 | 
							return cmp.Compare(len(a), len(b))
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						m := make(map[string]struct{}, len(arr))
 | 
				
			||||||
 | 
					loop0:
 | 
				
			||||||
 | 
						for _, p := range arr {
 | 
				
			||||||
 | 
							for parent := range m {
 | 
				
			||||||
 | 
								if strings.HasPrefix(p, parent+string(filepath.Separator)) {
 | 
				
			||||||
 | 
									continue loop0
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							m[p] = struct{}{}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for p := range m {
 | 
				
			||||||
 | 
							rel, err := filepath.Rel(wd, p)
 | 
				
			||||||
 | 
							if err == nil {
 | 
				
			||||||
 | 
								if !strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
 | 
				
			||||||
 | 
									delete(m, p)
 | 
				
			||||||
 | 
									m[rel] = struct{}{}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return m, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func groupSamePaths(in1, in2 []string) ([]string, []string, []string) {
 | 
				
			||||||
 | 
						if in1 == nil || in2 == nil {
 | 
				
			||||||
 | 
							return in1, in2, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						slices.Sort(in1)
 | 
				
			||||||
 | 
						slices.Sort(in2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						common := []string{}
 | 
				
			||||||
 | 
						i, j := 0, 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for i < len(in1) && j < len(in2) {
 | 
				
			||||||
 | 
							switch {
 | 
				
			||||||
 | 
							case in1[i] == in2[j]:
 | 
				
			||||||
 | 
								common = append(common, in1[i])
 | 
				
			||||||
 | 
								i++
 | 
				
			||||||
 | 
								j++
 | 
				
			||||||
 | 
							case in1[i] < in2[j]:
 | 
				
			||||||
 | 
								i++
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								j++
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						in1 = removeCommonPaths(in1, common)
 | 
				
			||||||
 | 
						in2 = removeCommonPaths(in2, common)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return in1, in2, common
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func removeCommonPaths(in, common []string) []string {
 | 
				
			||||||
 | 
						filtered := make([]string, 0, len(in))
 | 
				
			||||||
 | 
						commonIndex := 0
 | 
				
			||||||
 | 
						for _, path := range in {
 | 
				
			||||||
 | 
							if commonIndex < len(common) && path == common[commonIndex] {
 | 
				
			||||||
 | 
								commonIndex++
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							filtered = append(filtered, path)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return filtered
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func evaluatePaths(in []string) ([]string, error) {
 | 
				
			||||||
 | 
						out := make([]string, 0, len(in))
 | 
				
			||||||
 | 
						for _, p := range in {
 | 
				
			||||||
 | 
							v, err := filepath.Abs(p)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							v, err = filepath.EvalSymlinks(v)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, errors.Wrapf(err, "failed to evaluate path %q", p)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							out = append(out, v)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return out, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func evaluateToExistingPaths(in map[string]struct{}) (map[string]struct{}, error) {
 | 
				
			||||||
 | 
						m := make(map[string]struct{}, len(in))
 | 
				
			||||||
 | 
						for p := range in {
 | 
				
			||||||
 | 
							v, err := evaluateToExistingPath(p)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, errors.Wrapf(err, "failed to evaluate path %q", p)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							m[v] = struct{}{}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return m, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func evaluateToExistingPath(in string) (string, error) {
 | 
				
			||||||
 | 
						in, err := filepath.Abs(in)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return "", err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						volLen := volumeNameLen(in)
 | 
				
			||||||
 | 
						pathSeparator := string(os.PathSeparator)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if volLen < len(in) && os.IsPathSeparator(in[volLen]) {
 | 
				
			||||||
 | 
							volLen++
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						vol := in[:volLen]
 | 
				
			||||||
 | 
						dest := vol
 | 
				
			||||||
 | 
						linksWalked := 0
 | 
				
			||||||
 | 
						var end int
 | 
				
			||||||
 | 
						for start := volLen; start < len(in); start = end {
 | 
				
			||||||
 | 
							for start < len(in) && os.IsPathSeparator(in[start]) {
 | 
				
			||||||
 | 
								start++
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							end = start
 | 
				
			||||||
 | 
							for end < len(in) && !os.IsPathSeparator(in[end]) {
 | 
				
			||||||
 | 
								end++
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if end == start {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							} else if in[start:end] == "." {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							} else if in[start:end] == ".." {
 | 
				
			||||||
 | 
								var r int
 | 
				
			||||||
 | 
								for r = len(dest) - 1; r >= volLen; r-- {
 | 
				
			||||||
 | 
									if os.IsPathSeparator(dest[r]) {
 | 
				
			||||||
 | 
										break
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if r < volLen || dest[r+1:] == ".." {
 | 
				
			||||||
 | 
									if len(dest) > volLen {
 | 
				
			||||||
 | 
										dest += pathSeparator
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									dest += ".."
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									dest = dest[:r]
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if len(dest) > volumeNameLen(dest) && !os.IsPathSeparator(dest[len(dest)-1]) {
 | 
				
			||||||
 | 
								dest += pathSeparator
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							dest += in[start:end]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							fi, err := os.Lstat(dest)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								// If the component doesn't exist, return the last valid path
 | 
				
			||||||
 | 
								if os.IsNotExist(err) {
 | 
				
			||||||
 | 
									for r := len(dest) - 1; r >= volLen; r-- {
 | 
				
			||||||
 | 
										if os.IsPathSeparator(dest[r]) {
 | 
				
			||||||
 | 
											return dest[:r], nil
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									return vol, nil
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return "", err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if fi.Mode()&fs.ModeSymlink == 0 {
 | 
				
			||||||
 | 
								if !fi.Mode().IsDir() && end < len(in) {
 | 
				
			||||||
 | 
									return "", syscall.ENOTDIR
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							linksWalked++
 | 
				
			||||||
 | 
							if linksWalked > 255 {
 | 
				
			||||||
 | 
								return "", errors.New("too many symlinks")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							link, err := os.Readlink(dest)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return "", err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							in = link + in[end:]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							v := volumeNameLen(link)
 | 
				
			||||||
 | 
							if v > 0 {
 | 
				
			||||||
 | 
								if v < len(link) && os.IsPathSeparator(link[v]) {
 | 
				
			||||||
 | 
									v++
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								vol = link[:v]
 | 
				
			||||||
 | 
								dest = vol
 | 
				
			||||||
 | 
								end = len(vol)
 | 
				
			||||||
 | 
							} else if len(link) > 0 && os.IsPathSeparator(link[0]) {
 | 
				
			||||||
 | 
								dest = link[:1]
 | 
				
			||||||
 | 
								end = 1
 | 
				
			||||||
 | 
								vol = link[:1]
 | 
				
			||||||
 | 
								volLen = 1
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								var r int
 | 
				
			||||||
 | 
								for r = len(dest) - 1; r >= volLen; r-- {
 | 
				
			||||||
 | 
									if os.IsPathSeparator(dest[r]) {
 | 
				
			||||||
 | 
										break
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if r < volLen {
 | 
				
			||||||
 | 
									dest = vol
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									dest = dest[:r]
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								end = 0
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return filepath.Clean(dest), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func volumeNameLen(s string) int {
 | 
				
			||||||
 | 
						return len(filepath.VolumeName(s))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										393
									
								
								bake/entitlements_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										393
									
								
								bake/entitlements_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,393 @@
 | 
				
			|||||||
 | 
					package bake
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
						"slices"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/docker/buildx/build"
 | 
				
			||||||
 | 
						"github.com/docker/buildx/controller/pb"
 | 
				
			||||||
 | 
						"github.com/moby/buildkit/client"
 | 
				
			||||||
 | 
						"github.com/moby/buildkit/util/entitlements"
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/require"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestEvaluateToExistingPath(t *testing.T) {
 | 
				
			||||||
 | 
						tempDir := t.TempDir()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Setup temporary directory structure for testing
 | 
				
			||||||
 | 
						existingFile := filepath.Join(tempDir, "existing_file")
 | 
				
			||||||
 | 
						err := os.WriteFile(existingFile, []byte("test"), 0644)
 | 
				
			||||||
 | 
						require.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						existingDir := filepath.Join(tempDir, "existing_dir")
 | 
				
			||||||
 | 
						err = os.Mkdir(existingDir, 0755)
 | 
				
			||||||
 | 
						require.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						symlinkToFile := filepath.Join(tempDir, "symlink_to_file")
 | 
				
			||||||
 | 
						err = os.Symlink(existingFile, symlinkToFile)
 | 
				
			||||||
 | 
						require.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						symlinkToDir := filepath.Join(tempDir, "symlink_to_dir")
 | 
				
			||||||
 | 
						err = os.Symlink(existingDir, symlinkToDir)
 | 
				
			||||||
 | 
						require.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						nonexistentPath := filepath.Join(tempDir, "nonexistent", "path", "file.txt")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							name      string
 | 
				
			||||||
 | 
							input     string
 | 
				
			||||||
 | 
							expected  string
 | 
				
			||||||
 | 
							expectErr bool
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:      "Existing file",
 | 
				
			||||||
 | 
								input:     existingFile,
 | 
				
			||||||
 | 
								expected:  existingFile,
 | 
				
			||||||
 | 
								expectErr: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:      "Existing directory",
 | 
				
			||||||
 | 
								input:     existingDir,
 | 
				
			||||||
 | 
								expected:  existingDir,
 | 
				
			||||||
 | 
								expectErr: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:      "Symlink to file",
 | 
				
			||||||
 | 
								input:     symlinkToFile,
 | 
				
			||||||
 | 
								expected:  existingFile,
 | 
				
			||||||
 | 
								expectErr: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:      "Symlink to directory",
 | 
				
			||||||
 | 
								input:     symlinkToDir,
 | 
				
			||||||
 | 
								expected:  existingDir,
 | 
				
			||||||
 | 
								expectErr: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:      "Non-existent path",
 | 
				
			||||||
 | 
								input:     nonexistentPath,
 | 
				
			||||||
 | 
								expected:  tempDir,
 | 
				
			||||||
 | 
								expectErr: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:      "Non-existent intermediate path",
 | 
				
			||||||
 | 
								input:     filepath.Join(tempDir, "nonexistent", "file.txt"),
 | 
				
			||||||
 | 
								expected:  tempDir,
 | 
				
			||||||
 | 
								expectErr: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:      "Root path",
 | 
				
			||||||
 | 
								input:     "/",
 | 
				
			||||||
 | 
								expected:  "/",
 | 
				
			||||||
 | 
								expectErr: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, tt := range tests {
 | 
				
			||||||
 | 
							t.Run(tt.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								result, err := evaluateToExistingPath(tt.input)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if tt.expectErr {
 | 
				
			||||||
 | 
									require.Error(t, err)
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									require.NoError(t, err)
 | 
				
			||||||
 | 
									require.Equal(t, tt.expected, result)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestDedupePaths(t *testing.T) {
 | 
				
			||||||
 | 
						wd, err := os.Getwd()
 | 
				
			||||||
 | 
						require.NoError(t, err)
 | 
				
			||||||
 | 
						tcases := []struct {
 | 
				
			||||||
 | 
							in  map[string]struct{}
 | 
				
			||||||
 | 
							out map[string]struct{}
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								in: map[string]struct{}{
 | 
				
			||||||
 | 
									"/a/b/c": {},
 | 
				
			||||||
 | 
									"/a/b/d": {},
 | 
				
			||||||
 | 
									"/a/b/e": {},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								out: map[string]struct{}{
 | 
				
			||||||
 | 
									"/a/b/c": {},
 | 
				
			||||||
 | 
									"/a/b/d": {},
 | 
				
			||||||
 | 
									"/a/b/e": {},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								in: map[string]struct{}{
 | 
				
			||||||
 | 
									"/a/b/c":      {},
 | 
				
			||||||
 | 
									"/a/b/c/d":    {},
 | 
				
			||||||
 | 
									"/a/b/c/d/e":  {},
 | 
				
			||||||
 | 
									"/a/b/../b/c": {},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								out: map[string]struct{}{
 | 
				
			||||||
 | 
									"/a/b/c": {},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								in: map[string]struct{}{
 | 
				
			||||||
 | 
									filepath.Join(wd, "a/b/c"):   {},
 | 
				
			||||||
 | 
									filepath.Join(wd, "../aa"):   {},
 | 
				
			||||||
 | 
									filepath.Join(wd, "a/b"):     {},
 | 
				
			||||||
 | 
									filepath.Join(wd, "a/b/d"):   {},
 | 
				
			||||||
 | 
									filepath.Join(wd, "../aa/b"): {},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								out: map[string]struct{}{
 | 
				
			||||||
 | 
									"a/b":                      {},
 | 
				
			||||||
 | 
									filepath.Join(wd, "../aa"): {},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for i, tc := range tcases {
 | 
				
			||||||
 | 
							t.Run(fmt.Sprintf("case%d", i), func(t *testing.T) {
 | 
				
			||||||
 | 
								out, err := dedupPaths(tc.in)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									require.NoError(t, err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								require.Equal(t, tc.out, out)
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestValidateEntitlements(t *testing.T) {
 | 
				
			||||||
 | 
						dir1 := t.TempDir()
 | 
				
			||||||
 | 
						dir2 := t.TempDir()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						escapeLink := filepath.Join(dir1, "escape_link")
 | 
				
			||||||
 | 
						err := os.Symlink("../../aa", escapeLink)
 | 
				
			||||||
 | 
						require.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tcases := []struct {
 | 
				
			||||||
 | 
							name     string
 | 
				
			||||||
 | 
							conf     EntitlementConf
 | 
				
			||||||
 | 
							opt      build.Options
 | 
				
			||||||
 | 
							expected EntitlementConf
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "No entitlements",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "NetworkHostMissing",
 | 
				
			||||||
 | 
								opt: build.Options{
 | 
				
			||||||
 | 
									Allow: []entitlements.Entitlement{
 | 
				
			||||||
 | 
										entitlements.EntitlementNetworkHost,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expected: EntitlementConf{
 | 
				
			||||||
 | 
									NetworkHost: true,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "NetworkHostSet",
 | 
				
			||||||
 | 
								conf: EntitlementConf{
 | 
				
			||||||
 | 
									NetworkHost: true,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								opt: build.Options{
 | 
				
			||||||
 | 
									Allow: []entitlements.Entitlement{
 | 
				
			||||||
 | 
										entitlements.EntitlementNetworkHost,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expected: EntitlementConf{},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "SecurityAndNetworkHostMissing",
 | 
				
			||||||
 | 
								opt: build.Options{
 | 
				
			||||||
 | 
									Allow: []entitlements.Entitlement{
 | 
				
			||||||
 | 
										entitlements.EntitlementNetworkHost,
 | 
				
			||||||
 | 
										entitlements.EntitlementSecurityInsecure,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expected: EntitlementConf{
 | 
				
			||||||
 | 
									NetworkHost:      true,
 | 
				
			||||||
 | 
									SecurityInsecure: true,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "SecurityMissingAndNetworkHostSet",
 | 
				
			||||||
 | 
								conf: EntitlementConf{
 | 
				
			||||||
 | 
									NetworkHost: true,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								opt: build.Options{
 | 
				
			||||||
 | 
									Allow: []entitlements.Entitlement{
 | 
				
			||||||
 | 
										entitlements.EntitlementNetworkHost,
 | 
				
			||||||
 | 
										entitlements.EntitlementSecurityInsecure,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expected: EntitlementConf{
 | 
				
			||||||
 | 
									SecurityInsecure: true,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "SSHMissing",
 | 
				
			||||||
 | 
								opt: build.Options{
 | 
				
			||||||
 | 
									SSHSpecs: []*pb.SSH{
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											ID: "test",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expected: EntitlementConf{
 | 
				
			||||||
 | 
									SSH: true,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "ExportLocal",
 | 
				
			||||||
 | 
								opt: build.Options{
 | 
				
			||||||
 | 
									Exports: []client.ExportEntry{
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											Type: "local",
 | 
				
			||||||
 | 
											Attrs: map[string]string{
 | 
				
			||||||
 | 
												"dest": dir1,
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											Type: "local",
 | 
				
			||||||
 | 
											Attrs: map[string]string{
 | 
				
			||||||
 | 
												"dest": filepath.Join(dir1, "subdir"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											Type: "local",
 | 
				
			||||||
 | 
											Attrs: map[string]string{
 | 
				
			||||||
 | 
												"dest": dir2,
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expected: EntitlementConf{
 | 
				
			||||||
 | 
									FSWrite: func() []string {
 | 
				
			||||||
 | 
										exp := []string{dir1, dir2}
 | 
				
			||||||
 | 
										slices.Sort(exp)
 | 
				
			||||||
 | 
										return exp
 | 
				
			||||||
 | 
									}(),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "SecretFromSubFile",
 | 
				
			||||||
 | 
								opt: build.Options{
 | 
				
			||||||
 | 
									SecretSpecs: []*pb.Secret{
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											FilePath: filepath.Join(dir1, "subfile"),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								conf: EntitlementConf{
 | 
				
			||||||
 | 
									FSRead: []string{dir1},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "SecretFromEscapeLink",
 | 
				
			||||||
 | 
								opt: build.Options{
 | 
				
			||||||
 | 
									SecretSpecs: []*pb.Secret{
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											FilePath: escapeLink,
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								conf: EntitlementConf{
 | 
				
			||||||
 | 
									FSRead: []string{dir1},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expected: EntitlementConf{
 | 
				
			||||||
 | 
									FSRead: []string{filepath.Join(dir1, "../..")},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "SecretFromEscapeLinkAllowRoot",
 | 
				
			||||||
 | 
								opt: build.Options{
 | 
				
			||||||
 | 
									SecretSpecs: []*pb.Secret{
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											FilePath: escapeLink,
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								conf: EntitlementConf{
 | 
				
			||||||
 | 
									FSRead: []string{"/"},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expected: EntitlementConf{},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, tc := range tcases {
 | 
				
			||||||
 | 
							t.Run(tc.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								expected, err := tc.conf.Validate(map[string]build.Options{"test": tc.opt})
 | 
				
			||||||
 | 
								require.NoError(t, err)
 | 
				
			||||||
 | 
								require.Equal(t, tc.expected, expected)
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestGroupSamePaths(t *testing.T) {
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							name      string
 | 
				
			||||||
 | 
							in1       []string
 | 
				
			||||||
 | 
							in2       []string
 | 
				
			||||||
 | 
							expected1 []string
 | 
				
			||||||
 | 
							expected2 []string
 | 
				
			||||||
 | 
							expectedC []string
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:      "All common paths",
 | 
				
			||||||
 | 
								in1:       []string{"/path/a", "/path/b", "/path/c"},
 | 
				
			||||||
 | 
								in2:       []string{"/path/a", "/path/b", "/path/c"},
 | 
				
			||||||
 | 
								expected1: []string{},
 | 
				
			||||||
 | 
								expected2: []string{},
 | 
				
			||||||
 | 
								expectedC: []string{"/path/a", "/path/b", "/path/c"},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:      "No common paths",
 | 
				
			||||||
 | 
								in1:       []string{"/path/a", "/path/b"},
 | 
				
			||||||
 | 
								in2:       []string{"/path/c", "/path/d"},
 | 
				
			||||||
 | 
								expected1: []string{"/path/a", "/path/b"},
 | 
				
			||||||
 | 
								expected2: []string{"/path/c", "/path/d"},
 | 
				
			||||||
 | 
								expectedC: []string{},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:      "Some common paths",
 | 
				
			||||||
 | 
								in1:       []string{"/path/a", "/path/b", "/path/c"},
 | 
				
			||||||
 | 
								in2:       []string{"/path/b", "/path/c", "/path/d"},
 | 
				
			||||||
 | 
								expected1: []string{"/path/a"},
 | 
				
			||||||
 | 
								expected2: []string{"/path/d"},
 | 
				
			||||||
 | 
								expectedC: []string{"/path/b", "/path/c"},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:      "Empty inputs",
 | 
				
			||||||
 | 
								in1:       []string{},
 | 
				
			||||||
 | 
								in2:       []string{},
 | 
				
			||||||
 | 
								expected1: []string{},
 | 
				
			||||||
 | 
								expected2: []string{},
 | 
				
			||||||
 | 
								expectedC: []string{},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:      "One empty input",
 | 
				
			||||||
 | 
								in1:       []string{"/path/a", "/path/b"},
 | 
				
			||||||
 | 
								in2:       []string{},
 | 
				
			||||||
 | 
								expected1: []string{"/path/a", "/path/b"},
 | 
				
			||||||
 | 
								expected2: []string{},
 | 
				
			||||||
 | 
								expectedC: []string{},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:      "Unsorted inputs with common paths",
 | 
				
			||||||
 | 
								in1:       []string{"/path/c", "/path/a", "/path/b"},
 | 
				
			||||||
 | 
								in2:       []string{"/path/b", "/path/c", "/path/a"},
 | 
				
			||||||
 | 
								expected1: []string{},
 | 
				
			||||||
 | 
								expected2: []string{},
 | 
				
			||||||
 | 
								expectedC: []string{"/path/a", "/path/b", "/path/c"},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, tt := range tests {
 | 
				
			||||||
 | 
							t.Run(tt.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								out1, out2, common := groupSamePaths(tt.in1, tt.in2)
 | 
				
			||||||
 | 
								require.Equal(t, tt.expected1, out1, "in1 should match expected1")
 | 
				
			||||||
 | 
								require.Equal(t, tt.expected2, out2, "in2 should match expected2")
 | 
				
			||||||
 | 
								require.Equal(t, tt.expectedC, common, "common should match expectedC")
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -18,6 +18,7 @@ import (
 | 
				
			|||||||
	"github.com/containerd/containerd/images"
 | 
						"github.com/containerd/containerd/images"
 | 
				
			||||||
	"github.com/distribution/reference"
 | 
						"github.com/distribution/reference"
 | 
				
			||||||
	"github.com/docker/buildx/builder"
 | 
						"github.com/docker/buildx/builder"
 | 
				
			||||||
 | 
						controllerapi "github.com/docker/buildx/controller/pb"
 | 
				
			||||||
	"github.com/docker/buildx/driver"
 | 
						"github.com/docker/buildx/driver"
 | 
				
			||||||
	"github.com/docker/buildx/util/confutil"
 | 
						"github.com/docker/buildx/util/confutil"
 | 
				
			||||||
	"github.com/docker/buildx/util/desktop"
 | 
						"github.com/docker/buildx/util/desktop"
 | 
				
			||||||
@@ -76,6 +77,8 @@ type Options struct {
 | 
				
			|||||||
	NoCacheFilter []string
 | 
						NoCacheFilter []string
 | 
				
			||||||
	Platforms     []specs.Platform
 | 
						Platforms     []specs.Platform
 | 
				
			||||||
	Pull          bool
 | 
						Pull          bool
 | 
				
			||||||
 | 
						SecretSpecs   []*controllerapi.Secret
 | 
				
			||||||
 | 
						SSHSpecs      []*controllerapi.SSH
 | 
				
			||||||
	ShmSize       opts.MemBytes
 | 
						ShmSize       opts.MemBytes
 | 
				
			||||||
	Tags          []string
 | 
						Tags          []string
 | 
				
			||||||
	Target        string
 | 
						Target        string
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -107,6 +107,13 @@ func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in ba
 | 
				
			|||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						wd, err := os.Getwd()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return errors.Wrapf(err, "failed to get current working directory")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// filesystem access under the current working directory is allowed by default
 | 
				
			||||||
 | 
						ent.FSRead = append(ent.FSRead, wd)
 | 
				
			||||||
 | 
						ent.FSWrite = append(ent.FSWrite, wd)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx2, cancel := context.WithCancelCause(context.TODO())
 | 
						ctx2, cancel := context.WithCancelCause(context.TODO())
 | 
				
			||||||
	defer cancel(errors.WithStack(context.Canceled))
 | 
						defer cancel(errors.WithStack(context.Canceled))
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user