Merge pull request #3738 from tonistiigi/policy-eval-upt
Updated for policy eval
This commit is contained in:
+43
-23
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"maps"
|
||||
"os"
|
||||
@@ -33,6 +34,7 @@ type evalOpts struct {
|
||||
filename string
|
||||
printOutput bool
|
||||
fields []string
|
||||
platform string
|
||||
builder *string
|
||||
}
|
||||
|
||||
@@ -49,9 +51,14 @@ func evalCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
|
||||
return runEval(cmd.Context(), dockerCli, args[0], opts)
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringVar(&opts.filename, "filename", "Dockerfile", "Policy filename to evaluate")
|
||||
cmd.Flags().BoolVar(&opts.printOutput, "print", false, "Print policy output")
|
||||
cmd.Flags().StringSliceVar(&opts.fields, "fields", nil, "Fields to evaluate")
|
||||
flags := cmd.Flags()
|
||||
flags.StringVarP(&opts.filename, "file", "f", "Dockerfile", "Policy filename to evaluate")
|
||||
flags.BoolVar(&opts.printOutput, "print", false, "Print policy output")
|
||||
flags.StringSliceVar(&opts.fields, "fields", nil, "Fields to evaluate")
|
||||
flags.StringVar(&opts.platform, "platform", "", "Target platform for policy evaluation")
|
||||
// Deprecated: use --file instead
|
||||
flags.StringVar(&opts.filename, "filename", "Dockerfile", "Policy filename to evaluate")
|
||||
flags.MarkHidden("filename")
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -81,29 +88,29 @@ func runEval(ctx context.Context, dockerCli command.Cli, source string, opts eva
|
||||
return err
|
||||
}
|
||||
|
||||
workers, err := c.ListWorkers(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var p ocispecs.Platform
|
||||
if opts.platform != "" {
|
||||
parsedPlatform, err := parsePlatform(opts.platform)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p = *parsedPlatform
|
||||
} else {
|
||||
workers, err := c.ListWorkers(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(workers) == 0 {
|
||||
return errors.New("no workers available in the builder")
|
||||
}
|
||||
if len(workers) == 0 {
|
||||
return errors.New("no workers available in the builder")
|
||||
}
|
||||
|
||||
defaultPlatform := workers[0].Platforms[0]
|
||||
p := ocispecs.Platform{
|
||||
Architecture: defaultPlatform.Architecture,
|
||||
OS: defaultPlatform.OS,
|
||||
Variant: defaultPlatform.Variant,
|
||||
p = workers[0].Platforms[0]
|
||||
}
|
||||
metaResolver := sourcemeta.NewResolver(c)
|
||||
defer metaResolver.Close()
|
||||
|
||||
platform := &pb.Platform{
|
||||
Architecture: p.Architecture,
|
||||
OS: p.OS,
|
||||
Variant: p.Variant,
|
||||
}
|
||||
platform := toPBPlatform(p)
|
||||
verifier := policy.SignatureVerifier(confutil.NewConfig(dockerCli))
|
||||
|
||||
if opts.printOutput {
|
||||
@@ -185,9 +192,8 @@ func runEval(ctx context.Context, dockerCli command.Cli, source string, opts eva
|
||||
if opts.filename == "" {
|
||||
return errors.New("filename is required")
|
||||
}
|
||||
policyName := opts.filename
|
||||
policyFile := policyName + ".rego"
|
||||
policyData, err := os.ReadFile(policyFile)
|
||||
policyName, policyFile := policyFileNames(opts.filename)
|
||||
policyData, err := readPolicyData(policyFile, os.Stdin)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to read policy file %s", policyFile)
|
||||
}
|
||||
@@ -261,6 +267,20 @@ func runEval(ctx context.Context, dockerCli command.Cli, source string, opts eva
|
||||
}
|
||||
}
|
||||
|
||||
func policyFileNames(filename string) (string, string) {
|
||||
if filename == "-" {
|
||||
return "stdin", filename
|
||||
}
|
||||
return filename, filename + ".rego"
|
||||
}
|
||||
|
||||
func readPolicyData(filename string, stdin io.Reader) ([]byte, error) {
|
||||
if filename == "-" {
|
||||
return io.ReadAll(stdin)
|
||||
}
|
||||
return os.ReadFile(filename)
|
||||
}
|
||||
|
||||
func selectReloadFields(fields []string, unknowns []string) ([]string, []string) {
|
||||
if len(fields) == 0 {
|
||||
return nil, nil
|
||||
|
||||
@@ -6,10 +6,39 @@ import (
|
||||
policytypes "github.com/docker/buildx/policy"
|
||||
"github.com/docker/buildx/util/sourcemeta"
|
||||
gwpb "github.com/moby/buildkit/frontend/gateway/pb"
|
||||
"github.com/moby/buildkit/solver/pb"
|
||||
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParsePlatform(t *testing.T) {
|
||||
t.Run("normalize", func(t *testing.T) {
|
||||
platform, err := parsePlatform("linux/arm/v7")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &ocispecs.Platform{
|
||||
OS: "linux",
|
||||
Architecture: "arm",
|
||||
Variant: "v7",
|
||||
}, platform)
|
||||
})
|
||||
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
platform, err := parsePlatform("not-a-platform")
|
||||
require.Nil(t, platform)
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "invalid platform \"not-a-platform\"")
|
||||
require.ErrorContains(t, err, "unknown operating system or architecture")
|
||||
})
|
||||
}
|
||||
|
||||
func TestToPBPlatform(t *testing.T) {
|
||||
platform := ocispecs.Platform{OS: "linux", Architecture: "amd64"}
|
||||
require.Equal(t, &pb.Platform{
|
||||
OS: "linux",
|
||||
Architecture: "amd64",
|
||||
}, toPBPlatform(platform))
|
||||
}
|
||||
|
||||
func TestSourceResolverOptIncludesResolveAttestations(t *testing.T) {
|
||||
req := &gwpb.ResolveSourceMetaRequest{
|
||||
ResolveMode: "default",
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package policy
|
||||
|
||||
import (
|
||||
"github.com/containerd/platforms"
|
||||
"github.com/moby/buildkit/solver/pb"
|
||||
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func parsePlatform(platform string) (*ocispecs.Platform, error) {
|
||||
p, err := platforms.Parse(platform)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "invalid platform %q", platform)
|
||||
}
|
||||
p = platforms.Normalize(p)
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
func toPBPlatform(platform ocispecs.Platform) *pb.Platform {
|
||||
return &pb.Platform{
|
||||
Architecture: platform.Architecture,
|
||||
OS: platform.OS,
|
||||
Variant: platform.Variant,
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,8 @@ Evaluate policy for a source
|
||||
| `--builder` | `string` | | Override the configured builder instance |
|
||||
| `-D`, `--debug` | `bool` | | Enable debug logging |
|
||||
| `--fields` | `stringSlice` | | Fields to evaluate |
|
||||
| `--filename` | `string` | `Dockerfile` | Policy filename to evaluate |
|
||||
| `-f`, `--file` | `string` | `Dockerfile` | Policy filename to evaluate |
|
||||
| `--platform` | `string` | | Target platform for policy evaluation |
|
||||
| `--print` | `bool` | | Print policy output |
|
||||
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
var policyEvalTests = []func(t *testing.T, sb integration.Sandbox){
|
||||
testPolicyEvalAllow,
|
||||
testPolicyEvalDeny,
|
||||
testPolicyEvalStdinFile,
|
||||
testPolicyEvalPrint,
|
||||
testPolicyEvalFields,
|
||||
testPolicyEvalLabel,
|
||||
@@ -88,6 +89,63 @@ decision := {"allow": allow}
|
||||
require.Contains(t, string(out), "policy denied")
|
||||
}
|
||||
|
||||
func testPolicyEvalStdinFile(t *testing.T, sb integration.Sandbox) {
|
||||
skipNoCompatBuildKit(t, sb, ">= 0.26.0-0", "policy input requires BuildKit v0.26.0+")
|
||||
testCases := []struct {
|
||||
name string
|
||||
policy string
|
||||
wantErrContains string
|
||||
}{
|
||||
{
|
||||
name: "allow",
|
||||
policy: `
|
||||
package docker
|
||||
|
||||
default allow = false
|
||||
|
||||
allow if not input.image
|
||||
|
||||
allow if input.image.repo == "busybox"
|
||||
|
||||
decision := {"allow": allow}
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "deny",
|
||||
policy: `
|
||||
package docker
|
||||
|
||||
default allow = false
|
||||
|
||||
allow if input.image.repo == "alpine"
|
||||
|
||||
decision := {"allow": allow}
|
||||
`,
|
||||
wantErrContains: "policy denied",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cmd := buildxCmd(sb, withArgs(
|
||||
"policy",
|
||||
"eval",
|
||||
"--file",
|
||||
"-",
|
||||
"docker-image://busybox:latest",
|
||||
))
|
||||
cmd.Stdin = strings.NewReader(tc.policy)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if tc.wantErrContains == "" {
|
||||
require.NoError(t, err, string(out))
|
||||
return
|
||||
}
|
||||
require.Error(t, err, string(out))
|
||||
require.Contains(t, string(out), tc.wantErrContains)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testPolicyEvalPrint(t *testing.T, sb integration.Sandbox) {
|
||||
skipNoCompatBuildKit(t, sb, ">= 0.26.0-0", "policy input requires BuildKit v0.26.0+")
|
||||
cmd := buildxCmd(sb, withArgs(
|
||||
|
||||
Reference in New Issue
Block a user