policy: add multiple policy support

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
Tonis Tiigi
2026-01-14 09:09:10 -08:00
parent 4af8cdb6af
commit 0e1036bef5
4 changed files with 229 additions and 34 deletions
+48 -33
View File
@@ -327,7 +327,11 @@ func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt *O
}
defers = append(defers, releaseLoad)
if opt.Inputs.policy != nil {
if opt.Inputs.policy == nil {
if len(opt.Policy) > 0 {
return nil, nil, errors.New("policy file specified but no policy FS in build context")
}
} else {
env := policy.Env{}
for k, v := range opt.BuildArgs {
if env.Args == nil {
@@ -338,16 +342,25 @@ func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt *O
env.Filename = path.Base(opt.Inputs.DockerfilePath)
env.Target = opt.Target
env.Labels = opt.Labels
p := policy.NewPolicy(policy.Opt{
Files: opt.Inputs.policy.Files,
Env: env,
Log: func(msg string) {
log.Printf("[policy] %s", msg)
},
FS: opt.Inputs.policy.FS,
Config: cfg,
})
so.SourcePolicyProvider = policysession.NewPolicyProvider(p.CheckPolicy)
popts, err := withPolicyConfig(*opt.Inputs.policy, opt.Policy)
if err != nil {
return nil, nil, err
}
var cbs []policysession.PolicyCallback
for _, popt := range popts {
p := policy.NewPolicy(policy.Opt{
Files: popt.Files,
Env: env,
Log: func(msg string) {
log.Printf("[policy] %s", msg)
},
FS: opt.Inputs.policy.FS,
Config: cfg,
})
cbs = append(cbs, p.CheckPolicy)
}
so.SourcePolicyProvider = policysession.NewPolicyProvider(policy.MultiPolicyCallback(cbs...))
}
// add node identifier to shared key if one was specified
@@ -561,6 +574,25 @@ func loadInputs(ctx context.Context, d *driver.DriverHandle, inp *Inputs, pw pro
dockerfileName = "Dockerfile"
}
p := &policyOpt{
FS: func() (fs.StatFS, func() error, error) {
if contextDir == "" {
return nil, nil, errors.Errorf("unimplemented, cannot use policy file without a local build context")
}
root, err := os.OpenRoot(contextDir)
if err != nil {
return nil, nil, errors.Wrapf(err, "failed to open root for policy file %s.rego", dockerfileName)
}
baseFS := root.FS()
statFS, ok := baseFS.(fs.StatFS)
if !ok {
root.Close()
return nil, nil, errors.Errorf("invalid root FS type %T", baseFS)
}
return statFS, root.Close, nil
},
}
if dockerfileDir != "" {
if err := setLocalMount("dockerfile", dockerfileDir, target); err != nil {
return nil, err
@@ -573,33 +605,16 @@ func loadInputs(ctx context.Context, d *driver.DriverHandle, inp *Inputs, pw pro
if err != nil {
return nil, errors.Wrapf(err, "failed to read policy file %s.rego", dockerfileName)
}
inp.policy = &policyOpt{
Files: []policy.File{
{
Filename: dockerfileName + ".rego",
Data: dt,
},
},
FS: func() (fs.StatFS, func() error, error) {
if contextDir == "" {
return nil, nil, errors.Errorf("unimplemented, cannot use policy file without a local build context")
}
root, err := os.OpenRoot(contextDir)
if err != nil {
return nil, nil, errors.Wrapf(err, "failed to open root for policy file %s.rego", dockerfileName)
}
baseFS := root.FS()
statFS, ok := baseFS.(fs.StatFS)
if !ok {
root.Close()
return nil, nil, errors.Errorf("invalid root FS type %T", baseFS)
}
return statFS, root.Close, nil
p.Files = []policy.File{
{
Filename: dockerfileName + ".rego",
Data: dt,
},
}
}
}
}
inp.policy = p
target.FrontendAttrs["filename"] = dockerfileName
+5 -1
View File
@@ -260,7 +260,11 @@ func parsePolicyConfigs(in []string) ([]build.PolicyConfig, error) {
if value == "" {
return nil, errors.Errorf("invalid value %s", field)
}
cfg.Files = append(cfg.Files, policy.File{Filename: value})
dt, err := os.ReadFile(value)
if err != nil {
return nil, errors.Wrapf(err, "failed to read policy file %s", value)
}
cfg.Files = append(cfg.Files, policy.File{Filename: value, Data: dt})
case "reset":
b, err := strconv.ParseBool(value)
if err != nil {
+47
View File
@@ -0,0 +1,47 @@
package policy
import (
"context"
gwpb "github.com/moby/buildkit/frontend/gateway/pb"
moby_buildkit_v1_sourcepolicy "github.com/moby/buildkit/sourcepolicy/pb"
"github.com/moby/buildkit/sourcepolicy/policysession"
)
// MultiPolicyCallback returns a policy callback that requires all policies to allow.
func MultiPolicyCallback(callbacks ...policysession.PolicyCallback) policysession.PolicyCallback {
return func(ctx context.Context, req *policysession.CheckPolicyRequest) (*policysession.DecisionResponse, *gwpb.ResolveSourceMetaRequest, error) {
if len(callbacks) == 0 {
return &policysession.DecisionResponse{Action: moby_buildkit_v1_sourcepolicy.PolicyAction_ALLOW}, nil, nil
}
for _, cb := range callbacks {
if cb == nil {
continue
}
decision, metaReq, err := cb(ctx, req)
if err != nil {
return nil, nil, err
}
if metaReq != nil {
return nil, metaReq, nil
}
if decision == nil {
continue
}
switch decision.Action {
case moby_buildkit_v1_sourcepolicy.PolicyAction_DENY:
return decision, nil, nil
case moby_buildkit_v1_sourcepolicy.PolicyAction_CONVERT:
return decision, nil, nil
case moby_buildkit_v1_sourcepolicy.PolicyAction_ALLOW:
// noop
default:
// treat unknown actions as allow
}
}
return &policysession.DecisionResponse{Action: moby_buildkit_v1_sourcepolicy.PolicyAction_ALLOW}, nil, nil
}
}
+129
View File
@@ -0,0 +1,129 @@
package policy
import (
"context"
"testing"
gwpb "github.com/moby/buildkit/frontend/gateway/pb"
moby_buildkit_v1_sourcepolicy "github.com/moby/buildkit/sourcepolicy/pb"
"github.com/moby/buildkit/sourcepolicy/policysession"
solverpb "github.com/moby/buildkit/solver/pb"
"github.com/stretchr/testify/require"
)
func TestMultiPolicyCallbackNoCallbacks(t *testing.T) {
cb := MultiPolicyCallback()
decision, meta, err := cb(context.Background(), &policysession.CheckPolicyRequest{})
require.NoError(t, err)
require.Nil(t, meta)
require.NotNil(t, decision)
require.Equal(t, moby_buildkit_v1_sourcepolicy.PolicyAction_ALLOW, decision.Action)
}
func TestMultiPolicyCallbackAllowOnly(t *testing.T) {
cb := MultiPolicyCallback(func(context.Context, *policysession.CheckPolicyRequest) (*policysession.DecisionResponse, *gwpb.ResolveSourceMetaRequest, error) {
return &policysession.DecisionResponse{Action: moby_buildkit_v1_sourcepolicy.PolicyAction_ALLOW}, nil, nil
})
decision, meta, err := cb(context.Background(), &policysession.CheckPolicyRequest{})
require.NoError(t, err)
require.Nil(t, meta)
require.NotNil(t, decision)
require.Equal(t, moby_buildkit_v1_sourcepolicy.PolicyAction_ALLOW, decision.Action)
}
func TestMultiPolicyCallbackDenyWins(t *testing.T) {
denyMsgA := &policysession.DenyMessage{Message: "nope-a"}
denyMsgB := &policysession.DenyMessage{Message: "nope-b"}
cb := MultiPolicyCallback(
func(context.Context, *policysession.CheckPolicyRequest) (*policysession.DecisionResponse, *gwpb.ResolveSourceMetaRequest, error) {
return &policysession.DecisionResponse{Action: moby_buildkit_v1_sourcepolicy.PolicyAction_ALLOW}, nil, nil
},
func(context.Context, *policysession.CheckPolicyRequest) (*policysession.DecisionResponse, *gwpb.ResolveSourceMetaRequest, error) {
return &policysession.DecisionResponse{
Action: moby_buildkit_v1_sourcepolicy.PolicyAction_DENY,
DenyMessages: []*policysession.DenyMessage{denyMsgA, denyMsgB},
}, nil, nil
},
)
decision, meta, err := cb(context.Background(), &policysession.CheckPolicyRequest{})
require.NoError(t, err)
require.Nil(t, meta)
require.NotNil(t, decision)
require.Equal(t, moby_buildkit_v1_sourcepolicy.PolicyAction_DENY, decision.Action)
require.Equal(t, []*policysession.DenyMessage{denyMsgA, denyMsgB}, decision.DenyMessages)
}
func TestMultiPolicyCallbackDenyShortCircuits(t *testing.T) {
called := false
cb := MultiPolicyCallback(
func(context.Context, *policysession.CheckPolicyRequest) (*policysession.DecisionResponse, *gwpb.ResolveSourceMetaRequest, error) {
return &policysession.DecisionResponse{Action: moby_buildkit_v1_sourcepolicy.PolicyAction_DENY}, nil, nil
},
func(context.Context, *policysession.CheckPolicyRequest) (*policysession.DecisionResponse, *gwpb.ResolveSourceMetaRequest, error) {
called = true
return &policysession.DecisionResponse{Action: moby_buildkit_v1_sourcepolicy.PolicyAction_ALLOW}, nil, nil
},
)
decision, meta, err := cb(context.Background(), &policysession.CheckPolicyRequest{})
require.NoError(t, err)
require.Nil(t, meta)
require.NotNil(t, decision)
require.Equal(t, moby_buildkit_v1_sourcepolicy.PolicyAction_DENY, decision.Action)
require.False(t, called)
}
func TestMultiPolicyCallbackUpdateReturned(t *testing.T) {
update := &solverpb.SourceOp{}
cb := MultiPolicyCallback(
func(context.Context, *policysession.CheckPolicyRequest) (*policysession.DecisionResponse, *gwpb.ResolveSourceMetaRequest, error) {
return &policysession.DecisionResponse{Action: moby_buildkit_v1_sourcepolicy.PolicyAction_ALLOW}, nil, nil
},
func(context.Context, *policysession.CheckPolicyRequest) (*policysession.DecisionResponse, *gwpb.ResolveSourceMetaRequest, error) {
return &policysession.DecisionResponse{
Action: moby_buildkit_v1_sourcepolicy.PolicyAction_CONVERT,
Update: update,
}, nil, nil
},
)
decision, meta, err := cb(context.Background(), &policysession.CheckPolicyRequest{})
require.NoError(t, err)
require.Nil(t, meta)
require.NotNil(t, decision)
require.Equal(t, moby_buildkit_v1_sourcepolicy.PolicyAction_CONVERT, decision.Action)
require.Equal(t, update, decision.Update)
}
func TestMultiPolicyCallbackMetaRequest(t *testing.T) {
metaReq := &gwpb.ResolveSourceMetaRequest{}
cb := MultiPolicyCallback(
func(context.Context, *policysession.CheckPolicyRequest) (*policysession.DecisionResponse, *gwpb.ResolveSourceMetaRequest, error) {
return nil, metaReq, nil
},
func(context.Context, *policysession.CheckPolicyRequest) (*policysession.DecisionResponse, *gwpb.ResolveSourceMetaRequest, error) {
return &policysession.DecisionResponse{Action: moby_buildkit_v1_sourcepolicy.PolicyAction_DENY}, nil, nil
},
)
decision, meta, err := cb(context.Background(), &policysession.CheckPolicyRequest{})
require.NoError(t, err)
require.Nil(t, decision)
require.Equal(t, metaReq, meta)
}
func TestMultiPolicyCallbackNilCallbackIgnored(t *testing.T) {
cb := MultiPolicyCallback(nil, func(context.Context, *policysession.CheckPolicyRequest) (*policysession.DecisionResponse, *gwpb.ResolveSourceMetaRequest, error) {
return &policysession.DecisionResponse{Action: moby_buildkit_v1_sourcepolicy.PolicyAction_ALLOW}, nil, nil
})
decision, meta, err := cb(context.Background(), &policysession.CheckPolicyRequest{})
require.NoError(t, err)
require.Nil(t, meta)
require.NotNil(t, decision)
require.Equal(t, moby_buildkit_v1_sourcepolicy.PolicyAction_ALLOW, decision.Action)
}