diff --git a/pkg/commands/scan/command_test.go b/pkg/commands/scan/command_test.go index b383df2b..ccce6849 100644 --- a/pkg/commands/scan/command_test.go +++ b/pkg/commands/scan/command_test.go @@ -19,59 +19,59 @@ func Test_Execute(t *testing.T) { out string }{{ name: "foo-bar", - payload: "../../../testdata/foo-bar/payload.yaml", - policies: []string{"../../../testdata/foo-bar/policy.yaml"}, - out: "../../../testdata/foo-bar/out.txt", + payload: "../../../test/foo-bar/payload.yaml", + policies: []string{"../../../test/foo-bar/policy.yaml"}, + out: "../../../test/foo-bar/out.txt", wantErr: false, }, { - name: "jim", - payload: "../../../testdata/jim/payload.json", - policies: []string{"../../../testdata/jim/policy.yaml"}, - out: "../../../testdata/jim/out.txt", + name: "wildcard", + payload: "../../../test/wildcard/payload.json", + policies: []string{"../../../test/wildcard/policy.yaml"}, + out: "../../../test/wildcard/out.txt", wantErr: false, }, { name: "pod-no-latest", - payload: "../../../testdata/pod-no-latest/payload.yaml", - policies: []string{"../../../testdata/pod-no-latest/policy.yaml"}, - out: "../../../testdata/pod-no-latest/out.txt", + payload: "../../../test/pod-no-latest/payload.yaml", + policies: []string{"../../../test/pod-no-latest/policy.yaml"}, + out: "../../../test/pod-no-latest/out.txt", wantErr: false, }, { name: "pod-all-latest", - payload: "../../../testdata/pod-all-latest/payload.yaml", - policies: []string{"../../../testdata/pod-all-latest/policy.yaml"}, - out: "../../../testdata/pod-all-latest/out.txt", + payload: "../../../test/pod-all-latest/payload.yaml", + policies: []string{"../../../test/pod-all-latest/policy.yaml"}, + out: "../../../test/pod-all-latest/out.txt", wantErr: false, }, { name: "scripted", - payload: "../../../testdata/scripted/payload.yaml", - policies: []string{"../../../testdata/scripted/policy.yaml"}, - out: "../../../testdata/scripted/out.txt", + payload: "../../../test/scripted/payload.yaml", + policies: []string{"../../../test/scripted/policy.yaml"}, + out: "../../../test/scripted/out.txt", wantErr: false, }, { name: "payload-yaml", - payload: "../../../testdata/payload-yaml/payload.yaml", + payload: "../../../test/payload-yaml/payload.yaml", preprocessors: []string{"planned_values.root_module.resources"}, - policies: []string{"../../../testdata/payload-yaml/policy.yaml"}, - out: "../../../testdata/payload-yaml/out.txt", + policies: []string{"../../../test/payload-yaml/policy.yaml"}, + out: "../../../test/payload-yaml/out.txt", wantErr: false, }, { name: "tf-plan", - payload: "../../../testdata/tf-plan/tf.plan.json", + payload: "../../../test/tf-plan/payload.json", preprocessors: []string{"planned_values.root_module.resources"}, - policies: []string{"../../../testdata/tf-plan/policy.yaml"}, - out: "../../../testdata/tf-plan/out.txt", + policies: []string{"../../../test/tf-plan/policy.yaml"}, + out: "../../../test/tf-plan/out.txt", wantErr: false, }, { name: "escaped", - payload: "../../../testdata/escaped/payload.yaml", - policies: []string{"../../../testdata/escaped/policy.yaml"}, - out: "../../../testdata/escaped/out.txt", + payload: "../../../test/escaped/payload.yaml", + policies: []string{"../../../test/escaped/policy.yaml"}, + out: "../../../test/escaped/out.txt", wantErr: false, }, { name: "dockerfile", - payload: "../../../testdata/dockerfile/input.json", - policies: []string{"../../../testdata/dockerfile/policy-check-external.yaml"}, - out: "../../../testdata/dockerfile/out.txt", + payload: "../../../test/dockerfile/payload.json", + policies: []string{"../../../test/dockerfile/policy.yaml"}, + out: "../../../test/dockerfile/out.txt", wantErr: false, }} for _, tt := range tests { diff --git a/pkg/engine/assert/match.go b/pkg/engine/assert/match.go index cfafad86..1b77d4fc 100644 --- a/pkg/engine/assert/match.go +++ b/pkg/engine/assert/match.go @@ -19,46 +19,48 @@ func MatchAssert(ctx context.Context, path *field.Path, match *v1alpha1.Assert, return nil, field.Invalid(path, match, "an empty assert is not valid") } else { if len(match.Any) != 0 { - var errs []error + var fails []error path := path.Child("any") for i, assertion := range match.Any { - _errs, err := validate(ctx, path.Index(i).Child("check"), assertion.Check.Value, actual, bindings) + checkFails, err := validate(ctx, path.Index(i).Child("check"), assertion.Check.Value, actual, bindings) if err != nil { - return errs, err + return fails, err } - if len(_errs) == 0 { - errs = nil + if len(checkFails) == 0 { + fails = nil break } if assertion.Message != "" { - errs = append(errs, errors.New(template.String(ctx, assertion.Message, actual, bindings))) + msg := template.String(ctx, assertion.Message, actual, bindings) + msg += ": " + checkFails.ToAggregate().Error() + fails = append(fails, errors.New(msg)) } else { - for _, err := range _errs { - errs = append(errs, err) - } + fails = append(fails, checkFails.ToAggregate()) } } - if errs != nil { - return errs, nil + if fails != nil { + return fails, nil } } if len(match.All) != 0 { - var errs []error + var fails []error path := path.Child("all") for i, assertion := range match.All { - _errs, err := validate(ctx, path.Index(i).Child("check"), assertion.Check.Value, actual, bindings) + checkFails, err := validate(ctx, path.Index(i).Child("check"), assertion.Check.Value, actual, bindings) if err != nil { - return errs, err + return fails, err } - if assertion.Message != "" { - errs = append(errs, errors.New(template.String(ctx, assertion.Message, actual, bindings))) - } else { - for _, err := range _errs { - errs = append(errs, err) + if len(checkFails) > 0 { + if assertion.Message != "" { + msg := template.String(ctx, assertion.Message, actual, bindings) + msg += ": " + checkFails.ToAggregate().Error() + fails = append(fails, errors.New(msg)) + } else { + fails = append(fails, checkFails.ToAggregate()) } } } - return errs, nil + return fails, nil } return nil, nil } diff --git a/pkg/engine/assert/parse.go b/pkg/engine/assert/parse.go index 8ad84b03..88f45899 100644 --- a/pkg/engine/assert/parse.go +++ b/pkg/engine/assert/parse.go @@ -5,7 +5,6 @@ import ( "fmt" "reflect" - "github.com/jmespath-community/go-jmespath/pkg/binding" jpbinding "github.com/jmespath-community/go-jmespath/pkg/binding" "github.com/kyverno/kyverno-json/pkg/engine/match" "github.com/kyverno/kyverno-json/pkg/engine/template" @@ -97,7 +96,7 @@ func (n mapNode) assert(ctx context.Context, path *field.Path, value interface{} // if lengths match all descendants are evaluated with their corresponding items. type sliceNode []Assertion -func (n sliceNode) assert(ctx context.Context, path *field.Path, value interface{}, bindings binding.Bindings) (field.ErrorList, error) { +func (n sliceNode) assert(ctx context.Context, path *field.Path, value interface{}, bindings jpbinding.Bindings) (field.ErrorList, error) { var errs field.ErrorList if reflectutils.GetKind(value) != reflect.Slice { return nil, field.TypeInvalid(path, value, "expected a slice") @@ -125,7 +124,7 @@ type scalarNode struct { rhs interface{} } -func (n *scalarNode) assert(ctx context.Context, path *field.Path, value interface{}, bindings binding.Bindings) (field.ErrorList, error) { +func (n *scalarNode) assert(ctx context.Context, path *field.Path, value interface{}, bindings jpbinding.Bindings) (field.ErrorList, error) { rhs := n.rhs expression := parseExpression(ctx, rhs) // we only project if the expression uses the engine syntax diff --git a/pkg/json-engine/engine.go b/pkg/json-engine/engine.go index db76d604..4e6c4980 100644 --- a/pkg/json-engine/engine.go +++ b/pkg/json-engine/engine.go @@ -65,11 +65,7 @@ func New() engine.Engine[JsonEngineRequest, JsonEngineResponse] { if err != nil { response.Failure = err } else if err := multierr.Combine(errs...); err != nil { - // if r.rule.Validation.Message != "" { - // response.Error = errors.New(template.String(ctx, r.rule.Validation.Message, r.value, r.bindings)) - // } else { response.Error = err - // } } return response }). diff --git a/pkg/utils/rest/rest_test.go b/pkg/utils/rest/rest_test.go index 50b88617..ad4c0128 100644 --- a/pkg/utils/rest/rest_test.go +++ b/pkg/utils/rest/rest_test.go @@ -14,7 +14,7 @@ func TestRestConfig(t *testing.T) { wantErr bool }{{ name: "empty", - kubeConfig: "../../../testdata/.kube/config", + kubeConfig: "../../../test/.kube/config", }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/testdata/.kube/config b/test/.kube/config similarity index 100% rename from testdata/.kube/config rename to test/.kube/config diff --git a/testdata/api/README.md b/test/api/README.md similarity index 100% rename from testdata/api/README.md rename to test/api/README.md diff --git a/testdata/dockerfile/Dockerfile b/test/dockerfile/Dockerfile similarity index 100% rename from testdata/dockerfile/Dockerfile rename to test/dockerfile/Dockerfile diff --git a/testdata/dockerfile/README.md b/test/dockerfile/README.md similarity index 100% rename from testdata/dockerfile/README.md rename to test/dockerfile/README.md diff --git a/test/dockerfile/out.txt b/test/dockerfile/out.txt new file mode 100644 index 00000000..7efeb595 --- /dev/null +++ b/test/dockerfile/out.txt @@ -0,0 +1,6 @@ +Loading policies ... +Loading payload ... +Pre processing ... +Running ( evaluating 1 resource against 1 policy ) ... +- check-dockerfile / no-external / FAILED: HTTP calls are not allowed: all[0].check.~.(Stages[].Commands[].Args[].Value)[0].(contains(@, 'https://') || contains(@, 'http://')): Invalid value: true: Expected value: false; wget is not allowed: all[3].check.~.(Stages[].Commands[].CmdLine[])[0].(contains(@, 'wget')): Invalid value: true: Expected value: false +Done diff --git a/testdata/dockerfile/input.json b/test/dockerfile/payload.json similarity index 100% rename from testdata/dockerfile/input.json rename to test/dockerfile/payload.json diff --git a/test/dockerfile/policy.yaml b/test/dockerfile/policy.yaml new file mode 100644 index 00000000..54b1b3f3 --- /dev/null +++ b/test/dockerfile/policy.yaml @@ -0,0 +1,26 @@ +apiVersion: json.kyverno.io/v1alpha1 +kind: Policy +metadata: + name: check-dockerfile +spec: + rules: + - name: no-external + validate: + assert: + all: + - message: "HTTP calls are not allowed" + check: + ~.(Stages[].Commands[].Args[].Value): + (contains(@, 'https://') || contains(@, 'http://')): false + - message: "HTTP calls are not allowed" + check: + ~.(Stages[].Commands[].CmdLine[]): + (contains(@, 'https://') || contains(@, 'http://')): false + - message: "curl is not allowed" + check: + ~.(Stages[].Commands[].CmdLine[]): + (contains(@, 'curl')): false + - message: "wget is not allowed" + check: + ~.(Stages[].Commands[].CmdLine[]): + (contains(@, 'wget')): false \ No newline at end of file diff --git a/testdata/escaped/out.txt b/test/escaped/out.txt similarity index 100% rename from testdata/escaped/out.txt rename to test/escaped/out.txt diff --git a/testdata/escaped/payload.yaml b/test/escaped/payload.yaml similarity index 100% rename from testdata/escaped/payload.yaml rename to test/escaped/payload.yaml diff --git a/testdata/escaped/policy.yaml b/test/escaped/policy.yaml similarity index 100% rename from testdata/escaped/policy.yaml rename to test/escaped/policy.yaml diff --git a/testdata/foo-bar/out.txt b/test/foo-bar/out.txt similarity index 100% rename from testdata/foo-bar/out.txt rename to test/foo-bar/out.txt diff --git a/testdata/foo-bar/payload.yaml b/test/foo-bar/payload.yaml similarity index 100% rename from testdata/foo-bar/payload.yaml rename to test/foo-bar/payload.yaml diff --git a/testdata/foo-bar/policy.yaml b/test/foo-bar/policy.yaml similarity index 100% rename from testdata/foo-bar/policy.yaml rename to test/foo-bar/policy.yaml diff --git a/testdata/tf-plan/out.txt b/test/payload-yaml/out.txt similarity index 55% rename from testdata/tf-plan/out.txt rename to test/payload-yaml/out.txt index afc4a272..9a806e35 100644 --- a/testdata/tf-plan/out.txt +++ b/test/payload-yaml/out.txt @@ -2,5 +2,5 @@ Loading policies ... Loading payload ... Pre processing ... Running ( evaluating 1 resource against 1 policy ) ... -- required-s3-tags / require-team-tag / aws_s3_bucket.example FAILED: Bucket `example` (aws_s3_bucket.example) does not have the required tags {"Team":"Kyverno"} +- required-s3-tags / require-team-tag / aws_s3_bucket.example FAILED: Bucket `example` (aws_s3_bucket.example) does not have the required tags {"Team":"Kyverno"}: all[0].check.values.tags: Invalid value: map[string]interface {}{"Environment":"Dev", "Name":"My bucket"}: Expected value: map[string]interface {}{"Team":"Kyverno"} Done diff --git a/testdata/payload-yaml/payload.yaml b/test/payload-yaml/payload.yaml similarity index 100% rename from testdata/payload-yaml/payload.yaml rename to test/payload-yaml/payload.yaml diff --git a/testdata/payload-yaml/policy.yaml b/test/payload-yaml/policy.yaml similarity index 100% rename from testdata/payload-yaml/policy.yaml rename to test/payload-yaml/policy.yaml diff --git a/testdata/pod-all-latest/out.txt b/test/pod-all-latest/out.txt similarity index 100% rename from testdata/pod-all-latest/out.txt rename to test/pod-all-latest/out.txt diff --git a/testdata/pod-all-latest/payload.yaml b/test/pod-all-latest/payload.yaml similarity index 100% rename from testdata/pod-all-latest/payload.yaml rename to test/pod-all-latest/payload.yaml diff --git a/testdata/pod-all-latest/policy.yaml b/test/pod-all-latest/policy.yaml similarity index 100% rename from testdata/pod-all-latest/policy.yaml rename to test/pod-all-latest/policy.yaml diff --git a/test/pod-no-latest/out.txt b/test/pod-no-latest/out.txt new file mode 100644 index 00000000..027ef068 --- /dev/null +++ b/test/pod-no-latest/out.txt @@ -0,0 +1,6 @@ +Loading policies ... +Loading payload ... +Pre processing ... +Running ( evaluating 1 resource against 1 policy ) ... +- test / pod-no-latest / FAILED: [all[0].check.spec.~foo.containers->foos[0].(at($foos, $foo).image)->foo.(ends_with($foo, $tag)): Invalid value: true: Expected value: false, all[0].check.spec.~foo.containers->foos[1].(at($foos, $foo).image)->foo.(ends_with($foo, $tag)): Invalid value: true: Expected value: false, all[0].check.spec.~foo.containers->foos[2].(at($foos, $foo).image)->foo.(ends_with($foo, $tag)): Invalid value: true: Expected value: false]; [all[1].check.spec.~.containers->foo[0].image.(ends_with(@, ':latest')): Invalid value: true: Expected value: false, all[1].check.spec.~.containers->foo[1].image.(ends_with(@, ':latest')): Invalid value: true: Expected value: false, all[1].check.spec.~.containers->foo[2].image.(ends_with(@, ':latest')): Invalid value: true: Expected value: false]; [all[2].check.~index.(spec.containers[*].image)->images[0].(ends_with(@, ':latest')): Invalid value: true: Expected value: false, all[2].check.~index.(spec.containers[*].image)->images[1].(ends_with(@, ':latest')): Invalid value: true: Expected value: false, all[2].check.~index.(spec.containers[*].image)->images[2].(ends_with(@, ':latest')): Invalid value: true: Expected value: false] +Done diff --git a/testdata/pod-no-latest/payload.yaml b/test/pod-no-latest/payload.yaml similarity index 100% rename from testdata/pod-no-latest/payload.yaml rename to test/pod-no-latest/payload.yaml diff --git a/testdata/pod-no-latest/policy.yaml b/test/pod-no-latest/policy.yaml similarity index 100% rename from testdata/pod-no-latest/policy.yaml rename to test/pod-no-latest/policy.yaml diff --git a/testdata/scripted/out.txt b/test/scripted/out.txt similarity index 100% rename from testdata/scripted/out.txt rename to test/scripted/out.txt diff --git a/testdata/scripted/payload.yaml b/test/scripted/payload.yaml similarity index 100% rename from testdata/scripted/payload.yaml rename to test/scripted/payload.yaml diff --git a/testdata/scripted/policy.yaml b/test/scripted/policy.yaml similarity index 100% rename from testdata/scripted/policy.yaml rename to test/scripted/policy.yaml diff --git a/testdata/payload-yaml/out.txt b/test/tf-plan/out.txt similarity index 55% rename from testdata/payload-yaml/out.txt rename to test/tf-plan/out.txt index afc4a272..9a806e35 100644 --- a/testdata/payload-yaml/out.txt +++ b/test/tf-plan/out.txt @@ -2,5 +2,5 @@ Loading policies ... Loading payload ... Pre processing ... Running ( evaluating 1 resource against 1 policy ) ... -- required-s3-tags / require-team-tag / aws_s3_bucket.example FAILED: Bucket `example` (aws_s3_bucket.example) does not have the required tags {"Team":"Kyverno"} +- required-s3-tags / require-team-tag / aws_s3_bucket.example FAILED: Bucket `example` (aws_s3_bucket.example) does not have the required tags {"Team":"Kyverno"}: all[0].check.values.tags: Invalid value: map[string]interface {}{"Environment":"Dev", "Name":"My bucket"}: Expected value: map[string]interface {}{"Team":"Kyverno"} Done diff --git a/testdata/tf-plan/tf.plan.json b/test/tf-plan/payload.json similarity index 100% rename from testdata/tf-plan/tf.plan.json rename to test/tf-plan/payload.json diff --git a/testdata/tf-plan/policy.yaml b/test/tf-plan/policy.yaml similarity index 100% rename from testdata/tf-plan/policy.yaml rename to test/tf-plan/policy.yaml diff --git a/testdata/jim/out.txt b/test/wildcard/out.txt similarity index 100% rename from testdata/jim/out.txt rename to test/wildcard/out.txt diff --git a/testdata/jim/payload.json b/test/wildcard/payload.json similarity index 100% rename from testdata/jim/payload.json rename to test/wildcard/payload.json diff --git a/testdata/jim/policy.yaml b/test/wildcard/policy.yaml similarity index 100% rename from testdata/jim/policy.yaml rename to test/wildcard/policy.yaml diff --git a/testdata/dockerfile/out.txt b/testdata/dockerfile/out.txt deleted file mode 100644 index fa109fe3..00000000 --- a/testdata/dockerfile/out.txt +++ /dev/null @@ -1,6 +0,0 @@ -Loading policies ... -Loading payload ... -Pre processing ... -Running ( evaluating 1 resource against 1 policy ) ... -- check-dockerfile / no-external / FAILED: HTTP calls are not allowed; curl / wget are not allowed -Done diff --git a/testdata/dockerfile/policy-check-external.yaml b/testdata/dockerfile/policy-check-external.yaml deleted file mode 100644 index c72e9b99..00000000 --- a/testdata/dockerfile/policy-check-external.yaml +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: json.kyverno.io/v1alpha1 -kind: Policy -metadata: - name: check-dockerfile -spec: - rules: - - name: no-external - validate: - assert: - all: - - message: "HTTP calls are not allowed" - check: - ~.(Stages[].Commands[].Args[].Value): - (contains(@, 'https://') || contains(@, 'http://')): false - - message: "curl / wget are not allowed" - check: - ~.(Stages[].Commands[].CmdLine[]): - (contains(@, 'wget') || contains(@, 'curl')): false \ No newline at end of file diff --git a/testdata/pod-no-latest/out.txt b/testdata/pod-no-latest/out.txt deleted file mode 100644 index 0ee0d2e9..00000000 --- a/testdata/pod-no-latest/out.txt +++ /dev/null @@ -1,6 +0,0 @@ -Loading policies ... -Loading payload ... -Pre processing ... -Running ( evaluating 1 resource against 1 policy ) ... -- test / pod-no-latest / FAILED: all[0].check.spec.~foo.containers->foos[0].(at($foos, $foo).image)->foo.(ends_with($foo, $tag)): Invalid value: true: Expected value: false; all[0].check.spec.~foo.containers->foos[1].(at($foos, $foo).image)->foo.(ends_with($foo, $tag)): Invalid value: true: Expected value: false; all[0].check.spec.~foo.containers->foos[2].(at($foos, $foo).image)->foo.(ends_with($foo, $tag)): Invalid value: true: Expected value: false; all[1].check.spec.~.containers->foo[0].image.(ends_with(@, ':latest')): Invalid value: true: Expected value: false; all[1].check.spec.~.containers->foo[1].image.(ends_with(@, ':latest')): Invalid value: true: Expected value: false; all[1].check.spec.~.containers->foo[2].image.(ends_with(@, ':latest')): Invalid value: true: Expected value: false; all[2].check.~index.(spec.containers[*].image)->images[0].(ends_with(@, ':latest')): Invalid value: true: Expected value: false; all[2].check.~index.(spec.containers[*].image)->images[1].(ends_with(@, ':latest')): Invalid value: true: Expected value: false; all[2].check.~index.(spec.containers[*].image)->images[2].(ends_with(@, ':latest')): Invalid value: true: Expected value: false -Done