policy: enable proxy network from source policy caps

Evaluate source policy caps before solve requests so policies can enable
BuildKit proxy networking. Policy can return caps {"exec.proxy": true}
during the caps request to enable proxy network
support for the solve.

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
Tonis Tiigi
2026-06-10 15:04:23 +02:00
committed by CrazyMax
parent 2fcfce0e71
commit 4da04e3bf4
8 changed files with 431 additions and 57 deletions
+1
View File
@@ -140,6 +140,7 @@ type policyFileSpec struct {
type policyEvalOpt struct {
Strict bool
LogLevel *logrus.Level
SkipCaps bool
}
type policyOpt struct {
+26
View File
@@ -667,6 +667,7 @@ func configureSourcePolicy(ctx context.Context, np *noderesolver.ResolvedNode, o
Data: policy.DefaultPolicyData(),
}},
}
builtin.SkipCaps = true
popts = append([]policyOpt{builtin}, popts...)
}
@@ -742,6 +743,11 @@ func configureSourcePolicy(ctx context.Context, np *noderesolver.ResolvedNode, o
DefaultPlatform: defaultPlatform(bopts),
SourceResolver: sourceResolver,
})
if !popt.SkipCaps {
if err := applyPolicyCaps(ctx, p, bopts, so); err != nil {
return nil, err
}
}
policies = append(policies, p)
cbs = append(cbs, p.CheckPolicy)
if popt.Strict {
@@ -750,10 +756,30 @@ func configureSourcePolicy(ctx context.Context, np *noderesolver.ResolvedNode, o
}
}
}
if so.ProxyNetwork {
if policyLogger != nil {
policyLogger.Log("policy enabled network proxy")
}
}
so.SourcePolicyProvider = policysession.NewPolicyProvider(policy.MultiPolicyCallback(cbs...))
return defers, nil
}
func applyPolicyCaps(ctx context.Context, p *policy.Policy, bopts gateway.BuildOpts, so *client.SolveOpt) error {
caps, err := p.CheckCaps(ctx)
if err != nil {
return errors.Wrap(err, "failed to evaluate policy caps")
}
if !caps[policy.CapExecProxy] {
return nil
}
if err := bopts.LLBCaps.Supports(pb.CapExecMetaNetworkProxy); err != nil {
return errors.New("network proxy requested by policy is not supported by the current BuildKit daemon, please upgrade to version v0.31+")
}
so.ProxyNetwork = true
return nil
}
func policyEnvFilename(inp Inputs) string {
base := filepath.Base(filepath.Clean(inp.DockerfilePath))
if base != "." && base != string(filepath.Separator) {
+87
View File
@@ -5,11 +5,15 @@ import (
"sync"
"testing"
"github.com/docker/buildx/policy"
"github.com/docker/buildx/util/buildflags"
"github.com/docker/buildx/util/ocilayout"
"github.com/docker/buildx/util/progress"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/client/ociindex"
gateway "github.com/moby/buildkit/frontend/gateway/client"
"github.com/moby/buildkit/solver/pb"
"github.com/moby/buildkit/util/apicaps"
"github.com/opencontainers/go-digest"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
@@ -163,6 +167,67 @@ func TestProxyArgKeyExists(t *testing.T) {
}
}
func TestApplyPolicyCapsEnablesProxyNetwork(t *testing.T) {
p := policyWithDecision(`
package docker
decision := {
"allow": false,
"caps": {"exec.proxy": true},
}
`)
var so client.SolveOpt
err := applyPolicyCaps(context.Background(), p, buildOptsWithCaps(pb.CapExecMetaNetworkProxy), &so)
require.NoError(t, err)
require.True(t, so.ProxyNetwork)
}
func TestApplyPolicyCapsOrsProxyNetwork(t *testing.T) {
falsePolicy := policyWithDecision(`
package docker
decision := {
"allow": true,
"caps": {"exec.proxy": false},
}
`)
truePolicy := policyWithDecision(`
package docker
decision := {
"allow": true,
"caps": {"exec.proxy": true},
}
`)
var so client.SolveOpt
err := applyPolicyCaps(context.Background(), falsePolicy, buildOptsWithCaps(pb.CapExecMetaNetworkProxy), &so)
require.NoError(t, err)
require.False(t, so.ProxyNetwork)
err = applyPolicyCaps(context.Background(), truePolicy, buildOptsWithCaps(pb.CapExecMetaNetworkProxy), &so)
require.NoError(t, err)
require.True(t, so.ProxyNetwork)
}
func TestApplyPolicyCapsRequiresBuildKitCap(t *testing.T) {
p := policyWithDecision(`
package docker
decision := {
"allow": true,
"caps": {"exec.proxy": true},
}
`)
var so client.SolveOpt
err := applyPolicyCaps(context.Background(), p, buildOptsWithCaps(), &so)
require.ErrorContains(t, err, "network proxy requested by policy is not supported by the current BuildKit daemon")
require.ErrorContains(t, err, "please upgrade to version v0.31+")
require.False(t, so.ProxyNetwork)
}
func TestLoadInputsOCILayoutNamedContext(t *testing.T) {
layoutPath := t.TempDir()
@@ -299,3 +364,25 @@ func (w *captureProgressWriter) ValidateLogSource(digest.Digest, any) bool { ret
func (w *captureProgressWriter) ClearLogSource(any) {}
var _ progress.Writer = (*captureProgressWriter)(nil)
func policyWithDecision(decision string) *policy.Policy {
return policy.NewPolicy(policy.Opt{
Files: []policy.File{{
Filename: "policy.rego",
Data: []byte(decision),
}},
})
}
func buildOptsWithCaps(caps ...apicaps.CapID) gateway.BuildOpts {
out := make([]*apicaps.PBCap, 0, len(caps))
for _, c := range caps {
out = append(out, &apicaps.PBCap{
ID: string(c),
Enabled: true,
})
}
return gateway.BuildOpts{
LLBCaps: pb.Caps.CapSet(out),
}
}