bake: requires explicit allow for local output delete mode
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
This commit is contained in:
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/containerd/console"
|
||||
"github.com/docker/buildx/build"
|
||||
"github.com/docker/buildx/util/osutil"
|
||||
"github.com/moby/buildkit/client"
|
||||
"github.com/moby/buildkit/util/entitlements"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -36,6 +37,7 @@ const (
|
||||
EntitlementKeyImageLoad EntitlementKey = "image.load"
|
||||
EntitlementKeyImage EntitlementKey = "image"
|
||||
EntitlementKeySSH EntitlementKey = "ssh"
|
||||
EntitlementKeyLocalOutputDelete EntitlementKey = "local-output-delete"
|
||||
)
|
||||
|
||||
type EntitlementConf struct {
|
||||
@@ -47,6 +49,7 @@ type EntitlementConf struct {
|
||||
ImagePush []string
|
||||
ImageLoad []string
|
||||
SSH bool
|
||||
LocalOutputDelete bool
|
||||
}
|
||||
|
||||
type EntitlementsDevicesConf struct {
|
||||
@@ -64,6 +67,8 @@ func ParseEntitlements(in []string) (EntitlementConf, error) {
|
||||
conf.SecurityInsecure = true
|
||||
case string(EntitlementKeySSH):
|
||||
conf.SSH = true
|
||||
case string(EntitlementKeyLocalOutputDelete):
|
||||
conf.LocalOutputDelete = true
|
||||
default:
|
||||
k, v, _ := strings.Cut(e, "=")
|
||||
switch k {
|
||||
@@ -97,6 +102,8 @@ func ParseEntitlements(in []string) (EntitlementConf, error) {
|
||||
case string(EntitlementKeyImage):
|
||||
conf.ImagePush = append(conf.ImagePush, v)
|
||||
conf.ImageLoad = append(conf.ImageLoad, v)
|
||||
case string(EntitlementKeyLocalOutputDelete):
|
||||
return conf, errors.Errorf("%s does not accept a value", EntitlementKeyLocalOutputDelete)
|
||||
default:
|
||||
return conf, errors.Errorf("unknown entitlement key %q", k)
|
||||
}
|
||||
@@ -164,6 +171,19 @@ func (c EntitlementConf) check(bo build.Options, expected *EntitlementConf) erro
|
||||
rwPaths[p] = struct{}{}
|
||||
}
|
||||
|
||||
for _, ex := range bo.Exports {
|
||||
if ex.Type != client.ExporterLocal {
|
||||
continue
|
||||
}
|
||||
mode, err := client.ParseLocalExporterMode(ex.Attrs["mode"])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if mode == client.LocalExporterModeDelete && !c.LocalOutputDelete {
|
||||
expected.LocalOutputDelete = true
|
||||
}
|
||||
}
|
||||
|
||||
for _, ce := range bo.CacheTo {
|
||||
if ce.Type == "local" {
|
||||
if dest, ok := ce.Attrs["dest"]; ok {
|
||||
@@ -249,6 +269,10 @@ func (c EntitlementConf) Prompt(ctx context.Context, isRemote bool, out io.Write
|
||||
msgsFS = append(msgsFS, " - Forwarding default SSH agent socket")
|
||||
flagsFS = append(flagsFS, string(EntitlementKeySSH))
|
||||
}
|
||||
if c.LocalOutputDelete {
|
||||
msgs = append(msgs, " - Deleting stale files from local output destinations")
|
||||
flags = append(flags, string(EntitlementKeyLocalOutputDelete))
|
||||
}
|
||||
|
||||
roPaths, rwPaths, commonPaths := groupSamePaths(c.FSRead, c.FSWrite)
|
||||
wd, err := os.Getwd()
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package bake
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -10,6 +12,7 @@ import (
|
||||
"github.com/docker/buildx/build"
|
||||
"github.com/docker/buildx/util/buildflags"
|
||||
"github.com/docker/buildx/util/osutil"
|
||||
"github.com/moby/buildkit/client"
|
||||
"github.com/moby/buildkit/client/llb"
|
||||
"github.com/moby/buildkit/util/entitlements"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -405,6 +408,73 @@ func TestValidateEntitlements(t *testing.T) {
|
||||
FSWrite: []string{dir1},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "LocalOutputDeleteMissing",
|
||||
conf: EntitlementConf{
|
||||
FSWrite: []string{"*"},
|
||||
},
|
||||
opt: build.Options{
|
||||
Inputs: build.Inputs{
|
||||
ContextState: &llb.State{},
|
||||
},
|
||||
Exports: []client.ExportEntry{
|
||||
{
|
||||
Type: client.ExporterLocal,
|
||||
OutputDir: dir1,
|
||||
Attrs: map[string]string{
|
||||
"mode": string(client.LocalExporterModeDelete),
|
||||
},
|
||||
},
|
||||
},
|
||||
ExportsLocalPathsTemporary: []string{dir1},
|
||||
},
|
||||
expected: EntitlementConf{
|
||||
LocalOutputDelete: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "LocalOutputDeleteSet",
|
||||
conf: EntitlementConf{
|
||||
FSWrite: []string{"*"},
|
||||
LocalOutputDelete: true,
|
||||
},
|
||||
opt: build.Options{
|
||||
Inputs: build.Inputs{
|
||||
ContextState: &llb.State{},
|
||||
},
|
||||
Exports: []client.ExportEntry{
|
||||
{
|
||||
Type: client.ExporterLocal,
|
||||
OutputDir: dir1,
|
||||
Attrs: map[string]string{
|
||||
"mode": string(client.LocalExporterModeDelete),
|
||||
},
|
||||
},
|
||||
},
|
||||
ExportsLocalPathsTemporary: []string{dir1},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "LocalOutputCopy",
|
||||
conf: EntitlementConf{
|
||||
FSWrite: []string{"*"},
|
||||
},
|
||||
opt: build.Options{
|
||||
Inputs: build.Inputs{
|
||||
ContextState: &llb.State{},
|
||||
},
|
||||
Exports: []client.ExportEntry{
|
||||
{
|
||||
Type: client.ExporterLocal,
|
||||
OutputDir: dir1,
|
||||
Attrs: map[string]string{
|
||||
"mode": string(client.LocalExporterModeCopy),
|
||||
},
|
||||
},
|
||||
},
|
||||
ExportsLocalPathsTemporary: []string{dir1},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tcases {
|
||||
@@ -416,6 +486,47 @@ func TestValidateEntitlements(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateEntitlementsInvalidLocalOutputMode(t *testing.T) {
|
||||
_, err := EntitlementConf{}.Validate(map[string]build.Options{
|
||||
"test": {
|
||||
Inputs: build.Inputs{
|
||||
ContextState: &llb.State{},
|
||||
},
|
||||
Exports: []client.ExportEntry{
|
||||
{
|
||||
Type: client.ExporterLocal,
|
||||
Attrs: map[string]string{
|
||||
"mode": "backup",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.ErrorContains(t, err, `invalid local exporter mode "backup"`)
|
||||
}
|
||||
|
||||
func TestParseEntitlementsLocalOutputDelete(t *testing.T) {
|
||||
conf, err := ParseEntitlements([]string{string(EntitlementKeyLocalOutputDelete)})
|
||||
require.NoError(t, err)
|
||||
require.True(t, conf.LocalOutputDelete)
|
||||
|
||||
_, err = ParseEntitlements([]string{string(EntitlementKeyLocalOutputDelete) + "=true"})
|
||||
require.ErrorContains(t, err, "local-output-delete does not accept a value")
|
||||
}
|
||||
|
||||
func TestPromptLocalOutputDeleteCannotBeDisabledWithFSEntitlements(t *testing.T) {
|
||||
t.Setenv("BUILDX_BAKE_ENTITLEMENTS_FS", "0")
|
||||
|
||||
ctx, cancel := context.WithCancelCause(context.Background())
|
||||
cancel(nil)
|
||||
|
||||
var out bytes.Buffer
|
||||
err := EntitlementConf{LocalOutputDelete: true}.Prompt(ctx, true, &out)
|
||||
require.ErrorContains(t, err, "additional privileges requested")
|
||||
require.Contains(t, out.String(), "Deleting stale files from local output destinations")
|
||||
require.Contains(t, out.String(), "--allow=local-output-delete")
|
||||
}
|
||||
|
||||
func TestGroupSamePaths(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
+5
-1
@@ -329,7 +329,11 @@ func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in ba
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if progressMode != progressui.RawJSONMode {
|
||||
if progressMode == progressui.RawJSONMode {
|
||||
if exp.LocalOutputDelete {
|
||||
return errors.Errorf("additional privileges requested: pass %q to grant requested privileges", "--allow="+string(bake.EntitlementKeyLocalOutputDelete))
|
||||
}
|
||||
} else {
|
||||
if err := exp.Prompt(ctx, url != "", &syncWriter{w: dockerCli.Err(), wait: printer.Wait}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -901,6 +901,10 @@ target "default" {
|
||||
}
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> Local outputs with `mode=delete` require granting `--allow=local-output-delete`
|
||||
> when invoking `docker buildx bake`.
|
||||
|
||||
### `target.policy`
|
||||
|
||||
Policies to validate build sources and metadata. Each entry uses the same keys
|
||||
|
||||
@@ -85,6 +85,9 @@ The `fs` entitlements take a path value (relative or absolute) to a directory
|
||||
on the filesystem. Alternatively, you can pass a wildcard (`*`) to allow Bake
|
||||
to access the entire filesystem.
|
||||
|
||||
Bake also supports `--allow=local-output-delete` to grant local outputs
|
||||
permission to delete stale files when `mode=delete` is set.
|
||||
|
||||
### Example: fs.read
|
||||
|
||||
Given the following Bake configuration, Bake would need to access the parent
|
||||
|
||||
Reference in New Issue
Block a user