Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
12 changes: 10 additions & 2 deletions bake/bake.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/docker/buildx/bake/hclparser"
"github.com/docker/buildx/build"
"github.com/docker/buildx/util/buildflags"
"github.com/docker/buildx/util/ocilayout"
"github.com/docker/buildx/util/osutil"
"github.com/docker/buildx/util/platformutil"
"github.com/docker/buildx/util/progress"
Expand Down Expand Up @@ -1614,7 +1615,7 @@ func remoteURLWithSubdir(remoteURL, subdir string) string {
return base + "#" + ref + ":" + subdir
}

func collectLocalPaths(t build.Inputs) []string {
func collectLocalPaths(t build.Inputs) ([]string, error) {
var out []string
if t.ContextState == nil {
if v, ok := isLocalPath(t.ContextPath); ok {
Expand All @@ -1630,11 +1631,18 @@ func collectLocalPaths(t build.Inputs) []string {
if v.State != nil {
continue
}
if ref, ok, err := ocilayout.Parse(v.Path); ok {
if err != nil {
return nil, errors.Wrapf(err, "invalid OCI layout context %q", v.Path)
}
out = append(out, ref.Path)
continue
}
if v, ok := isLocalPath(v.Path); ok {
out = append(out, v)
}
}
return out
return out, nil
}

func isLocalPath(p string) (string, bool) {
Expand Down
32 changes: 26 additions & 6 deletions bake/entitlements.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,11 @@ func (c EntitlementConf) check(bo build.Options, expected *EntitlementConf) erro
rwPaths := map[string]struct{}{}
roPaths := map[string]struct{}{}

for _, p := range collectLocalPaths(bo.Inputs) {
localPaths, err := collectLocalPaths(bo.Inputs)
if err != nil {
return err
}
for _, p := range localPaths {
roPaths[p] = struct{}{}
}

Expand Down Expand Up @@ -200,8 +204,8 @@ func (c EntitlementConf) check(bo build.Options, expected *EntitlementConf) erro
}

for _, secret := range bo.SecretSpecs {
if secret.FilePath != "" {
roPaths[secret.FilePath] = struct{}{}
if filePath := secret.ResolveSource().FilePath; filePath != "" {
roPaths[filePath] = struct{}{}
}
}

Expand All @@ -216,7 +220,6 @@ func (c EntitlementConf) check(bo build.Options, expected *EntitlementConf) erro
}
}

var err error
expected.FSRead, err = findMissingPaths(c.FSRead, roPaths)
if err != nil {
return err
Expand All @@ -231,9 +234,19 @@ func (c EntitlementConf) check(bo build.Options, expected *EntitlementConf) erro
}

func (c EntitlementConf) Prompt(ctx context.Context, isRemote bool, out io.Writer) error {
return c.prompt(ctx, isRemote, out, true)
}

func (c EntitlementConf) Check(isRemote bool) error {
return c.prompt(context.Background(), isRemote, io.Discard, false)
}

func (c EntitlementConf) prompt(ctx context.Context, isRemote bool, out io.Writer, interactive bool) error {
var term bool
if _, err := console.ConsoleFromFile(os.Stdin); err == nil {
term = true
if interactive {
if _, err := console.ConsoleFromFile(os.Stdin); err == nil {
term = true
}
}

var msgs []string
Expand Down Expand Up @@ -365,6 +378,13 @@ func (c EntitlementConf) Prompt(ctx context.Context, isRemote bool, out io.Write
if fsEntitlementsEnabled && !fsEntitlementsSet && len(msgsFS) != 0 {
fmt.Fprintf(out, "To disable filesystem entitlements checks, you can set BUILDX_BAKE_ENTITLEMENTS_FS=0 .\n\n")
}
if !interactive {
requiredFlags := flags
if fsEntitlementsEnabled {
requiredFlags = slices.Concat(requiredFlags, flagsFS)
}
return errors.Errorf("additional privileges requested: pass %q to grant requested privileges", strings.Join(requiredFlags, " "))
}

if term {
fmt.Fprintf(out, "Do you want to grant requested privileges and continue? [y/N] ")
Expand Down
127 changes: 127 additions & 0 deletions bake/entitlements_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"os"
"path/filepath"
"slices"
"strings"
"testing"

"github.com/docker/buildx/build"
Expand Down Expand Up @@ -107,6 +108,13 @@ func TestValidateEntitlements(t *testing.T) {
require.NoError(t, err)
expWd, err := filepath.EvalSymlinks(wd)
require.NoError(t, err)
credentialPath := filepath.Join(dir1, "credential")
require.NoError(t, os.WriteFile(credentialPath, []byte("test credential"), 0600))
expCredentialPath, err := filepath.EvalSymlinks(credentialPath)
require.NoError(t, err)
layoutLink := filepath.Join(dir1, "layout-link")
require.NoError(t, os.Symlink(dir2, layoutLink))
t.Setenv("BUILDX_TEST_SECRET_SOURCE", "test credential")

tcases := []struct {
name string
Expand Down Expand Up @@ -223,6 +231,70 @@ func TestValidateEntitlements(t *testing.T) {
FSRead: []string{wd, dir1},
},
},
{
name: "secret-id-file-fallback-requires-read",
opt: build.Options{
SecretSpecs: []*buildflags.Secret{{ID: credentialPath}},
},
conf: EntitlementConf{FSRead: []string{wd}},
expected: EntitlementConf{
FSRead: []string{expCredentialPath},
},
},
{
name: "secret-id-file-fallback-allowed",
opt: build.Options{
SecretSpecs: []*buildflags.Secret{{ID: credentialPath}},
},
conf: EntitlementConf{FSRead: []string{wd, dir1}},
},
{
name: "secret-id-env-fallback-needs-no-read",
opt: build.Options{
SecretSpecs: []*buildflags.Secret{{ID: "BUILDX_TEST_SECRET_SOURCE"}},
},
conf: EntitlementConf{FSRead: []string{wd}},
},
{
name: "explicit-secret-env-needs-no-read",
opt: build.Options{
SecretSpecs: []*buildflags.Secret{{ID: credentialPath, Env: "BUILDX_TEST_SECRET_SOURCE"}},
},
conf: EntitlementConf{FSRead: []string{wd}},
},
{
name: "oci-layout-named-context-requires-read",
opt: build.Options{
Inputs: build.Inputs{NamedContexts: map[string]build.NamedContext{
"layout": {Path: "oci-layout://" + dir1 + ":latest"},
}},
},
conf: EntitlementConf{FSRead: []string{wd}},
expected: EntitlementConf{
FSRead: []string{expDir1},
},
},
{
name: "oci-layout-named-context-allowed",
opt: build.Options{
Inputs: build.Inputs{NamedContexts: map[string]build.NamedContext{
"layout": {Path: "oci-layout://" + dir1 + ":latest"},
}},
},
conf: EntitlementConf{FSRead: []string{wd, dir1}},
},
{
name: "oci-layout-named-context-symlink-requires-destination",
opt: build.Options{
Inputs: build.Inputs{NamedContexts: map[string]build.NamedContext{
"layout": {Path: "oci-layout://" + layoutLink + ":latest"},
}},
},
conf: EntitlementConf{FSRead: []string{wd, dir1}},
expected: EntitlementConf{
FSRead: []string{expDir2},
},
},
{
name: "SecretFromEscapeLink",
opt: build.Options{
Expand Down Expand Up @@ -441,6 +513,61 @@ func TestPromptLocalOutputDeleteCannotBeDisabledWithFSEntitlements(t *testing.T)
require.Contains(t, out.String(), "--allow=buildx.local.delete")
}

func TestCheckEntitlements(t *testing.T) {
t.Run("all entitlement types", func(t *testing.T) {
t.Setenv("BUILDX_BAKE_ENTITLEMENTS_FS", "1")

err := EntitlementConf{
NetworkHost: true,
SecurityInsecure: true,
Devices: &EntitlementsDevicesConf{
Devices: map[string]struct{}{"vendor.com/device=foo": {}},
},
FSRead: []string{t.TempDir()},
FSWrite: []string{t.TempDir()},
SSH: true,
LocalOutputDelete: true,
}.Check(false)
require.Error(t, err)
require.True(t, strings.HasPrefix(err.Error(), `additional privileges requested: pass "`), err.Error())
for _, flag := range []string{
"--allow=network.host",
"--allow=security.insecure",
"--allow=device=vendor.com/device=foo",
"--allow=fs.read=",
"--allow=fs.write=",
"--allow=ssh",
"--allow=buildx.local.delete",
} {
require.ErrorContains(t, err, flag)
}
})

t.Run("filesystem checks disabled", func(t *testing.T) {
t.Setenv("BUILDX_BAKE_ENTITLEMENTS_FS", "0")

err := EntitlementConf{
FSRead: []string{t.TempDir()},
FSWrite: []string{t.TempDir()},
SSH: true,
}.Check(false)
require.NoError(t, err)
})

t.Run("filesystem setting does not disable other checks", func(t *testing.T) {
t.Setenv("BUILDX_BAKE_ENTITLEMENTS_FS", "0")

err := EntitlementConf{
NetworkHost: true,
FSRead: []string{t.TempDir()},
SSH: true,
}.Check(false)
require.ErrorContains(t, err, "--allow=network.host")
require.NotContains(t, err.Error(), "--allow=fs.read=")
require.NotContains(t, err.Error(), "--allow=ssh")
})
}

func TestGroupSamePaths(t *testing.T) {
tests := []struct {
name string
Expand Down
4 changes: 2 additions & 2 deletions commands/bake.go
Original file line number Diff line number Diff line change
Expand Up @@ -341,8 +341,8 @@ func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in ba
return err
}
if progressMode == progressui.RawJSONMode {
if exp.LocalOutputDelete {
return errors.Errorf("additional privileges requested: pass %q to grant requested privileges", "--allow="+string(bake.EntitlementKeyBuildxLocalDelete))
if err := exp.Check(url != ""); err != nil {
return err
}
} else {
if err := exp.Prompt(ctx, url != "", &syncWriter{w: dockerCli.Err(), wait: printer.Wait}); err != nil {
Expand Down
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/docker/buildx

go 1.26.3
go 1.26.8

require (
github.com/Masterminds/semver/v3 v3.4.0
Expand All @@ -9,7 +9,7 @@ require (
github.com/aws/aws-sdk-go-v2/config v1.32.39
github.com/compose-spec/compose-go/v2 v2.14.0
github.com/containerd/console v1.0.5
github.com/containerd/containerd/v2 v2.3.4
github.com/containerd/containerd/v2 v2.3.6
github.com/containerd/continuity v0.5.0
github.com/containerd/errdefs v1.0.0
github.com/containerd/log v0.1.0
Expand All @@ -29,7 +29,7 @@ require (
github.com/hashicorp/hcl/v2 v2.24.0
github.com/in-toto/in-toto-golang v0.11.0
github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/moby/buildkit v0.33.0
github.com/moby/buildkit v0.33.1
github.com/moby/moby/api v1.55.0
github.com/moby/moby/client v0.5.0
github.com/moby/policy-helpers v0.0.0-20260901104222-dd6c5499c491
Expand Down Expand Up @@ -60,7 +60,7 @@ require (
go.opentelemetry.io/otel/sdk v1.45.0
go.opentelemetry.io/otel/trace v1.45.0
go.yaml.in/yaml/v3 v3.0.5
golang.org/x/crypto v0.55.0
golang.org/x/crypto v0.56.0
golang.org/x/mod v0.40.0
golang.org/x/sync v0.22.0
golang.org/x/sys v0.47.0
Expand Down
12 changes: 6 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ github.com/containerd/console v1.0.5 h1:R0ymNeydRqH2DmakFNdmjR2k0t7UPuiOV/N/27/q
github.com/containerd/console v1.0.5/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
github.com/containerd/containerd/api v1.11.1 h1:h8nfoDW9+fNsC/9TwiAHj8B1GzXKtR4eFtkhi/X5RLU=
github.com/containerd/containerd/api v1.11.1/go.mod h1:CaQFRu+N1MtbgL6JDOJLUB1hCKESU1lD6MuTJhgtdlw=
github.com/containerd/containerd/v2 v2.3.4 h1:c2PJo/9UGVdiiw8SwrxuLxWGY+9b3jQ6Xp9zntneIvI=
github.com/containerd/containerd/v2 v2.3.4/go.mod h1:a30D8fWZJ1Uzx/2WpjLbLsxBkq9He41pe8ENW+QZ3LY=
github.com/containerd/containerd/v2 v2.3.6 h1:huoqZXaDW1x1kgPvFC4rMwxv92vUJnqgq7I5b1rSddM=
github.com/containerd/containerd/v2 v2.3.6/go.mod h1:qaUSWKk9EchqbudSbhv1MxI8dXKGYHLpZtMQdkEkVZ4=
github.com/containerd/continuity v0.5.0 h1:7a85HZpCSs+1Zps0Ee3DPSuAWY+0SJM1JNM51nlEVDg=
github.com/containerd/continuity v0.5.0/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
Expand Down Expand Up @@ -393,8 +393,8 @@ github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/moby/buildkit v0.33.0 h1:zBbt1FiMcTB/oFg1iCNcKa83k5Rn8MGcVjXFIcfYhuQ=
github.com/moby/buildkit v0.33.0/go.mod h1:uNKSZnfMk1aSa18JiCR5BT68M/3jIDGo6XuAByUK3ek=
github.com/moby/buildkit v0.33.1 h1:UUrdifmRdRadykO+f5l8BT1CseUXst2sJHV7AmkjjxE=
github.com/moby/buildkit v0.33.1/go.mod h1:584wW8T/WG4O+lpXUCoDaWEc5e+rTGC3iP+l9mNcX8E=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8=
Expand Down Expand Up @@ -634,8 +634,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
go.yaml.in/yaml/v4 v4.0.0-rc.4 h1:UP4+v6fFrBIb1l934bDl//mmnoIZEDK0idg1+AIvX5U=
go.yaml.in/yaml/v4 v4.0.0-rc.4/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y=
golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I=
golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs=
golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE=
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
Expand Down
43 changes: 43 additions & 0 deletions tests/bake.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ var bakeTests = []func(t *testing.T, sb integration.Sandbox){
testBakeDefinitionNotExistingSubdirNoParallel,
testBakeDefinitionNotExistingOutsideNoParallel,
testBakeDefinitionExistingOutsideNoParallel,
testBakeRawJSONEntitlementsNoParallel,
testBakeDefinitionSymlinkOutsideNoParallel,
testBakeDefinitionSymlinkOutsideGrantedNoParallel,
testBakeSSHPathNoParallel,
Expand Down Expand Up @@ -1853,6 +1854,48 @@ target "default" {
}
}

func testBakeRawJSONEntitlementsNoParallel(t *testing.T, sb integration.Sandbox) {
t.Setenv("BUILDX_BAKE_ENTITLEMENTS_FS", "1")
dockerfile := []byte(`
FROM scratch
COPY foo /foo
`)
dirSrc := tmpdir(
t,
fstest.CreateFile("Dockerfile", dockerfile, 0600),
fstest.CreateFile("foo", []byte("foo"), 0600),
)
dirDest := t.TempDir()
bakefile := fmt.Appendf(nil, `
target "default" {
context = %q
output = ["type=local,dest=%s"]
}
`, dirSrc, dirDest)
dirSpec := tmpdir(
t,
fstest.CreateFile("docker-bake.hcl", bakefile, 0600),
)

cmd := buildxCmd(sb, withDir(dirSpec), withArgs("bake", "--progress=rawjson"))
out, err := cmd.CombinedOutput()
require.Error(t, err, string(out))
require.Contains(t, string(out), "additional privileges requested")
require.Contains(t, string(out), "--allow=fs.read=")
require.Contains(t, string(out), "--allow=fs.write=")
require.NoFileExists(t, filepath.Join(dirDest, "foo"))

cmd = buildxCmd(sb, withDir(dirSpec), withArgs(
"bake",
"--progress=rawjson",
"--allow=fs.read="+dirSrc,
"--allow=fs.write="+dirDest,
))
out, err = cmd.CombinedOutput()
require.NoError(t, err, string(out))
require.FileExists(t, filepath.Join(dirDest, "foo"))
}

func testBakeDefinitionSymlinkOutsideNoParallel(t *testing.T, sb integration.Sandbox) {
for _, ent := range []bool{true, false} {
t.Run(fmt.Sprintf("ent=%v", ent), func(t *testing.T) {
Expand Down
Loading
Loading