From 4da04e3bf4933317130de50cf4edeb83686e6eca Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Tue, 9 Jun 2026 23:24:58 -0700 Subject: [PATCH 1/2] 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 --- build/build.go | 1 + build/opt.go | 26 ++++++ build/opt_test.go | 87 ++++++++++++++++++ policy/tester.go | 14 +++ policy/types.go | 20 +++- policy/validate.go | 197 +++++++++++++++++++++++++++++----------- policy/validate_test.go | 80 ++++++++++++++++ tests/policy_build.go | 63 +++++++++++++ 8 files changed, 431 insertions(+), 57 deletions(-) diff --git a/build/build.go b/build/build.go index 4297fc2ce..18b66f714 100644 --- a/build/build.go +++ b/build/build.go @@ -140,6 +140,7 @@ type policyFileSpec struct { type policyEvalOpt struct { Strict bool LogLevel *logrus.Level + SkipCaps bool } type policyOpt struct { diff --git a/build/opt.go b/build/opt.go index 8f0410aee..838c25505 100644 --- a/build/opt.go +++ b/build/opt.go @@ -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) { diff --git a/build/opt_test.go b/build/opt_test.go index 068755681..9327d211d 100644 --- a/build/opt_test.go +++ b/build/opt_test.go @@ -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), + } +} diff --git a/policy/tester.go b/policy/tester.go index 5cc193e62..4baae4cb2 100644 --- a/policy/tester.go +++ b/policy/tester.go @@ -683,9 +683,23 @@ func decodeDecision(decision any) *Decision { if len(denyMsgs) == 0 { denyMsgs = nil } + caps := Caps{} + if v, ok := obj["caps"]; ok { + if m, ok := v.(map[string]any); ok { + for k, entry := range m { + if b, ok := entry.(bool); ok { + caps[k] = b + } + } + } + } + if len(caps) == 0 { + caps = nil + } return &Decision{ Allow: allow, DenyMessages: denyMsgs, + Caps: caps, } } diff --git a/policy/types.go b/policy/types.go index d7f80a8f4..b0ae44daa 100644 --- a/policy/types.go +++ b/policy/types.go @@ -21,14 +21,24 @@ type Input struct { type Decision struct { Allow *bool `json:"allow,omitempty"` DenyMessages []string `json:"deny_msg,omitempty"` + Caps Caps `json:"caps,omitempty"` +} + +type Caps map[string]bool + +const CapExecProxy = "exec.proxy" + +var KnownCaps = map[string]struct{}{ + CapExecProxy: {}, } type Env struct { - Args map[string]*string `json:"args,omitempty"` - Labels map[string]string `json:"labels,omitempty"` - Filename string `json:"filename,omitempty"` - Target string `json:"target,omitempty"` - Depth int `json:"depth"` + Args map[string]*string `json:"args,omitempty"` + Labels map[string]string `json:"labels,omitempty"` + Filename string `json:"filename,omitempty"` + Target string `json:"target,omitempty"` + CapsRequest bool `json:"capsRequest,omitempty"` + Depth int `json:"depth"` } type HTTP struct { diff --git a/policy/validate.go b/policy/validate.go index 37d1470f8..0f8a7bfac 100644 --- a/policy/validate.go +++ b/policy/validate.go @@ -130,27 +130,7 @@ func (p *Policy) IsPolicyError(err error) bool { return false } -func (p *Policy) CheckPolicy(ctx context.Context, req *policysession.CheckPolicyRequest) (*policysession.DecisionResponse, *gwpb.ResolveSourceMetaRequest, error) { - if req.Source == nil || req.Source.Source == nil { - return nil, nil, errors.Errorf("no source info in request") - } - - var platform *ocispecs.Platform - if req.Platform != nil { - pl, err := platformFromReq(req) - if err != nil { - return nil, nil, err - } - platform = pl - } else { - platform = p.opt.DefaultPlatform - } - - inp, err := SourceToInput(ctx, p.opt.VerifierProvider, req.Source, platform, p.opt.Log) - if err != nil { - return nil, nil, errors.Wrap(err, "failed to build policy input") - } - +func (p *Policy) regoBaseOpts() ([]func(*rego.Rego), func(), error) { caps := &ast.Capabilities{ Builtins: builtins(), Features: slices.Clone(ast.Features), @@ -171,11 +151,11 @@ func (p *Policy) CheckPolicy(ctx context.Context, req *policysession.CheckPolicy var root fs.StatFS var closeFS func() error - defer func() { + closeRoot := func() { if closeFS != nil { closeFS() } - }() + } comp = comp.WithModuleLoader(func(resolved map[string]*ast.Module) (parsed map[string]*ast.Module, err error) { out := make(map[string]*ast.Module) @@ -240,6 +220,36 @@ func (p *Policy) CheckPolicy(ctx context.Context, req *policysession.CheckPolicy baseOpts = append(baseOpts, rego.Module(file.Filename, string(file.Data))) } + return baseOpts, closeRoot, nil +} + +func (p *Policy) CheckPolicy(ctx context.Context, req *policysession.CheckPolicyRequest) (*policysession.DecisionResponse, *gwpb.ResolveSourceMetaRequest, error) { + if req.Source == nil || req.Source.Source == nil { + return nil, nil, errors.Errorf("no source info in request") + } + + var platform *ocispecs.Platform + if req.Platform != nil { + pl, err := platformFromReq(req) + if err != nil { + return nil, nil, err + } + platform = pl + } else { + platform = p.opt.DefaultPlatform + } + + inp, err := SourceToInput(ctx, p.opt.VerifierProvider, req.Source, platform, p.opt.Log) + if err != nil { + return nil, nil, errors.Wrap(err, "failed to build policy input") + } + + baseOpts, closeRoot, err := p.regoBaseOpts() + if err != nil { + return nil, nil, err + } + defer closeRoot() + p.log(logrus.InfoLevel, "checking policy for source %s", sourceName(req)) for range maxResolveIterations { @@ -304,42 +314,23 @@ func (p *Policy) CheckPolicy(ctx context.Context, req *policysession.CheckPolicy continue } - if len(rs) == 0 { - return nil, nil, errors.Errorf("policy returned zero result") + decision, err := policyDecisionFromResult(rs) + if err != nil { + return nil, nil, err } - rsz := rs[0] - if len(rsz.Expressions) == 0 { - return nil, nil, errors.Errorf("policy returned zero expressions") - } - v := rsz.Expressions[0].Value - vt, ok := v.(map[string]any) - if !ok { - return nil, nil, errors.Errorf("unexpected policy return type: %T %s", vt, rsz.Expressions[0].Text) - } - resp := &policysession.DecisionResponse{ Action: moby_buildkit_v1_sourcepolicy.PolicyAction_DENY, } - p.log(logrus.DebugLevel, "policy response: %+v", vt) + p.log(logrus.DebugLevel, "policy response: %+v", decision) - if v, ok := vt["allow"]; ok { - if vv, ok := v.(bool); !ok { - return nil, nil, errors.Errorf("invalid allowed property type %T, expecting bool", v) - } else if vv { - resp.Action = moby_buildkit_v1_sourcepolicy.PolicyAction_ALLOW - } + if decision.Allow != nil && *decision.Allow { + resp.Action = moby_buildkit_v1_sourcepolicy.PolicyAction_ALLOW } - if v, ok := vt["deny_msg"]; ok { - if vv, ok := v.([]any); ok { - for _, m := range vv { - if m, ok := m.(string); ok { - resp.DenyMessages = append(resp.DenyMessages, &policysession.DenyMessage{ - Message: m, - }) - } - } - } + for _, m := range decision.DenyMessages { + resp.DenyMessages = append(resp.DenyMessages, &policysession.DenyMessage{ + Message: m, + }) } if resp.Action == moby_buildkit_v1_sourcepolicy.PolicyAction_ALLOW { @@ -373,6 +364,108 @@ func (p *Policy) CheckPolicy(ctx context.Context, req *policysession.CheckPolicy return nil, nil, errors.Errorf("maximum attempts reached for resolving policy metadata") } +func (p *Policy) CheckCaps(ctx context.Context) (Caps, error) { + baseOpts, closeRoot, err := p.regoBaseOpts() + if err != nil { + return nil, err + } + defer closeRoot() + + env := p.opt.Env + env.CapsRequest = true + runInput := Input{} + applyEnvWithDepth(&runInput, env, 0) + + runOpts := append([]func(*rego.Rego){}, baseOpts...) + runOpts = append(runOpts, rego.Input(runInput)) + + st := &state{Input: runInput} + for _, f := range p.funcs { + runOpts = append(runOpts, f.impl(st)) + } + + dt, err := json.MarshalIndent(runInput, "", " ") + if err != nil { + return nil, errors.Wrapf(err, "failed to marshal policy input") + } + p.log(logrus.DebugLevel, "policy input: %s", dt) + + rs, err := rego.New(runOpts...).Eval(ctx) + if err != nil { + return nil, err + } + if refs := runtimeUnknownInputRefs(st); len(refs) > 0 || st.checksumNeededForSignature != nil { + return nil, errors.Errorf("policy caps request cannot resolve source metadata: %+v", summarizeUnknownsForLog(refs)) + } + + decision, err := policyDecisionFromResult(rs) + if err != nil { + return nil, err + } + if len(decision.Caps) == 0 { + return nil, nil + } + return decision.Caps, nil +} + +func policyDecisionFromResult(rs rego.ResultSet) (*Decision, error) { + if len(rs) == 0 { + return nil, errors.Errorf("policy returned zero result") + } + rsz := rs[0] + if len(rsz.Expressions) == 0 { + return nil, errors.Errorf("policy returned zero expressions") + } + v := rsz.Expressions[0].Value + vt, ok := v.(map[string]any) + if !ok { + return nil, errors.Errorf("unexpected policy return type: %T %s", v, rsz.Expressions[0].Text) + } + return parsePolicyDecision(vt) +} + +func parsePolicyDecision(vt map[string]any) (*Decision, error) { + decision := &Decision{} + + if v, ok := vt["allow"]; ok { + vv, ok := v.(bool) + if !ok { + return nil, errors.Errorf("invalid allowed property type %T, expecting bool", v) + } + decision.Allow = &vv + } + + if v, ok := vt["deny_msg"]; ok { + if vv, ok := v.([]any); ok { + for _, m := range vv { + if m, ok := m.(string); ok { + decision.DenyMessages = append(decision.DenyMessages, m) + } + } + } + } + + if v, ok := vt["caps"]; ok { + vv, ok := v.(map[string]any) + if !ok { + return nil, errors.Errorf("invalid caps property type %T, expecting object", v) + } + decision.Caps = make(Caps, len(vv)) + for k, cv := range vv { + if _, ok := KnownCaps[k]; !ok { + return nil, errors.Errorf("unknown policy cap %q", k) + } + b, ok := cv.(bool) + if !ok { + return nil, errors.Errorf("invalid caps.%s property type %T, expecting bool", k, cv) + } + decision.Caps[k] = b + } + } + + return decision, nil +} + func (p *Policy) resolveUnknowns(ctx context.Context, input *Input, req *policysession.CheckPolicyRequest, defaultPlatform *ocispecs.Platform, unk []string, st *state) (bool, *gwpb.ResolveSourceMetaRequest, error) { var resolver SourceMetadataResolver if p.opt.SourceResolver != nil { diff --git a/policy/validate_test.go b/policy/validate_test.go index efb95918b..73e8a5664 100644 --- a/policy/validate_test.go +++ b/policy/validate_test.go @@ -883,6 +883,86 @@ func TestSourceToInputSingleSource(t *testing.T) { } } +func TestCheckCaps(t *testing.T) { + p := NewPolicy(Opt{ + Files: []File{{ + Filename: "policy.rego", + Data: []byte(` +package docker + +decision := { + "allow": false, + "deny_msg": ["ignored for caps"], + "caps": { + "exec.proxy": input.env.capsRequest, + }, +} if { + input.env.filename == "Dockerfile" + input.env.target == "release" + input.env.args.MODE == "prod" +} +`), + }}, + Env: Env{ + Args: map[string]*string{"MODE": stringPtr("prod")}, + Filename: "Dockerfile", + Target: "release", + }, + }) + + caps, err := p.CheckCaps(context.Background()) + require.NoError(t, err) + require.Equal(t, Caps{ + CapExecProxy: true, + }, caps) +} + +func TestCheckCapsMalformedCaps(t *testing.T) { + p := NewPolicy(Opt{ + Files: []File{{ + Filename: "policy.rego", + Data: []byte(` +package docker + +decision := { + "allow": true, + "caps": { + "exec.proxy": "yes", + }, +} +`), + }}, + }) + + _, err := p.CheckCaps(context.Background()) + require.ErrorContains(t, err, "invalid caps.exec.proxy property type string, expecting bool") +} + +func TestCheckCapsUnknownCaps(t *testing.T) { + p := NewPolicy(Opt{ + Files: []File{{ + Filename: "policy.rego", + Data: []byte(` +package docker + +decision := { + "allow": true, + "caps": { + "exec.unknown": true, + }, +} +`), + }}, + }) + + _, err := p.CheckCaps(context.Background()) + require.ErrorContains(t, err, `unknown policy cap "exec.unknown"`) +} + +func stringPtr(v string) *string { + return &v +} + func mustMarshalImageConfig(t *testing.T, img ocispecs.Image) []byte { t.Helper() dt, err := json.Marshal(img) diff --git a/tests/policy_build.go b/tests/policy_build.go index 35837416b..cbf9c8ac8 100644 --- a/tests/policy_build.go +++ b/tests/policy_build.go @@ -40,6 +40,69 @@ var policyBuildTests = []func(t *testing.T, sb integration.Sandbox){ testBuildPolicyRemotePolicyFiles, testBuildPolicyRemoteHTTPPolicyFiles, testBuildPolicyConfigFlags, + testBuildPolicyCapsProxy, + testBuildPolicyCapsProxyUnsupported, +} + +var policyCapsProxyFile = []byte(` +package docker +default allow = true +default caps := {} +caps := {"exec.proxy": true} if input.env.capsRequest +decision := {"allow": allow, "caps": caps} +`) + +func testBuildPolicyCapsProxy(t *testing.T, sb integration.Sandbox) { + if buildkitTag() != "master" { + skipNoCompatBuildKit(t, sb, ">= 0.31.0-0", "network proxy requires BuildKit v0.31.0+") + } + dockerfile := []byte(` +FROM scratch +COPY foo /foo +`) + dir := tmpdir( + t, + fstest.CreateFile("Dockerfile", dockerfile, 0600), + fstest.CreateFile("Dockerfile.rego", policyCapsProxyFile, 0600), + fstest.CreateFile("foo", []byte("foo"), 0600), + ) + + cmd := buildxCmd(sb, withDir(dir), withArgs( + "build", + "--progress=plain", + "--output=type=cacheonly", + dir, + )) + out, err := cmd.CombinedOutput() + require.NoError(t, err, string(out)) + require.Contains(t, string(out), "policy enabled network proxy") +} + +func testBuildPolicyCapsProxyUnsupported(t *testing.T, sb integration.Sandbox) { + skipNoCompatBuildKit(t, sb, ">= 0.26.0-0", "policy input requires BuildKit v0.26.0+") + if buildkitTag() == "master" || matchesBuildKitVersion(t, sb, ">= 0.31.0-0") { + t.Skip("BuildKit daemon supports network proxy") + } + dockerfile := []byte(` +FROM scratch +COPY foo /foo +`) + dir := tmpdir( + t, + fstest.CreateFile("Dockerfile", dockerfile, 0600), + fstest.CreateFile("Dockerfile.rego", policyCapsProxyFile, 0600), + fstest.CreateFile("foo", []byte("foo"), 0600), + ) + + cmd := buildxCmd(sb, withDir(dir), withArgs( + "build", + "--progress=plain", + "--output=type=cacheonly", + dir, + )) + out, err := cmd.CombinedOutput() + require.Error(t, err, string(out)) + require.Contains(t, string(out), "network proxy requested by policy is not supported by the current BuildKit daemon") } func testBuildPolicyAllow(t *testing.T, sb integration.Sandbox) { From 25db0fb0509022b001a477b1cdc172e8dbdeb632 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Tue, 9 Jun 2026 23:50:46 -0700 Subject: [PATCH 2/2] tests: verify exec traffic is policy-checked in caps proxy test Extend testBuildPolicyCapsProxy so that instead of only checking that the network proxy was enabled, the build runs a command that makes an HTTP request to a local test server. The policy denies that URL as an HTTP source, so the test now verifies that exec traffic actually flows through the proxy and is subject to source policy, including the deny message and DENY decision in the build output. Signed-off-by: Tonis Tiigi --- tests/policy_build.go | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/tests/policy_build.go b/tests/policy_build.go index cbf9c8ac8..3c1fabf35 100644 --- a/tests/policy_build.go +++ b/tests/policy_build.go @@ -44,27 +44,37 @@ var policyBuildTests = []func(t *testing.T, sb integration.Sandbox){ testBuildPolicyCapsProxyUnsupported, } -var policyCapsProxyFile = []byte(` +func policyCapsProxyFile(serverURL string) []byte { + return fmt.Appendf(nil, ` package docker default allow = true +default deny_msg := [] default caps := {} caps := {"exec.proxy": true} if input.env.capsRequest -decision := {"allow": allow, "caps": caps} -`) +allow := false if input.http.url == "%s/file" +deny_msg := ["exec proxy http source denied by policy"] if input.http.url == "%s/file" +decision := {"allow": allow, "deny_msg": deny_msg, "caps": caps} +`, serverURL, serverURL) +} func testBuildPolicyCapsProxy(t *testing.T, sb integration.Sandbox) { if buildkitTag() != "master" { skipNoCompatBuildKit(t, sb, ">= 0.31.0-0", "network proxy requires BuildKit v0.31.0+") } + resp := &httpserver.Response{Content: []byte("policy-caps-proxy")} + server := httpserver.NewTestServer(map[string]*httpserver.Response{ + "/file": resp, + }) + defer server.Close() + dockerfile := []byte(` -FROM scratch -COPY foo /foo +FROM busybox:latest +RUN wget -O- ` + server.URL + `/file `) dir := tmpdir( t, fstest.CreateFile("Dockerfile", dockerfile, 0600), - fstest.CreateFile("Dockerfile.rego", policyCapsProxyFile, 0600), - fstest.CreateFile("foo", []byte("foo"), 0600), + fstest.CreateFile("Dockerfile.rego", policyCapsProxyFile(server.URL), 0600), ) cmd := buildxCmd(sb, withDir(dir), withArgs( @@ -74,8 +84,11 @@ COPY foo /foo dir, )) out, err := cmd.CombinedOutput() - require.NoError(t, err, string(out)) + require.Error(t, err, string(out)) require.Contains(t, string(out), "policy enabled network proxy") + require.Contains(t, string(out), "exec proxy http source denied by policy") + require.Contains(t, string(out), "policy decision for source "+server.URL+"/file") + require.Contains(t, string(out), "DENY") } func testBuildPolicyCapsProxyUnsupported(t *testing.T, sb integration.Sandbox) { @@ -90,7 +103,7 @@ COPY foo /foo dir := tmpdir( t, fstest.CreateFile("Dockerfile", dockerfile, 0600), - fstest.CreateFile("Dockerfile.rego", policyCapsProxyFile, 0600), + fstest.CreateFile("Dockerfile.rego", policyCapsProxyFile(""), 0600), fstest.CreateFile("foo", []byte("foo"), 0600), )