From 601d3a0598af4590aded83caa88a203a99bd9b94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Charles-Edouard=20Br=C3=A9t=C3=A9ch=C3=A9?= Date: Wed, 18 Oct 2023 20:46:34 +0200 Subject: [PATCH] feat: allow custom resource identifier (#114) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Charles-Edouard Brétéché --- docs/user/commands/kyverno-json_scan.md | 1 + pkg/commands/scan/command.go | 1 + pkg/commands/scan/command_test.go | 39 +++++++++++++--------- pkg/commands/scan/options.go | 12 +++++-- test/dockerfile/out.txt | 2 +- test/escaped/out.txt | 2 +- test/foo-bar/out.txt | 2 +- test/pod-all-latest/out.txt | 2 +- test/pod-no-latest/out.txt | 2 +- test/scripted/out.txt | 2 +- test/wildcard/out.txt | 2 +- website/docs/commands/kyverno-json_scan.md | 1 + 12 files changed, 44 insertions(+), 24 deletions(-) diff --git a/docs/user/commands/kyverno-json_scan.md b/docs/user/commands/kyverno-json_scan.md index 10c18cd6..ed3a701d 100644 --- a/docs/user/commands/kyverno-json_scan.md +++ b/docs/user/commands/kyverno-json_scan.md @@ -14,6 +14,7 @@ kyverno-json scan [flags] ``` -h, --help help for scan + --identifier string JmesPath expression used to identify a resource --labels strings Labels selectors for policies --payload string Path to payload (json or yaml file) --policy strings Path to kyverno-json policies diff --git a/pkg/commands/scan/command.go b/pkg/commands/scan/command.go index 628ca0b3..0e29a2a9 100644 --- a/pkg/commands/scan/command.go +++ b/pkg/commands/scan/command.go @@ -18,5 +18,6 @@ func Command() *cobra.Command { cmd.Flags().StringSliceVar(&command.preprocessors, "pre-process", nil, "JmesPath expression used to pre process payload") cmd.Flags().StringSliceVar(&command.policies, "policy", nil, "Path to kyverno-json policies") cmd.Flags().StringSliceVar(&command.selectors, "labels", nil, "Labels selectors for policies") + cmd.Flags().StringVar(&command.identifier, "identifier", "", "JmesPath expression used to identify a resource") return cmd } diff --git a/pkg/commands/scan/command_test.go b/pkg/commands/scan/command_test.go index ccce6849..196370e4 100644 --- a/pkg/commands/scan/command_test.go +++ b/pkg/commands/scan/command_test.go @@ -15,6 +15,7 @@ func Test_Execute(t *testing.T) { payload string preprocessors []string policies []string + identifier string wantErr bool out string }{{ @@ -24,23 +25,26 @@ func Test_Execute(t *testing.T) { out: "../../../test/foo-bar/out.txt", wantErr: false, }, { - name: "wildcard", - payload: "../../../test/wildcard/payload.json", - policies: []string{"../../../test/wildcard/policy.yaml"}, - out: "../../../test/wildcard/out.txt", - wantErr: false, + name: "wildcard", + payload: "../../../test/wildcard/payload.json", + policies: []string{"../../../test/wildcard/policy.yaml"}, + identifier: "name", + out: "../../../test/wildcard/out.txt", + wantErr: false, }, { - name: "pod-no-latest", - 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-no-latest", + payload: "../../../test/pod-no-latest/payload.yaml", + policies: []string{"../../../test/pod-no-latest/policy.yaml"}, + identifier: "metadata.name", + out: "../../../test/pod-no-latest/out.txt", + wantErr: false, }, { - name: "pod-all-latest", - 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: "pod-all-latest", + payload: "../../../test/pod-all-latest/payload.yaml", + policies: []string{"../../../test/pod-all-latest/policy.yaml"}, + identifier: "metadata.name", + out: "../../../test/pod-all-latest/out.txt", + wantErr: false, }, { name: "scripted", payload: "../../../test/scripted/payload.yaml", @@ -52,6 +56,7 @@ func Test_Execute(t *testing.T) { payload: "../../../test/payload-yaml/payload.yaml", preprocessors: []string{"planned_values.root_module.resources"}, policies: []string{"../../../test/payload-yaml/policy.yaml"}, + identifier: "address", out: "../../../test/payload-yaml/out.txt", wantErr: false, }, { @@ -59,6 +64,7 @@ func Test_Execute(t *testing.T) { payload: "../../../test/tf-plan/payload.json", preprocessors: []string{"planned_values.root_module.resources"}, policies: []string{"../../../test/tf-plan/policy.yaml"}, + identifier: "address", out: "../../../test/tf-plan/out.txt", wantErr: false, }, { @@ -87,6 +93,9 @@ func Test_Execute(t *testing.T) { args = append(args, "--policy", policy) } args = append(args, "--payload", tt.payload) + if tt.identifier != "" { + args = append(args, "--identifier", tt.identifier) + } cmd.SetArgs(args) out := bytes.NewBufferString("") cmd.SetOut(out) diff --git a/pkg/commands/scan/options.go b/pkg/commands/scan/options.go index 3ad7ae03..28a90d5a 100644 --- a/pkg/commands/scan/options.go +++ b/pkg/commands/scan/options.go @@ -13,7 +13,6 @@ import ( "github.com/kyverno/kyverno-json/pkg/policy" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/output/pluralize" "github.com/spf13/cobra" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" ) @@ -22,6 +21,7 @@ type options struct { preprocessors []string policies []string selectors []string + identifier string } func (c *options) run(cmd *cobra.Command, _ []string) error { @@ -80,7 +80,15 @@ func (c *options) run(cmd *cobra.Command, _ []string) error { Policies: policies, }) for _, response := range responses { - resourceName, _, _ := unstructured.NestedString(response.Resource.(map[string]interface{}), "address") + resourceName := "(unknown)" + if c.identifier != "" { + result, err := template.Execute(context.Background(), c.identifier, response.Resource, nil) + if err != nil { + resourceName = fmt.Sprintf("(error: %s)", err) + } else { + resourceName = fmt.Sprint(result) + } + } if response.Failure != nil { fmt.Fprintln(out, "-", response.Policy.Name, "/", response.Rule.Name, "/", resourceName, "ERROR:", response.Failure) } else { diff --git a/test/dockerfile/out.txt b/test/dockerfile/out.txt index acf94cd4..4a4a78e7 100644 --- a/test/dockerfile/out.txt +++ b/test/dockerfile/out.txt @@ -2,5 +2,5 @@ Loading policies ... Loading payload ... Pre processing ... Running ( evaluating 1 resource against 1 policy ) ... -- check-dockerfile / deny-external-calls / 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 +- check-dockerfile / deny-external-calls / (unknown) 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/test/escaped/out.txt b/test/escaped/out.txt index 7592126b..99abf8a9 100644 --- a/test/escaped/out.txt +++ b/test/escaped/out.txt @@ -2,5 +2,5 @@ Loading policies ... Loading payload ... Pre processing ... Running ( evaluating 1 resource against 1 policy ) ... -- test / foo-bar-4 / PASSED +- test / foo-bar-4 / (unknown) PASSED Done diff --git a/test/foo-bar/out.txt b/test/foo-bar/out.txt index 7592126b..99abf8a9 100644 --- a/test/foo-bar/out.txt +++ b/test/foo-bar/out.txt @@ -2,5 +2,5 @@ Loading policies ... Loading payload ... Pre processing ... Running ( evaluating 1 resource against 1 policy ) ... -- test / foo-bar-4 / PASSED +- test / foo-bar-4 / (unknown) PASSED Done diff --git a/test/pod-all-latest/out.txt b/test/pod-all-latest/out.txt index 1665e958..980a4d52 100644 --- a/test/pod-all-latest/out.txt +++ b/test/pod-all-latest/out.txt @@ -2,5 +2,5 @@ Loading policies ... Loading payload ... Pre processing ... Running ( evaluating 1 resource against 1 policy ) ... -- test / pod-no-latest / PASSED +- test / pod-no-latest / webserver PASSED Done diff --git a/test/pod-no-latest/out.txt b/test/pod-no-latest/out.txt index 027ef068..2e8b82bc 100644 --- a/test/pod-no-latest/out.txt +++ b/test/pod-no-latest/out.txt @@ -2,5 +2,5 @@ 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] +- test / pod-no-latest / webserver 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/test/scripted/out.txt b/test/scripted/out.txt index 7592126b..99abf8a9 100644 --- a/test/scripted/out.txt +++ b/test/scripted/out.txt @@ -2,5 +2,5 @@ Loading policies ... Loading payload ... Pre processing ... Running ( evaluating 1 resource against 1 policy ) ... -- test / foo-bar-4 / PASSED +- test / foo-bar-4 / (unknown) PASSED Done diff --git a/test/wildcard/out.txt b/test/wildcard/out.txt index 319a8d00..0f416f3c 100644 --- a/test/wildcard/out.txt +++ b/test/wildcard/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 / FAILED: all[0].check.tags.(wildcard('?*', Team)): Invalid value: true: Expected value: false +- required-s3-tags / require-team-tag / bucket1 FAILED: all[0].check.tags.(wildcard('?*', Team)): Invalid value: true: Expected value: false Done diff --git a/website/docs/commands/kyverno-json_scan.md b/website/docs/commands/kyverno-json_scan.md index 10c18cd6..ed3a701d 100644 --- a/website/docs/commands/kyverno-json_scan.md +++ b/website/docs/commands/kyverno-json_scan.md @@ -14,6 +14,7 @@ kyverno-json scan [flags] ``` -h, --help help for scan + --identifier string JmesPath expression used to identify a resource --labels strings Labels selectors for policies --payload string Path to payload (json or yaml file) --policy strings Path to kyverno-json policies