diff --git a/commands/policy/eval.go b/commands/policy/eval.go index 3c7361a90..c13d46dda 100644 --- a/commands/policy/eval.go +++ b/commands/policy/eval.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "io" "io/fs" "maps" "os" @@ -191,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) } @@ -267,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 diff --git a/docs/reference/buildx_policy_eval.md b/docs/reference/buildx_policy_eval.md index ad8d8d0b0..9dc5dc378 100644 --- a/docs/reference/buildx_policy_eval.md +++ b/docs/reference/buildx_policy_eval.md @@ -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 | diff --git a/tests/policy_eval.go b/tests/policy_eval.go index 19018648d..f0b695db1 100644 --- a/tests/policy_eval.go +++ b/tests/policy_eval.go @@ -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(