build: resolve policy files from context state and unified FS
Support remote context policy files via resolved context state and cwd:// override. Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
+20
-7
@@ -8,7 +8,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"maps"
|
||||
"os"
|
||||
"slices"
|
||||
@@ -22,7 +21,6 @@ import (
|
||||
noderesolver "github.com/docker/buildx/build/resolver"
|
||||
"github.com/docker/buildx/builder"
|
||||
"github.com/docker/buildx/driver"
|
||||
"github.com/docker/buildx/policy"
|
||||
"github.com/docker/buildx/util/buildflags"
|
||||
"github.com/docker/buildx/util/confutil"
|
||||
"github.com/docker/buildx/util/desktop"
|
||||
@@ -120,13 +118,24 @@ type Inputs struct {
|
||||
policy *policyOpt
|
||||
}
|
||||
|
||||
type policyOpt struct {
|
||||
Files []policy.File
|
||||
FS func() (fs.StatFS, func() error, error)
|
||||
type policyFileSpec struct {
|
||||
Filename string
|
||||
Optional bool
|
||||
Data []byte
|
||||
}
|
||||
|
||||
type policyEvalOpt struct {
|
||||
Strict bool
|
||||
LogLevel *logrus.Level
|
||||
}
|
||||
|
||||
type policyOpt struct {
|
||||
Files []policyFileSpec
|
||||
ContextDir string
|
||||
ContextState *llb.State
|
||||
policyEvalOpt
|
||||
}
|
||||
|
||||
func withPolicyConfig(defaultPolicy policyOpt, configs []buildflags.PolicyConfig) ([]policyOpt, error) {
|
||||
if len(configs) == 0 {
|
||||
if len(defaultPolicy.Files) == 0 {
|
||||
@@ -176,7 +185,10 @@ func withPolicyConfig(defaultPolicy policyOpt, configs []buildflags.PolicyConfig
|
||||
}
|
||||
|
||||
opt := policyOpt{
|
||||
Files: cfg.Files,
|
||||
Files: make([]policyFileSpec, 0, len(cfg.Files)),
|
||||
}
|
||||
for _, f := range cfg.Files {
|
||||
opt.Files = append(opt.Files, policyFileSpec{Filename: f.Filename})
|
||||
}
|
||||
if last.Strict != nil {
|
||||
opt.Strict = *last.Strict
|
||||
@@ -190,7 +202,8 @@ func withPolicyConfig(defaultPolicy policyOpt, configs []buildflags.PolicyConfig
|
||||
if cfg.LogLevel != nil {
|
||||
opt.LogLevel = cfg.LogLevel
|
||||
}
|
||||
opt.FS = defaultPolicy.FS
|
||||
opt.ContextDir = defaultPolicy.ContextDir
|
||||
opt.ContextState = defaultPolicy.ContextState
|
||||
out = append(out, opt)
|
||||
}
|
||||
|
||||
|
||||
+166
-113
@@ -5,7 +5,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"maps"
|
||||
"os"
|
||||
"path"
|
||||
@@ -495,90 +494,11 @@ func toSolveOpt(ctx context.Context, np *noderesolver.ResolvedNode, multiDriver
|
||||
releaseLoad()
|
||||
})
|
||||
|
||||
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 {
|
||||
env.Args = map[string]*string{}
|
||||
}
|
||||
env.Args[k] = &v
|
||||
}
|
||||
env.Filename = path.Base(opt.Inputs.DockerfilePath)
|
||||
env.Target = opt.Target
|
||||
env.Labels = opt.Labels
|
||||
|
||||
popts, err := withPolicyConfig(*opt.Inputs.policy, opt.Policy)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
var sourceResolver *sourcemeta.Resolver
|
||||
if len(popts) > 0 {
|
||||
c, err := np.Client(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
sourceResolver = sourcemeta.NewResolver(c, sourcemeta.WithProgressWriter(pw))
|
||||
defers = append(defers, func(error) {
|
||||
_ = sourceResolver.Close()
|
||||
})
|
||||
}
|
||||
var policyFiles []string
|
||||
for _, popt := range popts {
|
||||
for _, f := range popt.Files {
|
||||
if f.Filename != "" {
|
||||
policyFiles = append(policyFiles, f.Filename)
|
||||
}
|
||||
}
|
||||
}
|
||||
var policyLogger *policyProgressLogger
|
||||
if len(policyFiles) > 0 {
|
||||
policyLogger = newPolicyProgressLogger(pw, fmt.Sprintf("loading policies %s", strings.Join(policyFiles, ", ")))
|
||||
}
|
||||
var policies []*policy.Policy
|
||||
if policyLogger != nil {
|
||||
defers = append(defers, func(inErr error) {
|
||||
if len(policysession.DenyMessages(inErr)) > 0 || isPolicyEvaluationError(policies, inErr) {
|
||||
policyLogger.Close(inErr)
|
||||
return
|
||||
}
|
||||
policyLogger.Close(nil)
|
||||
})
|
||||
}
|
||||
var cbs []policysession.PolicyCallback
|
||||
for _, popt := range popts {
|
||||
policyLevel := logrus.GetLevel()
|
||||
if popt.LogLevel != nil {
|
||||
policyLevel = *popt.LogLevel
|
||||
}
|
||||
logf := func(level logrus.Level, msg string) {
|
||||
if policyLogger == nil || level > policyLevel {
|
||||
return
|
||||
}
|
||||
policyLogger.Log(msg)
|
||||
}
|
||||
p := policy.NewPolicy(policy.Opt{
|
||||
Files: popt.Files,
|
||||
Env: env,
|
||||
Log: logf,
|
||||
FS: opt.Inputs.policy.FS,
|
||||
VerifierProvider: policy.SignatureVerifier(cfg),
|
||||
DefaultPlatform: defaultPlatform(bopts),
|
||||
SourceResolver: sourceResolver,
|
||||
})
|
||||
policies = append(policies, p)
|
||||
cbs = append(cbs, p.CheckPolicy)
|
||||
if popt.Strict {
|
||||
if bopts.LLBCaps.Supports(pb.CapSourcePolicySession) != nil {
|
||||
return nil, nil, errors.New("strict policy is not supported by the current BuildKit daemon, please upgrade to version v0.27+")
|
||||
}
|
||||
}
|
||||
}
|
||||
so.SourcePolicyProvider = policysession.NewPolicyProvider(policy.MultiPolicyCallback(cbs...))
|
||||
policyDefers, err := configureSourcePolicy(ctx, np, opt, cfg, bopts, &so, pw)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defers = append(defers, policyDefers...)
|
||||
|
||||
// add node identifier to shared key if one was specified
|
||||
nodeID := cfg.TryNodeIdentifier()
|
||||
@@ -667,6 +587,114 @@ func toSolveOpt(ctx context.Context, np *noderesolver.ResolvedNode, multiDriver
|
||||
return &so, releaseF, nil
|
||||
}
|
||||
|
||||
func configureSourcePolicy(ctx context.Context, np *noderesolver.ResolvedNode, opt *Options, cfg *confutil.Config, bopts gateway.BuildOpts, so *client.SolveOpt, pw progress.Writer) (defers []func(error), err error) {
|
||||
if opt.Inputs.policy == nil {
|
||||
if len(opt.Policy) > 0 {
|
||||
return nil, errors.New("policy file specified but no policy FS in build context")
|
||||
}
|
||||
so.SourcePolicyProvider = nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
env := policy.Env{}
|
||||
for k, v := range opt.BuildArgs {
|
||||
if env.Args == nil {
|
||||
env.Args = map[string]*string{}
|
||||
}
|
||||
env.Args[k] = &v
|
||||
}
|
||||
env.Filename = path.Base(opt.Inputs.DockerfilePath)
|
||||
env.Target = opt.Target
|
||||
env.Labels = opt.Labels
|
||||
|
||||
popts, err := withPolicyConfig(*opt.Inputs.policy, opt.Policy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(popts) == 0 {
|
||||
so.SourcePolicyProvider = nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
c, err := np.Client(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sourceResolver := sourcemeta.NewResolver(c, sourcemeta.WithProgressWriter(pw))
|
||||
defers = []func(error){
|
||||
func(error) {
|
||||
_ = sourceResolver.Close()
|
||||
},
|
||||
}
|
||||
defer func() {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
for _, f := range defers {
|
||||
f(err)
|
||||
}
|
||||
defers = nil
|
||||
}()
|
||||
|
||||
loadedOpts, err := resolvePolicyOpts(ctx, popts, sourceResolver)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var policyFiles []string
|
||||
for _, popt := range loadedOpts {
|
||||
for _, f := range popt.Files {
|
||||
if f.Filename != "" {
|
||||
policyFiles = append(policyFiles, f.Filename)
|
||||
}
|
||||
}
|
||||
}
|
||||
var policyLogger *policyProgressLogger
|
||||
if len(policyFiles) > 0 {
|
||||
policyLogger = newPolicyProgressLogger(pw, fmt.Sprintf("loading policies %s", strings.Join(policyFiles, ", ")))
|
||||
}
|
||||
var policies []*policy.Policy
|
||||
if policyLogger != nil {
|
||||
defers = append(defers, func(inErr error) {
|
||||
if len(policysession.DenyMessages(inErr)) > 0 || isPolicyEvaluationError(policies, inErr) {
|
||||
policyLogger.Close(inErr)
|
||||
return
|
||||
}
|
||||
policyLogger.Close(nil)
|
||||
})
|
||||
}
|
||||
var cbs []policysession.PolicyCallback
|
||||
for _, popt := range loadedOpts {
|
||||
policyLevel := logrus.GetLevel()
|
||||
if popt.LogLevel != nil {
|
||||
policyLevel = *popt.LogLevel
|
||||
}
|
||||
logf := func(level logrus.Level, msg string) {
|
||||
if policyLogger == nil || level > policyLevel {
|
||||
return
|
||||
}
|
||||
policyLogger.Log(msg)
|
||||
}
|
||||
p := policy.NewPolicy(policy.Opt{
|
||||
Files: popt.Files,
|
||||
Env: env,
|
||||
Log: logf,
|
||||
FS: popt.FS,
|
||||
VerifierProvider: policy.SignatureVerifier(cfg),
|
||||
DefaultPlatform: defaultPlatform(bopts),
|
||||
SourceResolver: sourceResolver,
|
||||
})
|
||||
policies = append(policies, p)
|
||||
cbs = append(cbs, p.CheckPolicy)
|
||||
if popt.Strict {
|
||||
if bopts.LLBCaps.Supports(pb.CapSourcePolicySession) != nil {
|
||||
return nil, errors.New("strict policy is not supported by the current BuildKit daemon, please upgrade to version v0.27+")
|
||||
}
|
||||
}
|
||||
}
|
||||
so.SourcePolicyProvider = policysession.NewPolicyProvider(policy.MultiPolicyCallback(cbs...))
|
||||
return defers, nil
|
||||
}
|
||||
|
||||
func loadInputs(ctx context.Context, d *driver.DriverHandle, inp *Inputs, pw progress.Writer, target *client.SolveOpt) (func(), error) {
|
||||
if inp.ContextPath == "" {
|
||||
return nil, errors.New("please specify build context (e.g. \".\" for the current directory)")
|
||||
@@ -678,6 +706,8 @@ func loadInputs(ctx context.Context, d *driver.DriverHandle, inp *Inputs, pw pro
|
||||
err error
|
||||
dockerfileReader io.ReadCloser
|
||||
contextDir string
|
||||
remoteContext bool
|
||||
remotePolicyState *llb.State
|
||||
dockerfileDir string
|
||||
dockerfileName = inp.DockerfilePath
|
||||
dockerfileSrcName = inp.DockerfilePath
|
||||
@@ -687,6 +717,7 @@ func loadInputs(ctx context.Context, d *driver.DriverHandle, inp *Inputs, pw pro
|
||||
|
||||
switch {
|
||||
case inp.ContextState != nil:
|
||||
remotePolicyState = inp.ContextState
|
||||
if target.FrontendInputs == nil {
|
||||
target.FrontendInputs = make(map[string]llb.State)
|
||||
}
|
||||
@@ -745,6 +776,7 @@ func loadInputs(ctx context.Context, d *driver.DriverHandle, inp *Inputs, pw pro
|
||||
dockerfileName = filepath.Base(inp.DockerfilePath)
|
||||
}
|
||||
case urlutil.IsRemoteURL(inp.ContextPath):
|
||||
remoteContext = true
|
||||
if inp.DockerfilePath == "-" {
|
||||
dockerfileReader = inp.InStream.NewReadCloser()
|
||||
} else if filepath.IsAbs(inp.DockerfilePath) {
|
||||
@@ -758,6 +790,7 @@ func loadInputs(ctx context.Context, d *driver.DriverHandle, inp *Inputs, pw pro
|
||||
return nil, err
|
||||
}
|
||||
if st, ok := target.FrontendInputs["context"]; ok {
|
||||
remotePolicyState = &st
|
||||
if dockerfileReader == nil && !filepath.IsAbs(inp.DockerfilePath) {
|
||||
target.FrontendInputs["dockerfile"] = st
|
||||
}
|
||||
@@ -801,22 +834,11 @@ func loadInputs(ctx context.Context, d *driver.DriverHandle, inp *Inputs, pw pro
|
||||
}
|
||||
|
||||
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
|
||||
},
|
||||
ContextDir: contextDir,
|
||||
}
|
||||
p.ContextState = remotePolicyState
|
||||
if p.ContextState == nil && remoteContext {
|
||||
p.ContextState = resolveRemotePolicyContextState(inp.ContextPath, target)
|
||||
}
|
||||
|
||||
if dockerfileDir != "" {
|
||||
@@ -824,22 +846,31 @@ func loadInputs(ctx context.Context, d *driver.DriverHandle, inp *Inputs, pw pro
|
||||
return nil, err
|
||||
}
|
||||
dockerfileName = handleLowercaseDockerfile(dockerfileDir, dockerfileName)
|
||||
|
||||
if fi, err := os.Lstat(filepath.Join(dockerfileDir, dockerfileName+".rego")); err == nil {
|
||||
if fi.Mode().IsRegular() {
|
||||
dt, err := os.ReadFile(filepath.Join(dockerfileDir, dockerfileName+".rego"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to read policy file %s.rego", dockerfileName)
|
||||
}
|
||||
p.Files = []policy.File{
|
||||
{
|
||||
Filename: dockerfileName + ".rego",
|
||||
Data: dt,
|
||||
},
|
||||
}
|
||||
}
|
||||
defaultPolicyFilename := dockerfileName + ".rego"
|
||||
if dockerfileDir != "" {
|
||||
defaultPolicyFilename = filepath.Join(dockerfileDir, defaultPolicyFilename)
|
||||
}
|
||||
defaultPolicy := policyFileSpec{
|
||||
Filename: defaultPolicyFilename,
|
||||
Optional: true,
|
||||
}
|
||||
includeDefaultPolicy := true
|
||||
if dockerfileDir != "" && p.ContextState == nil {
|
||||
dt, err := os.ReadFile(defaultPolicyFilename)
|
||||
if err != nil {
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
return nil, errors.Wrapf(err, "failed to read policy file %s", defaultPolicyFilename)
|
||||
}
|
||||
includeDefaultPolicy = false
|
||||
} else {
|
||||
defaultPolicy.Data = dt
|
||||
}
|
||||
}
|
||||
if includeDefaultPolicy {
|
||||
p.Files = append(p.Files, defaultPolicy)
|
||||
}
|
||||
|
||||
inp.policy = p
|
||||
|
||||
target.FrontendAttrs["filename"] = dockerfileName
|
||||
@@ -928,6 +959,28 @@ func loadInputs(ctx context.Context, d *driver.DriverHandle, inp *Inputs, pw pro
|
||||
return release, nil
|
||||
}
|
||||
|
||||
func resolveRemotePolicyContextState(contextPath string, target *client.SolveOpt) *llb.State {
|
||||
if target != nil && target.FrontendInputs != nil {
|
||||
if st, ok := target.FrontendInputs["context"]; ok {
|
||||
return &st
|
||||
}
|
||||
}
|
||||
|
||||
keepGitDir := false
|
||||
if st, ok, _ := dockerui.DetectGitContext(contextPath, &keepGitDir); ok {
|
||||
return st
|
||||
}
|
||||
|
||||
st, filename, ok := dockerui.DetectHTTPContext(contextPath)
|
||||
if !ok || filename == "" {
|
||||
return nil
|
||||
}
|
||||
bc := llb.Scratch().File(llb.Copy(*st, filename, "/", &llb.CopyInfo{
|
||||
AttemptUnpack: true,
|
||||
}))
|
||||
return &bc
|
||||
}
|
||||
|
||||
func resolveDigest(localPath, tag string) (dig string, _ error) {
|
||||
idx := ociindex.NewStoreIndex(localPath)
|
||||
|
||||
|
||||
@@ -0,0 +1,391 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/docker/buildx/policy"
|
||||
"github.com/docker/buildx/util/sourcemeta"
|
||||
"github.com/moby/buildkit/client/llb"
|
||||
gwclient "github.com/moby/buildkit/frontend/gateway/client"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/tonistiigi/fsutil/types"
|
||||
)
|
||||
|
||||
type loadedPolicyOpt struct {
|
||||
Files []policy.File
|
||||
FS func() (fs.StatFS, func() error, error)
|
||||
policyEvalOpt
|
||||
}
|
||||
|
||||
func resolvePolicyOpts(ctx context.Context, in []policyOpt, resolver *sourcemeta.Resolver) ([]loadedPolicyOpt, error) {
|
||||
if len(in) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
out := make([]loadedPolicyOpt, 0, len(in))
|
||||
for _, popt := range in {
|
||||
provider := newPolicyPathFS(ctx, resolver, popt)
|
||||
loaded := loadedPolicyOpt{
|
||||
policyEvalOpt: popt.policyEvalOpt,
|
||||
FS: provider,
|
||||
}
|
||||
for _, f := range popt.Files {
|
||||
if f.Data != nil {
|
||||
loaded.Files = append(loaded.Files, policy.File{
|
||||
Filename: f.Filename,
|
||||
Data: f.Data,
|
||||
})
|
||||
continue
|
||||
}
|
||||
dt, ok, err := loadPolicyData(provider, f.Filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !ok {
|
||||
if f.Optional {
|
||||
continue
|
||||
}
|
||||
return nil, errors.Errorf("policy file %s not found", f.Filename)
|
||||
}
|
||||
loaded.Files = append(loaded.Files, policy.File{
|
||||
Filename: f.Filename,
|
||||
Data: dt,
|
||||
})
|
||||
}
|
||||
if len(loaded.Files) > 0 {
|
||||
out = append(out, loaded)
|
||||
}
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func loadPolicyData(provider func() (fs.StatFS, func() error, error), filename string) ([]byte, bool, error) {
|
||||
root, closeFS, err := provider()
|
||||
if err != nil {
|
||||
return nil, false, errors.Wrapf(err, "failed to get policy FS for %s", filename)
|
||||
}
|
||||
if closeFS != nil {
|
||||
defer closeFS()
|
||||
}
|
||||
if root == nil {
|
||||
return nil, false, nil
|
||||
}
|
||||
if _, err := root.Stat(filename); err != nil {
|
||||
if isFileNotFoundError(err) {
|
||||
return nil, false, nil
|
||||
}
|
||||
return nil, false, errors.Wrapf(err, "failed to stat policy file %s", filename)
|
||||
}
|
||||
dt, err := fs.ReadFile(root, filename)
|
||||
if err != nil {
|
||||
if isFileNotFoundError(err) {
|
||||
return nil, false, nil
|
||||
}
|
||||
return nil, false, errors.Wrapf(err, "failed to read policy file %s", filename)
|
||||
}
|
||||
return dt, true, nil
|
||||
}
|
||||
|
||||
type policyPathFS struct {
|
||||
ctx context.Context
|
||||
resolver *sourcemeta.Resolver
|
||||
contextDir string
|
||||
contextState *llb.State
|
||||
|
||||
cwdFS memoizedPolicyFS
|
||||
contextFS memoizedPolicyFS
|
||||
}
|
||||
|
||||
func newPolicyPathFS(ctx context.Context, resolver *sourcemeta.Resolver, popt policyOpt) func() (fs.StatFS, func() error, error) {
|
||||
p := &policyPathFS{
|
||||
ctx: context.WithoutCancel(ctx),
|
||||
resolver: resolver,
|
||||
contextDir: popt.ContextDir,
|
||||
contextState: popt.ContextState,
|
||||
}
|
||||
|
||||
p.cwdFS.init = func() (fs.StatFS, func() error, error) {
|
||||
root, err := os.OpenRoot(".")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
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.contextFS.init = func() (fs.StatFS, func() error, error) {
|
||||
if p.contextState != nil {
|
||||
if resolver == nil {
|
||||
return nil, nil, errors.New("policy resolver is not configured")
|
||||
}
|
||||
return newRemotePolicyFS(p.ctx, resolver, *p.contextState), nil, nil
|
||||
}
|
||||
if p.contextDir == "" {
|
||||
return nil, nil, nil
|
||||
}
|
||||
root, err := os.OpenRoot(p.contextDir)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
return func() (fs.StatFS, func() error, error) {
|
||||
return p, p.Close, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (p *policyPathFS) Open(name string) (fs.File, error) {
|
||||
backend, target, err := p.resolve(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if backend == nil {
|
||||
return nil, fs.ErrNotExist
|
||||
}
|
||||
return backend.Open(target)
|
||||
}
|
||||
|
||||
func (p *policyPathFS) Stat(name string) (fs.FileInfo, error) {
|
||||
backend, target, err := p.resolve(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if backend == nil {
|
||||
return nil, fs.ErrNotExist
|
||||
}
|
||||
return backend.Stat(target)
|
||||
}
|
||||
|
||||
func (p *policyPathFS) Close() error {
|
||||
if err := p.cwdFS.close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return p.contextFS.close()
|
||||
}
|
||||
|
||||
func (p *policyPathFS) resolve(name string) (fs.StatFS, string, error) {
|
||||
if name == "" {
|
||||
return nil, "", errors.New("policy filename is empty")
|
||||
}
|
||||
if v, ok := strings.CutPrefix(name, "cwd://"); ok {
|
||||
if v == "" {
|
||||
return nil, "", errors.Errorf("invalid policy filename %q", name)
|
||||
}
|
||||
cwd, err := p.cwdFS.get()
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return cwd, filepath.Clean(v), nil
|
||||
}
|
||||
|
||||
contextFS, err := p.contextFS.get()
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if p.contextState != nil {
|
||||
target, err := normalizeRemotePolicyPath(name)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return contextFS, target, nil
|
||||
}
|
||||
return contextFS, normalizeLocalPolicyPath(name, p.contextDir), nil
|
||||
}
|
||||
|
||||
func normalizeLocalPolicyPath(name, contextDir string) string {
|
||||
if filepath.IsAbs(name) && contextDir != "" {
|
||||
if rel, err := filepath.Rel(contextDir, name); err == nil {
|
||||
rel = filepath.Clean(rel)
|
||||
if rel != ".." && !strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
|
||||
return rel
|
||||
}
|
||||
}
|
||||
}
|
||||
return filepath.Clean(name)
|
||||
}
|
||||
|
||||
type memoizedPolicyFS struct {
|
||||
init func() (fs.StatFS, func() error, error)
|
||||
once sync.Once
|
||||
fs fs.StatFS
|
||||
closeFn func() error
|
||||
err error
|
||||
}
|
||||
|
||||
func (m *memoizedPolicyFS) get() (fs.StatFS, error) {
|
||||
m.once.Do(func() {
|
||||
if m.init == nil {
|
||||
return
|
||||
}
|
||||
m.fs, m.closeFn, m.err = m.init()
|
||||
})
|
||||
if m.err != nil {
|
||||
return nil, m.err
|
||||
}
|
||||
return m.fs, nil
|
||||
}
|
||||
|
||||
func (m *memoizedPolicyFS) close() error {
|
||||
if m.closeFn != nil {
|
||||
return m.closeFn()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizeRemotePolicyPath(raw string) (string, error) {
|
||||
clean := strings.TrimPrefix(path.Join("/", filepath.ToSlash(raw)), "/")
|
||||
if clean == "." || clean == "" {
|
||||
return "", errors.Errorf("invalid remote policy filename %q", raw)
|
||||
}
|
||||
return clean, nil
|
||||
}
|
||||
|
||||
func isFileNotFoundError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return true
|
||||
}
|
||||
msg := strings.ToLower(err.Error())
|
||||
return strings.Contains(msg, "not found") || strings.Contains(msg, "no such file")
|
||||
}
|
||||
|
||||
type remotePolicyFS struct {
|
||||
ctx context.Context
|
||||
resolver *sourcemeta.Resolver
|
||||
state llb.State
|
||||
|
||||
once sync.Once
|
||||
ref gwclient.Reference
|
||||
err error
|
||||
}
|
||||
|
||||
func newRemotePolicyFS(ctx context.Context, resolver *sourcemeta.Resolver, state llb.State) *remotePolicyFS {
|
||||
return &remotePolicyFS{
|
||||
ctx: context.WithoutCancel(ctx),
|
||||
resolver: resolver,
|
||||
state: state,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *remotePolicyFS) Open(name string) (fs.File, error) {
|
||||
p, err := normalizeRemotePolicyPath(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ref, err := r.resolveRef()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
st, err := ref.StatFile(r.ctx, gwclient.StatRequest{Path: p})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dt, err := ref.ReadFile(r.ctx, gwclient.ReadRequest{Filename: p})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fi := policyFileInfo{
|
||||
name: path.Base(p),
|
||||
size: int64(len(dt)),
|
||||
mode: fs.FileMode(st.Mode),
|
||||
tm: time.Unix(0, st.ModTime),
|
||||
}
|
||||
if fi.size == 0 {
|
||||
fi.size = st.Size
|
||||
}
|
||||
|
||||
return &policyReadFile{
|
||||
Reader: bytes.NewReader(dt),
|
||||
info: fi,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *remotePolicyFS) Stat(name string) (fs.FileInfo, error) {
|
||||
p, err := normalizeRemotePolicyPath(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ref, err := r.resolveRef()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
st, err := ref.StatFile(r.ctx, gwclient.StatRequest{Path: p})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return policyFileInfo{
|
||||
name: path.Base(p),
|
||||
size: st.Size,
|
||||
mode: fs.FileMode(st.Mode),
|
||||
tm: time.Unix(0, st.ModTime),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *remotePolicyFS) resolveRef() (gwclient.Reference, error) {
|
||||
r.once.Do(func() {
|
||||
r.ref, r.err = r.resolver.ResolveState(r.ctx, r.state)
|
||||
})
|
||||
if r.err != nil {
|
||||
return nil, r.err
|
||||
}
|
||||
return r.ref, nil
|
||||
}
|
||||
|
||||
type policyReadFile struct {
|
||||
*bytes.Reader
|
||||
info policyFileInfo
|
||||
}
|
||||
|
||||
func (f *policyReadFile) Stat() (fs.FileInfo, error) {
|
||||
return f.info, nil
|
||||
}
|
||||
|
||||
func (f *policyReadFile) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type policyFileInfo struct {
|
||||
name string
|
||||
size int64
|
||||
mode fs.FileMode
|
||||
tm time.Time
|
||||
}
|
||||
|
||||
func (i policyFileInfo) Name() string { return i.name }
|
||||
func (i policyFileInfo) Size() int64 { return i.size }
|
||||
func (i policyFileInfo) Mode() fs.FileMode { return i.mode }
|
||||
func (i policyFileInfo) ModTime() time.Time { return i.tm }
|
||||
func (i policyFileInfo) IsDir() bool { return i.mode.IsDir() }
|
||||
func (i policyFileInfo) Sys() any {
|
||||
return &types.Stat{Mode: uint32(i.mode), Size: i.size, ModTime: i.tm.UnixNano()}
|
||||
}
|
||||
|
||||
var _ fs.StatFS = (*policyPathFS)(nil)
|
||||
var _ fs.StatFS = (*remotePolicyFS)(nil)
|
||||
var _ fs.File = (*policyReadFile)(nil)
|
||||
var _ io.ReaderAt = (*bytes.Reader)(nil)
|
||||
+9
-19
@@ -1,7 +1,6 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/buildx/policy"
|
||||
@@ -21,11 +20,8 @@ func levelPtr(v logrus.Level) *logrus.Level {
|
||||
// TestWithPolicyConfigDefaults ensures default policy is returned when no configs are provided.
|
||||
func TestWithPolicyConfigDefaults(t *testing.T) {
|
||||
defaultPolicy := policyOpt{
|
||||
Files: []policy.File{
|
||||
{Filename: "default.rego", Data: []byte("package policy")},
|
||||
},
|
||||
FS: func() (fs.StatFS, func() error, error) {
|
||||
return nil, nil, nil
|
||||
Files: []policyFileSpec{
|
||||
{Filename: "default.rego", Optional: true},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -35,7 +31,6 @@ func TestWithPolicyConfigDefaults(t *testing.T) {
|
||||
require.Equal(t, defaultPolicy.Files, out[0].Files)
|
||||
require.False(t, out[0].Strict)
|
||||
require.Nil(t, out[0].LogLevel)
|
||||
require.NotNil(t, out[0].FS)
|
||||
}
|
||||
|
||||
// TestWithPolicyConfigDisabled validates disabled policy behavior across invalid and valid combinations.
|
||||
@@ -76,10 +71,7 @@ func TestWithPolicyConfigDisabled(t *testing.T) {
|
||||
// TestWithPolicyConfigResetAndFiles ensures reset drops defaults and uses explicitly provided files.
|
||||
func TestWithPolicyConfigResetAndFiles(t *testing.T) {
|
||||
defaultPolicy := policyOpt{
|
||||
Files: []policy.File{{Filename: "default.rego"}},
|
||||
FS: func() (fs.StatFS, func() error, error) {
|
||||
return nil, nil, nil
|
||||
},
|
||||
Files: []policyFileSpec{{Filename: "default.rego", Optional: true}},
|
||||
}
|
||||
|
||||
out, err := withPolicyConfig(defaultPolicy, []buildflags.PolicyConfig{
|
||||
@@ -89,13 +81,13 @@ func TestWithPolicyConfigResetAndFiles(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Len(t, out, 1)
|
||||
require.Equal(t, "a.rego", out[0].Files[0].Filename)
|
||||
require.NotNil(t, out[0].FS)
|
||||
require.False(t, out[0].Files[0].Optional)
|
||||
}
|
||||
|
||||
// TestWithPolicyConfigStrictAndLogLevel ensures strict and log level apply to existing policy.
|
||||
func TestWithPolicyConfigStrictAndLogLevel(t *testing.T) {
|
||||
defaultPolicy := policyOpt{
|
||||
Files: []policy.File{{Filename: "default.rego"}},
|
||||
Files: []policyFileSpec{{Filename: "default.rego", Optional: true}},
|
||||
}
|
||||
|
||||
out, err := withPolicyConfig(defaultPolicy, []buildflags.PolicyConfig{
|
||||
@@ -120,10 +112,7 @@ func TestWithPolicyConfigStrictIgnoredWithoutPolicy(t *testing.T) {
|
||||
// TestWithPolicyConfigMultipleFilesAndOverrides ensures per-entry overrides and carryover apply across multiple files.
|
||||
func TestWithPolicyConfigMultipleFilesAndOverrides(t *testing.T) {
|
||||
defaultPolicy := policyOpt{
|
||||
Files: []policy.File{{Filename: "default.rego"}},
|
||||
FS: func() (fs.StatFS, func() error, error) {
|
||||
return nil, nil, nil
|
||||
},
|
||||
Files: []policyFileSpec{{Filename: "default.rego", Optional: true}},
|
||||
}
|
||||
|
||||
out, err := withPolicyConfig(defaultPolicy, []buildflags.PolicyConfig{
|
||||
@@ -134,12 +123,13 @@ func TestWithPolicyConfigMultipleFilesAndOverrides(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Len(t, out, 3)
|
||||
require.Equal(t, "default.rego", out[0].Files[0].Filename)
|
||||
require.True(t, out[0].Files[0].Optional)
|
||||
require.Equal(t, "a.rego", out[1].Files[0].Filename)
|
||||
require.False(t, out[1].Files[0].Optional)
|
||||
require.True(t, out[1].Strict)
|
||||
require.NotNil(t, out[1].LogLevel)
|
||||
require.Equal(t, logrus.WarnLevel, *out[1].LogLevel)
|
||||
require.Equal(t, "b.rego", out[2].Files[0].Filename)
|
||||
require.False(t, out[2].Files[0].Optional)
|
||||
require.True(t, out[2].Strict)
|
||||
require.NotNil(t, out[1].FS)
|
||||
require.NotNil(t, out[2].FS)
|
||||
}
|
||||
|
||||
+305
-1
@@ -1,6 +1,8 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -33,6 +35,8 @@ var policyBuildTests = []func(t *testing.T, sb integration.Sandbox){
|
||||
testBuildPolicyEnv,
|
||||
testBuildPolicyHTTP,
|
||||
testBuildPolicyGit,
|
||||
testBuildPolicyRemotePolicyFiles,
|
||||
testBuildPolicyRemoteHTTPPolicyFiles,
|
||||
testBuildPolicyConfigFlags,
|
||||
}
|
||||
|
||||
@@ -1089,7 +1093,7 @@ decision := {"allow": allow}
|
||||
t,
|
||||
fstest.CreateFile("policy.rego", []byte(tc.policy), 0600),
|
||||
)
|
||||
policyPath := filepath.Join(dir, "policy.rego")
|
||||
policyPath := "cwd://policy.rego"
|
||||
|
||||
cmd := buildxCmd(sb, withDir(dir), withArgs(
|
||||
"build",
|
||||
@@ -1257,3 +1261,303 @@ decision := {"allow": allow}
|
||||
require.Contains(t, string(out), "disabled policy cannot be combined with other policy flags")
|
||||
})
|
||||
}
|
||||
|
||||
func testBuildPolicyRemotePolicyFiles(t *testing.T, sb integration.Sandbox) {
|
||||
skipNoCompatBuildKit(t, sb, ">= 0.26.0-0", "policy input requires BuildKit v0.26.0+")
|
||||
|
||||
gitDir := t.TempDir()
|
||||
require.NoError(t, os.WriteFile(filepath.Join(gitDir, "Dockerfile"), []byte("FROM busybox:latest\nRUN echo remote-policy\n"), 0600))
|
||||
require.NoError(t, os.MkdirAll(filepath.Join(gitDir, "policy"), 0700))
|
||||
require.NoError(t, os.MkdirAll(filepath.Join(gitDir, "hack"), 0700))
|
||||
|
||||
require.NoError(t, os.WriteFile(filepath.Join(gitDir, "policy", "import.rego"), []byte(`
|
||||
package docker
|
||||
|
||||
import data.hack.allow as allowlib
|
||||
|
||||
default allow = false
|
||||
|
||||
allow if allowlib.required
|
||||
|
||||
decision := {"allow": allow}
|
||||
`), 0600))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(gitDir, "hack", "allow.rego"), []byte(`
|
||||
package hack.allow
|
||||
|
||||
required := true
|
||||
`), 0600))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(gitDir, "policy", "json.rego"), []byte(`
|
||||
package docker
|
||||
|
||||
default allow = false
|
||||
|
||||
cfg := load_json("policy/config.json")
|
||||
|
||||
allow if cfg.allow == true
|
||||
|
||||
decision := {"allow": allow}
|
||||
`), 0600))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(gitDir, "policy", "config.json"), []byte(`{"allow": true}`), 0600))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(gitDir, "policy", "deny.rego"), []byte(`
|
||||
package docker
|
||||
|
||||
default allow = false
|
||||
|
||||
decision := {"allow": allow}
|
||||
`), 0600))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(gitDir, "remote-only.rego"), []byte(`
|
||||
package docker
|
||||
|
||||
default allow = false
|
||||
|
||||
decision := {"allow": allow}
|
||||
`), 0600))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(gitDir, "Dockerfile.rego"), []byte(`
|
||||
package docker
|
||||
|
||||
default allow = true
|
||||
|
||||
decision := {"allow": allow}
|
||||
`), 0600))
|
||||
|
||||
git, err := gitutil.New(gitutil.WithWorkingDir(gitDir))
|
||||
require.NoError(t, err)
|
||||
gittestutil.GitInit(git, t)
|
||||
gittestutil.GitAdd(git, t, ".")
|
||||
gittestutil.GitCommit(git, t, "initial commit")
|
||||
baseURL := gittestutil.GitServeHTTP(git, t)
|
||||
|
||||
workDir := t.TempDir()
|
||||
require.NoError(t, os.WriteFile(filepath.Join(workDir, "local-allow.rego"), []byte(`
|
||||
package docker
|
||||
|
||||
default allow = true
|
||||
|
||||
decision := {"allow": allow}
|
||||
`), 0600))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(workDir, "local-only.rego"), []byte(`
|
||||
package docker
|
||||
|
||||
default allow = true
|
||||
|
||||
decision := {"allow": allow}
|
||||
`), 0600))
|
||||
|
||||
t.Run("remote-policy-import", func(t *testing.T) {
|
||||
cmd := buildxCmd(sb, withDir(workDir), withArgs(
|
||||
"build",
|
||||
"--progress=plain",
|
||||
"--policy", "filename=policy/import.rego",
|
||||
"--output=type=cacheonly",
|
||||
baseURL,
|
||||
))
|
||||
out, err := cmd.CombinedOutput()
|
||||
require.NoError(t, err, string(out))
|
||||
require.Contains(t, string(out), "loading policies")
|
||||
require.Contains(t, string(out), "policy/import.rego")
|
||||
})
|
||||
|
||||
t.Run("remote-policy-load-json", func(t *testing.T) {
|
||||
cmd := buildxCmd(sb, withDir(workDir), withArgs(
|
||||
"build",
|
||||
"--progress=plain",
|
||||
"--policy", "filename=policy/json.rego",
|
||||
"--output=type=cacheonly",
|
||||
baseURL,
|
||||
))
|
||||
out, err := cmd.CombinedOutput()
|
||||
require.NoError(t, err, string(out))
|
||||
require.Contains(t, string(out), "loading policies")
|
||||
require.Contains(t, string(out), "policy/json.rego")
|
||||
})
|
||||
|
||||
t.Run("remote-policy-cwd-override", func(t *testing.T) {
|
||||
cmd := buildxCmd(sb, withDir(workDir), withArgs(
|
||||
"build",
|
||||
"--progress=plain",
|
||||
"--policy", "filename=cwd://local-allow.rego",
|
||||
"--output=type=cacheonly",
|
||||
baseURL,
|
||||
))
|
||||
out, err := cmd.CombinedOutput()
|
||||
require.NoError(t, err, string(out))
|
||||
require.Contains(t, string(out), "loading policies")
|
||||
require.Contains(t, string(out), "cwd://local-allow.rego")
|
||||
})
|
||||
|
||||
t.Run("remote-policy-no-local-fallback", func(t *testing.T) {
|
||||
cmd := buildxCmd(sb, withDir(workDir), withArgs(
|
||||
"build",
|
||||
"--progress=plain",
|
||||
"--policy", "filename=does-not-exist.rego",
|
||||
"--output=type=cacheonly",
|
||||
baseURL,
|
||||
))
|
||||
out, err := cmd.CombinedOutput()
|
||||
require.Error(t, err, string(out))
|
||||
require.Contains(t, string(out), "policy file does-not-exist.rego not found")
|
||||
})
|
||||
|
||||
t.Run("remote-policy-prefers-remote-over-cwd", func(t *testing.T) {
|
||||
cmd := buildxCmd(sb, withDir(workDir), withArgs(
|
||||
"build",
|
||||
"--progress=plain",
|
||||
"--policy", "filename=remote-only.rego",
|
||||
"--output=type=cacheonly",
|
||||
baseURL,
|
||||
))
|
||||
out, err := cmd.CombinedOutput()
|
||||
require.Error(t, err, string(out))
|
||||
require.Contains(t, string(out), "not allowed by policy")
|
||||
require.Contains(t, string(out), "loading policies")
|
||||
require.Contains(t, string(out), "remote-only.rego")
|
||||
})
|
||||
|
||||
t.Run("remote-policy-cwd-prefix-uses-local", func(t *testing.T) {
|
||||
cmd := buildxCmd(sb, withDir(workDir), withArgs(
|
||||
"build",
|
||||
"--progress=plain",
|
||||
"--policy", "filename=cwd://local-only.rego",
|
||||
"--output=type=cacheonly",
|
||||
baseURL,
|
||||
))
|
||||
out, err := cmd.CombinedOutput()
|
||||
require.NoError(t, err, string(out))
|
||||
require.Contains(t, string(out), "loading policies")
|
||||
require.Contains(t, string(out), "cwd://local-only.rego")
|
||||
})
|
||||
|
||||
t.Run("remote-policy-default-from-remote-git", func(t *testing.T) {
|
||||
cmd := buildxCmd(sb, withDir(workDir), withArgs(
|
||||
"build",
|
||||
"--progress=plain",
|
||||
"--output=type=cacheonly",
|
||||
baseURL,
|
||||
))
|
||||
out, err := cmd.CombinedOutput()
|
||||
require.NoError(t, err, string(out))
|
||||
require.Contains(t, string(out), "loading policies Dockerfile.rego")
|
||||
})
|
||||
}
|
||||
|
||||
func testBuildPolicyRemoteHTTPPolicyFiles(t *testing.T, sb integration.Sandbox) {
|
||||
skipNoCompatBuildKit(t, sb, ">= 0.26.0-0", "policy input requires BuildKit v0.26.0+")
|
||||
|
||||
makeTar := func(t *testing.T, files map[string]string) []byte {
|
||||
t.Helper()
|
||||
var buf bytes.Buffer
|
||||
tw := tar.NewWriter(&buf)
|
||||
for name, data := range files {
|
||||
dt := []byte(data)
|
||||
require.NoError(t, tw.WriteHeader(&tar.Header{
|
||||
Name: name,
|
||||
Mode: 0600,
|
||||
Size: int64(len(dt)),
|
||||
}))
|
||||
_, err := tw.Write(dt)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.NoError(t, tw.Close())
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
archiveURLPath := "/context.tar"
|
||||
singleURLPath := "/Dockerfile"
|
||||
server := httpserver.NewTestServer(map[string]*httpserver.Response{
|
||||
archiveURLPath: {
|
||||
Content: makeTar(t, map[string]string{
|
||||
"Dockerfile": `FROM busybox:latest
|
||||
RUN echo http-archive-policy
|
||||
`,
|
||||
"policy/allow.rego": `
|
||||
package docker
|
||||
|
||||
default allow = true
|
||||
|
||||
decision := {"allow": allow}
|
||||
`,
|
||||
}),
|
||||
},
|
||||
singleURLPath: {
|
||||
Content: []byte("FROM busybox:latest\nRUN echo http-single-file\n"),
|
||||
},
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
workDir := t.TempDir()
|
||||
require.NoError(t, os.WriteFile(filepath.Join(workDir, "Dockerfile.rego"), []byte(`
|
||||
package docker
|
||||
|
||||
default allow = false
|
||||
|
||||
decision := {"allow": allow}
|
||||
`), 0600))
|
||||
|
||||
t.Run("http-archive-policy-file", func(t *testing.T) {
|
||||
cmd := buildxCmd(sb, withDir(workDir), withArgs(
|
||||
"build",
|
||||
"--progress=plain",
|
||||
"--policy", "filename=policy/allow.rego",
|
||||
"--output=type=cacheonly",
|
||||
server.URL+archiveURLPath,
|
||||
))
|
||||
out, err := cmd.CombinedOutput()
|
||||
require.NoError(t, err, string(out))
|
||||
require.Contains(t, string(out), "loading policies")
|
||||
require.Contains(t, string(out), "policy/allow.rego")
|
||||
})
|
||||
|
||||
t.Run("http-single-file-required-policy-not-found", func(t *testing.T) {
|
||||
cmd := buildxCmd(sb, withDir(workDir), withArgs(
|
||||
"build",
|
||||
"--progress=plain",
|
||||
"--policy", "filename=policy/allow.rego",
|
||||
"--output=type=cacheonly",
|
||||
server.URL+singleURLPath,
|
||||
))
|
||||
out, err := cmd.CombinedOutput()
|
||||
require.Error(t, err, string(out))
|
||||
require.Contains(t, string(out), "policy file policy/allow.rego not found")
|
||||
})
|
||||
|
||||
t.Run("http-single-file-no-local-default-fallback", func(t *testing.T) {
|
||||
cmd := buildxCmd(sb, withDir(workDir), withArgs(
|
||||
"build",
|
||||
"--progress=plain",
|
||||
"--output=type=cacheonly",
|
||||
server.URL+singleURLPath,
|
||||
))
|
||||
out, err := cmd.CombinedOutput()
|
||||
require.NoError(t, err, string(out))
|
||||
})
|
||||
|
||||
t.Run("http-archive-default-from-remote-context", func(t *testing.T) {
|
||||
serverWithDefault := httpserver.NewTestServer(map[string]*httpserver.Response{
|
||||
archiveURLPath: {
|
||||
Content: makeTar(t, map[string]string{
|
||||
"Dockerfile": `FROM busybox:latest
|
||||
RUN echo http-archive-default
|
||||
`,
|
||||
"Dockerfile.rego": `
|
||||
package docker
|
||||
|
||||
default allow = true
|
||||
|
||||
decision := {"allow": allow}
|
||||
`,
|
||||
}),
|
||||
},
|
||||
})
|
||||
defer serverWithDefault.Close()
|
||||
|
||||
cmd := buildxCmd(sb, withDir(workDir), withArgs(
|
||||
"build",
|
||||
"--progress=plain",
|
||||
"--output=type=cacheonly",
|
||||
serverWithDefault.URL+archiveURLPath,
|
||||
))
|
||||
out, err := cmd.CombinedOutput()
|
||||
require.NoError(t, err, string(out))
|
||||
require.Contains(t, string(out), "loading policies Dockerfile.rego")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package buildflags
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -56,11 +55,7 @@ func parsePolicyFields(fields []string) (PolicyConfig, error) {
|
||||
if value == "" {
|
||||
return PolicyConfig{}, errors.Errorf("invalid value %s", field)
|
||||
}
|
||||
dt, err := os.ReadFile(value)
|
||||
if err != nil {
|
||||
return PolicyConfig{}, errors.Wrapf(err, "failed to read policy file %s", value)
|
||||
}
|
||||
cfg.Files = append(cfg.Files, policy.File{Filename: value, Data: dt})
|
||||
cfg.Files = append(cfg.Files, policy.File{Filename: value})
|
||||
case "reset":
|
||||
b, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
|
||||
@@ -14,8 +14,7 @@ import (
|
||||
func TestPolicyConfigs_FromCtyValue(t *testing.T) {
|
||||
policyDir := t.TempDir()
|
||||
policyPath := filepath.Join(policyDir, "policy.rego")
|
||||
policyData := []byte("package docker\n")
|
||||
require.NoError(t, os.WriteFile(policyPath, policyData, 0o600))
|
||||
require.NoError(t, os.WriteFile(policyPath, []byte("package docker\n"), 0o600))
|
||||
|
||||
in := cty.TupleVal([]cty.Value{
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
@@ -33,7 +32,7 @@ func TestPolicyConfigs_FromCtyValue(t *testing.T) {
|
||||
require.Len(t, actual, 2)
|
||||
|
||||
require.Equal(t, policyPath, actual[0].Files[0].Filename)
|
||||
require.Equal(t, policyData, actual[0].Files[0].Data)
|
||||
require.Nil(t, actual[0].Files[0].Data)
|
||||
require.True(t, actual[0].Reset)
|
||||
require.NotNil(t, actual[0].Strict)
|
||||
require.True(t, *actual[0].Strict)
|
||||
@@ -41,7 +40,7 @@ func TestPolicyConfigs_FromCtyValue(t *testing.T) {
|
||||
require.Equal(t, logrus.WarnLevel, *actual[0].LogLevel)
|
||||
|
||||
require.Equal(t, policyPath, actual[1].Files[0].Filename)
|
||||
require.Equal(t, policyData, actual[1].Files[0].Data)
|
||||
require.Nil(t, actual[1].Files[0].Data)
|
||||
require.True(t, actual[1].Disabled)
|
||||
}
|
||||
|
||||
@@ -82,13 +81,12 @@ func TestPolicyConfigs_ToCtyValue(t *testing.T) {
|
||||
func TestPolicyConfig_FromCtyValue(t *testing.T) {
|
||||
policyDir := t.TempDir()
|
||||
policyPath := filepath.Join(policyDir, "policy.rego")
|
||||
policyData := []byte("package docker\n")
|
||||
require.NoError(t, os.WriteFile(policyPath, policyData, 0o600))
|
||||
require.NoError(t, os.WriteFile(policyPath, []byte("package docker\n"), 0o600))
|
||||
|
||||
var actual PolicyConfig
|
||||
err := actual.FromCtyValue(cty.StringVal("filename="+policyPath+",disabled=true"), nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, policyPath, actual.Files[0].Filename)
|
||||
require.Equal(t, policyData, actual.Files[0].Data)
|
||||
require.Nil(t, actual.Files[0].Data)
|
||||
require.True(t, actual.Disabled)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user