mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-05-20 10:27:46 +08:00
bake: add fs entitlements for context paths
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
parent
1af4f05ba4
commit
9a7b028bab
67
bake/bake.go
67
bake/bake.go
@ -1117,62 +1117,36 @@ func updateContext(t *build.Inputs, inp *Input) {
|
|||||||
t.ContextState = &st
|
t.ContextState = &st
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateContextsEntitlements is a basic check to ensure contexts do not
|
func collectLocalPaths(t build.Inputs) []string {
|
||||||
// escape local directories when loaded from remote sources. This is to be
|
var out []string
|
||||||
// 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 t.ContextState == nil {
|
||||||
if err := checkPath(t.ContextPath); err != nil {
|
if v, ok := isLocalPath(t.ContextPath); ok {
|
||||||
return err
|
out = append(out, v)
|
||||||
|
}
|
||||||
|
if v, ok := isLocalPath(t.DockerfilePath); ok {
|
||||||
|
out = append(out, v)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if strings.HasPrefix(t.ContextPath, "cwd://") {
|
||||||
|
out = append(out, strings.TrimPrefix(t.ContextPath, "cwd://"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, v := range t.NamedContexts {
|
for _, v := range t.NamedContexts {
|
||||||
if v.State != nil {
|
if v.State != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := checkPath(v.Path); err != nil {
|
if v, ok := isLocalPath(v.Path); ok {
|
||||||
return err
|
out = append(out, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkPath(p string) error {
|
func isLocalPath(p string) (string, bool) {
|
||||||
if build.IsRemoteURL(p) || strings.HasPrefix(p, "target:") || strings.HasPrefix(p, "docker-image:") {
|
if build.IsRemoteURL(p) || strings.HasPrefix(p, "target:") || strings.HasPrefix(p, "docker-image:") {
|
||||||
return nil
|
return "", false
|
||||||
}
|
}
|
||||||
p, err := filepath.EvalSymlinks(p)
|
return strings.TrimPrefix(p, "cwd://"), true
|
||||||
if err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
p, err = filepath.Abs(p)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
wd, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
rel, err := filepath.Rel(wd, p)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
parts := strings.Split(rel, string(os.PathSeparator))
|
|
||||||
if parts[0] == ".." {
|
|
||||||
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) {
|
func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
|
||||||
@ -1212,9 +1186,6 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
|
|||||||
// it's not outside the working directory and then resolve it to an
|
// it's not outside the working directory and then resolve it to an
|
||||||
// absolute path.
|
// absolute path.
|
||||||
bi.DockerfilePath = path.Clean(strings.TrimPrefix(bi.DockerfilePath, "cwd://"))
|
bi.DockerfilePath = path.Clean(strings.TrimPrefix(bi.DockerfilePath, "cwd://"))
|
||||||
if err := checkPath(bi.DockerfilePath); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var err error
|
var err error
|
||||||
bi.DockerfilePath, err = filepath.Abs(bi.DockerfilePath)
|
bi.DockerfilePath, err = filepath.Abs(bi.DockerfilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1251,10 +1222,6 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validateContextsEntitlements(bi, inp); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Context = &bi.ContextPath
|
t.Context = &bi.ContextPath
|
||||||
|
|
||||||
args := map[string]string{}
|
args := map[string]string{}
|
||||||
|
@ -108,6 +108,10 @@ func (c EntitlementConf) check(bo build.Options, expected *EntitlementConf) erro
|
|||||||
rwPaths := map[string]struct{}{}
|
rwPaths := map[string]struct{}{}
|
||||||
roPaths := map[string]struct{}{}
|
roPaths := map[string]struct{}{}
|
||||||
|
|
||||||
|
for _, p := range collectLocalPaths(bo.Inputs) {
|
||||||
|
roPaths[p] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
for _, out := range bo.Exports {
|
for _, out := range bo.Exports {
|
||||||
if out.Type == "local" {
|
if out.Type == "local" {
|
||||||
if dest, ok := out.Attrs["dest"]; ok {
|
if dest, ok := out.Attrs["dest"]; ok {
|
||||||
@ -167,7 +171,7 @@ func (c EntitlementConf) check(bo build.Options, expected *EntitlementConf) erro
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c EntitlementConf) Prompt(ctx context.Context, out io.Writer) error {
|
func (c EntitlementConf) Prompt(ctx context.Context, isRemote bool, out io.Writer) error {
|
||||||
var term bool
|
var term bool
|
||||||
if _, err := console.ConsoleFromFile(os.Stdin); err == nil {
|
if _, err := console.ConsoleFromFile(os.Stdin); err == nil {
|
||||||
term = true
|
term = true
|
||||||
@ -195,6 +199,17 @@ func (c EntitlementConf) Prompt(ctx context.Context, out io.Writer) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
roPaths, rwPaths, commonPaths := groupSamePaths(c.FSRead, c.FSWrite)
|
roPaths, rwPaths, commonPaths := groupSamePaths(c.FSRead, c.FSWrite)
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to get current working directory")
|
||||||
|
}
|
||||||
|
wd, err = filepath.EvalSymlinks(wd)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to evaluate working directory")
|
||||||
|
}
|
||||||
|
roPaths = toRelativePaths(roPaths, wd)
|
||||||
|
rwPaths = toRelativePaths(rwPaths, wd)
|
||||||
|
commonPaths = toRelativePaths(commonPaths, wd)
|
||||||
|
|
||||||
if len(commonPaths) > 0 {
|
if len(commonPaths) > 0 {
|
||||||
for _, p := range commonPaths {
|
for _, p := range commonPaths {
|
||||||
@ -251,6 +266,17 @@ func (c EntitlementConf) Prompt(ctx context.Context, out io.Writer) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fsEntitlementsEnabled := false
|
fsEntitlementsEnabled := false
|
||||||
|
if isRemote {
|
||||||
|
if v, ok := os.LookupEnv("BAKE_ALLOW_REMOTE_FS_ACCESS"); ok {
|
||||||
|
vv, err := strconv.ParseBool(v)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to parse BAKE_ALLOW_REMOTE_FS_ACCESS value %q", v)
|
||||||
|
}
|
||||||
|
fsEntitlementsEnabled = !vv
|
||||||
|
} else {
|
||||||
|
fsEntitlementsEnabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
v, fsEntitlementsSet := os.LookupEnv("BUILDX_BAKE_ENTITLEMENTS_FS")
|
v, fsEntitlementsSet := os.LookupEnv("BUILDX_BAKE_ENTITLEMENTS_FS")
|
||||||
if fsEntitlementsSet {
|
if fsEntitlementsSet {
|
||||||
vv, err := strconv.ParseBool(v)
|
vv, err := strconv.ParseBool(v)
|
||||||
@ -334,11 +360,6 @@ loop0:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func dedupPaths(in map[string]struct{}) (map[string]struct{}, error) {
|
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))
|
arr := make([]string, 0, len(in))
|
||||||
for p := range in {
|
for p := range in {
|
||||||
arr = append(arr, filepath.Clean(p))
|
arr = append(arr, filepath.Clean(p))
|
||||||
@ -358,18 +379,23 @@ loop0:
|
|||||||
}
|
}
|
||||||
m[p] = struct{}{}
|
m[p] = struct{}{}
|
||||||
}
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
for p := range m {
|
func toRelativePaths(in []string, wd string) []string {
|
||||||
|
out := make([]string, 0, len(in))
|
||||||
|
for _, p := range in {
|
||||||
rel, err := filepath.Rel(wd, p)
|
rel, err := filepath.Rel(wd, p)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if !strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
|
// allow up to one level of ".." in the path
|
||||||
delete(m, p)
|
if !strings.HasPrefix(rel, ".."+string(filepath.Separator)+"..") {
|
||||||
m[rel] = struct{}{}
|
out = append(out, rel)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
out = append(out, p)
|
||||||
}
|
}
|
||||||
|
return out
|
||||||
return m, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func groupSamePaths(in1, in2 []string) ([]string, []string, []string) {
|
func groupSamePaths(in1, in2 []string) ([]string, []string, []string) {
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/docker/buildx/build"
|
"github.com/docker/buildx/build"
|
||||||
"github.com/docker/buildx/controller/pb"
|
"github.com/docker/buildx/controller/pb"
|
||||||
"github.com/moby/buildkit/client"
|
"github.com/moby/buildkit/client"
|
||||||
|
"github.com/moby/buildkit/client/llb"
|
||||||
"github.com/moby/buildkit/util/entitlements"
|
"github.com/moby/buildkit/util/entitlements"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@ -137,10 +138,12 @@ func TestDedupePaths(t *testing.T) {
|
|||||||
filepath.Join(wd, "a/b"): {},
|
filepath.Join(wd, "a/b"): {},
|
||||||
filepath.Join(wd, "a/b/d"): {},
|
filepath.Join(wd, "a/b/d"): {},
|
||||||
filepath.Join(wd, "../aa/b"): {},
|
filepath.Join(wd, "../aa/b"): {},
|
||||||
|
filepath.Join(wd, "../../bb"): {},
|
||||||
},
|
},
|
||||||
out: map[string]struct{}{
|
out: map[string]struct{}{
|
||||||
"a/b": {},
|
"a/b": {},
|
||||||
filepath.Join(wd, "../aa"): {},
|
"../aa": {},
|
||||||
|
filepath.Join(wd, "../../bb"): {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -151,7 +154,18 @@ func TestDedupePaths(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
require.Equal(t, tc.out, out)
|
// convert to relative paths as that is shown to user
|
||||||
|
arr := make([]string, 0, len(out))
|
||||||
|
for k := range out {
|
||||||
|
arr = append(arr, k)
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
arr = toRelativePaths(arr, wd)
|
||||||
|
m := make(map[string]struct{})
|
||||||
|
for _, v := range arr {
|
||||||
|
m[v] = struct{}{}
|
||||||
|
}
|
||||||
|
require.Equal(t, tc.out, m)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -164,6 +178,9 @@ func TestValidateEntitlements(t *testing.T) {
|
|||||||
err := os.Symlink("../../aa", escapeLink)
|
err := os.Symlink("../../aa", escapeLink)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
tcases := []struct {
|
tcases := []struct {
|
||||||
name string
|
name string
|
||||||
conf EntitlementConf
|
conf EntitlementConf
|
||||||
@ -172,6 +189,11 @@ func TestValidateEntitlements(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "No entitlements",
|
name: "No entitlements",
|
||||||
|
opt: build.Options{
|
||||||
|
Inputs: build.Inputs{
|
||||||
|
ContextState: &llb.State{},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "NetworkHostMissing",
|
name: "NetworkHostMissing",
|
||||||
@ -182,6 +204,7 @@ func TestValidateEntitlements(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expected: EntitlementConf{
|
expected: EntitlementConf{
|
||||||
NetworkHost: true,
|
NetworkHost: true,
|
||||||
|
FSRead: []string{wd},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -194,7 +217,9 @@ func TestValidateEntitlements(t *testing.T) {
|
|||||||
entitlements.EntitlementNetworkHost,
|
entitlements.EntitlementNetworkHost,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: EntitlementConf{},
|
expected: EntitlementConf{
|
||||||
|
FSRead: []string{wd},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "SecurityAndNetworkHostMissing",
|
name: "SecurityAndNetworkHostMissing",
|
||||||
@ -207,6 +232,7 @@ func TestValidateEntitlements(t *testing.T) {
|
|||||||
expected: EntitlementConf{
|
expected: EntitlementConf{
|
||||||
NetworkHost: true,
|
NetworkHost: true,
|
||||||
SecurityInsecure: true,
|
SecurityInsecure: true,
|
||||||
|
FSRead: []string{wd},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -222,6 +248,7 @@ func TestValidateEntitlements(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expected: EntitlementConf{
|
expected: EntitlementConf{
|
||||||
SecurityInsecure: true,
|
SecurityInsecure: true,
|
||||||
|
FSRead: []string{wd},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -235,6 +262,7 @@ func TestValidateEntitlements(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expected: EntitlementConf{
|
expected: EntitlementConf{
|
||||||
SSH: true,
|
SSH: true,
|
||||||
|
FSRead: []string{wd},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -267,6 +295,7 @@ func TestValidateEntitlements(t *testing.T) {
|
|||||||
slices.Sort(exp)
|
slices.Sort(exp)
|
||||||
return exp
|
return exp
|
||||||
}(),
|
}(),
|
||||||
|
FSRead: []string{wd},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -279,7 +308,7 @@ func TestValidateEntitlements(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
conf: EntitlementConf{
|
conf: EntitlementConf{
|
||||||
FSRead: []string{dir1},
|
FSRead: []string{wd, dir1},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -292,7 +321,7 @@ func TestValidateEntitlements(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
conf: EntitlementConf{
|
conf: EntitlementConf{
|
||||||
FSRead: []string{dir1},
|
FSRead: []string{wd, dir1},
|
||||||
},
|
},
|
||||||
expected: EntitlementConf{
|
expected: EntitlementConf{
|
||||||
FSRead: []string{filepath.Join(dir1, "../..")},
|
FSRead: []string{filepath.Join(dir1, "../..")},
|
||||||
|
@ -257,7 +257,7 @@ func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in ba
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := exp.Prompt(ctx, &syncWriter{w: dockerCli.Err(), wait: printer.Wait}); err != nil {
|
if err := exp.Prompt(ctx, url != "", &syncWriter{w: dockerCli.Err(), wait: printer.Wait}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if printer.IsDone() {
|
if printer.IsDone() {
|
||||||
|
@ -508,7 +508,8 @@ EOT
|
|||||||
withArgs(addr, "--set", "*.output=type=local,dest="+dirDest),
|
withArgs(addr, "--set", "*.output=type=local,dest="+dirDest),
|
||||||
)
|
)
|
||||||
require.Error(t, err, out)
|
require.Error(t, err, out)
|
||||||
require.Contains(t, out, "outside of the working directory, please set BAKE_ALLOW_REMOTE_FS_ACCESS")
|
require.Contains(t, out, "Your build is requesting privileges for following possibly insecure capabilities")
|
||||||
|
require.Contains(t, out, "Read access to path ../")
|
||||||
|
|
||||||
out, err = bakeCmd(
|
out, err = bakeCmd(
|
||||||
sb,
|
sb,
|
||||||
@ -555,7 +556,8 @@ EOT
|
|||||||
withArgs(addr, "--set", "*.output=type=local,dest="+dirDest),
|
withArgs(addr, "--set", "*.output=type=local,dest="+dirDest),
|
||||||
)
|
)
|
||||||
require.Error(t, err, out)
|
require.Error(t, err, out)
|
||||||
require.Contains(t, out, "outside of the working directory, please set BAKE_ALLOW_REMOTE_FS_ACCESS")
|
require.Contains(t, out, "Your build is requesting privileges for following possibly insecure capabilities")
|
||||||
|
require.Contains(t, out, "Read access to path ..")
|
||||||
|
|
||||||
out, err = bakeCmd(
|
out, err = bakeCmd(
|
||||||
sb,
|
sb,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user