Skip to content

Commit

Permalink
fix: path missing assert element (#517)
Browse files Browse the repository at this point in the history
Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
  • Loading branch information
eddycharly authored Sep 24, 2024
1 parent 4cb9d0f commit 123686c
Show file tree
Hide file tree
Showing 11 changed files with 41 additions and 40 deletions.
27 changes: 13 additions & 14 deletions pkg/json-engine/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func (c *compiler) compileContextEntry(
}, nil
}

func (c *compiler) compileContext(
func (c *compiler) compileContextEntries(
path *field.Path,
compilers compilers.Compilers,
in ...v1alpha1.ContextEntry,
Expand Down Expand Up @@ -310,11 +310,11 @@ func (c *compiler) compileRule(
path *field.Path,
compilers compilers.Compilers,
in v1alpha1.ValidatingRule,
) (func(any, binding.Bindings) []RuleResponse, error) {
) (func(any, binding.Bindings) *RuleResponse, error) {
if in.Compiler != nil {
compilers = compilers.WithDefaultCompiler(string(*in.Compiler))
}
context, err := c.compileContext(path.Child("context"), compilers, in.Context...)
context, err := c.compileContextEntries(path.Child("context"), compilers, in.Context...)
if err != nil {
return nil, err
}
Expand All @@ -334,24 +334,23 @@ func (c *compiler) compileRule(
if err != nil {
return nil, err
}
// TODO: fix path
assert, err := c.compileAssert(nil, compilers, in.Assert)
assert, err := c.compileAssert(path.Child("assert"), compilers, in.Assert)
if err != nil {
return nil, err
}
return func(resource any, bindings binding.Bindings) []RuleResponse {
return func(resource any, bindings binding.Bindings) *RuleResponse {
// register context bindings
bindings = context(resource, bindings)
// process match clause
if match != nil {
if errs, err := match(resource, bindings); err != nil {
return []RuleResponse{{
return &RuleResponse{
Rule: in,
Timestamp: time.Now(),
Identifier: identifier(resource, bindings),
Feedback: feedback(resource, bindings),
Error: err,
}}
}
} else if len(errs) != 0 {
// didn't match
return nil
Expand All @@ -360,13 +359,13 @@ func (c *compiler) compileRule(
// process exclude clause
if exclude != nil {
if errs, err := exclude(resource, bindings); err != nil {
return []RuleResponse{{
return &RuleResponse{
Rule: in,
Timestamp: time.Now(),
Identifier: identifier(resource, bindings),
Feedback: feedback(resource, bindings),
Error: err,
}}
}
} else if len(errs) == 0 {
// matched
return nil
Expand All @@ -375,20 +374,20 @@ func (c *compiler) compileRule(
// evaluate assertions
violations, err := assert(resource, bindings)
if err != nil {
return []RuleResponse{{
return &RuleResponse{
Rule: in,
Timestamp: time.Now(),
Identifier: identifier(resource, bindings),
Feedback: feedback(resource, bindings),
Error: err,
}}
}
}
return []RuleResponse{{
return &RuleResponse{
Rule: in,
Timestamp: time.Now(),
Identifier: identifier(resource, bindings),
Feedback: feedback(resource, bindings),
Violations: violations,
}}
}
}, nil
}
12 changes: 7 additions & 5 deletions pkg/json-engine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,18 @@ func New() engine.Engine[Request, Response] {
compilers := compilers.DefaultCompilers.WithDefaultCompiler(compilers.CompilerJP)
ruleCompiler := compiler{}
ruleEngine := builder.
Function(func(ctx context.Context, r ruleRequest) []RuleResponse {
Function(func(ctx context.Context, r ruleRequest) *RuleResponse {
compilers := compilers
if r.policy.Spec.Compiler != nil {
compilers = compilers.WithDefaultCompiler(string(*r.policy.Spec.Compiler))
}
compiled, err := ruleCompiler.compileRule(nil, compilers, r.rule)
if err != nil {
return []RuleResponse{{
return &RuleResponse{
Rule: r.rule,
Timestamp: time.Now(),
Error: err,
}}
}
}
bindings := r.bindings.Register("$rule", binding.NewBinding(r.rule))
return compiled(r.resource, bindings)
Expand All @@ -49,12 +49,14 @@ func New() engine.Engine[Request, Response] {
}
bindings := r.bindings.Register("$policy", binding.NewBinding(r.policy))
for _, rule := range r.policy.Spec.Rules {
response.Rules = append(response.Rules, ruleEngine.Run(ctx, ruleRequest{
if ruleResponse := ruleEngine.Run(ctx, ruleRequest{
policy: r.policy,
rule: rule,
resource: r.resource,
bindings: bindings.Register("$rule", binding.NewBinding(rule)),
})...)
}); ruleResponse != nil {
response.Rules = append(response.Rules, *ruleResponse)
}
}
return response
})
Expand Down
2 changes: 1 addition & 1 deletion pkg/server/ui/dist/assets/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
{
"category": "Dockerfile",
"name": "check-dockerfile",
"policy": "apiVersion: json.kyverno.io/v1alpha1\nkind: ValidatingPolicy\nmetadata:\n name: check-dockerfile\nspec:\n rules:\n - name: deny-external-calls\n assert:\n all:\n - message: \"HTTP calls are not allowed\"\n check:\n ~.(Stages[].Commands[].Args[].Value):\n (contains(@, 'https://') || contains(@, 'http://')): false\n - message: \"HTTP calls are not allowed\"\n check:\n ~.(Stages[].Commands[].CmdLine[]):\n (contains(@, 'https://') || contains(@, 'http://')): false\n - message: \"curl is not allowed\"\n check:\n ~.(Stages[].Commands[].CmdLine[]):\n (contains(@, 'curl')): false\n - message: \"wget is not allowed\"\n check:\n ~.(Stages[].Commands[].CmdLine[]):\n (contains(@, 'wget')): false",
"policy": "apiVersion: json.kyverno.io/v1alpha1\nkind: ValidatingPolicy\nmetadata:\n name: check-dockerfile\nspec:\n rules:\n - name: deny-external-calls\n assert:\n all:\n - message: HTTP calls are not allowed\n check:\n ~.(Stages[].Commands[].Args[].Value):\n (contains(@, 'https://') || contains(@, 'http://')): false\n - message: HTTP calls are not allowed\n check:\n ~.(Stages[].Commands[].CmdLine[]):\n (contains(@, 'https://') || contains(@, 'http://')): false\n - message: curl is not allowed\n check:\n ~.(Stages[].Commands[].CmdLine[]):\n (contains(@, 'curl')): false\n - message: wget is not allowed\n check:\n ~.(Stages[].Commands[].CmdLine[]):\n (contains(@, 'wget')): false",
"payload": "MetaArgs:\n- DefaultValue: '\"linux/amd64\"'\n Key: BUILD_PLATFORM\n ProvidedValue: null\n Value: '\"linux/amd64\"'\n- DefaultValue: '\"golang:1.20.6-alpine3.18\"'\n Key: BUILDER_IMAGE\n ProvidedValue: null\n Value: '\"golang:1.20.6-alpine3.18\"'\nStages:\n- As: builder\n BaseName: '\"golang:1.20.6-alpine3.18\"'\n Commands:\n - Name: WORKDIR\n Path: /\n - Chmod: \"\"\n Chown: \"\"\n DestPath: ./\n From: \"\"\n Link: false\n Name: COPY\n SourceContents: null\n SourcePaths:\n - .\n - Args:\n - Comment: \"\"\n Key: SIGNER_BINARY_LINK\n Value: '\"https://d2hvyiie56hcat.cloudfront.net/linux/amd64/plugin/latest/notation-aws-signer-plugin.zip\"'\n Name: ARG\n - Args:\n - Comment: \"\"\n Key: SIGNER_BINARY_FILE\n Value: '\"notation-aws-signer-plugin.zip\"'\n Name: ARG\n - CmdLine:\n - wget -O ${SIGNER_BINARY_FILE} ${SIGNER_BINARY_LINK}\n Files: null\n FlagsUsed: []\n Name: RUN\n PrependShell: true\n - CmdLine:\n - apk update \u0026\u0026 apk add unzip \u0026\u0026 unzip -o ${SIGNER_BINARY_FILE}\n Files: null\n FlagsUsed: []\n Name: RUN\n PrependShell: true\n - CmdLine:\n - GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags=\"-w -s\" -o kyverno-notation-aws\n .\n Files: null\n FlagsUsed: []\n Name: RUN\n PrependShell: true\n Comment: \"\"\n From:\n Image: '\"golang:1.20.6-alpine3.18\"'\n Location:\n - End:\n Character: 0\n Line: 4\n Start:\n Character: 0\n Line: 4\n Name: builder\n Platform: $BUILD_PLATFORM\n SourceCode: FROM --platform=$BUILD_PLATFORM $BUILDER_IMAGE as builder\n- BaseName: gcr.io/distroless/static:nonroot\n Commands:\n - Name: WORKDIR\n Path: /\n - Env:\n - Key: PLUGINS_DIR\n Value: /plugins\n Name: ENV\n - Chmod: \"\"\n Chown: \"\"\n DestPath: plugins/com.amazonaws.signer.notation.plugin/notation-com.amazonaws.signer.notation.plugin\n From: builder\n Link: false\n Name: COPY\n SourceContents: null\n SourcePaths:\n - notation-com.amazonaws.signer.notation.plugin\n - Chmod: \"\"\n Chown: \"\"\n DestPath: kyverno-notation-aws\n From: builder\n Link: false\n Name: COPY\n SourceContents: null\n SourcePaths:\n - kyverno-notation-aws\n - CmdLine:\n - /kyverno-notation-aws\n Files: null\n Name: ENTRYPOINT\n PrependShell: false\n Comment: \"\"\n From:\n Image: gcr.io/distroless/static:nonroot\n Location:\n - End:\n Character: 0\n Line: 20\n Start:\n Character: 0\n Line: 20\n Name: \"\"\n Platform: \"\"\n SourceCode: FROM gcr.io/distroless/static:nonroot\n"
},
{
Expand Down
4 changes: 2 additions & 2 deletions test/commands/scan/dockerfile/out.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ 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
-> assert.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
-> assert.all[3].check.~.(Stages[].Commands[].CmdLine[])[0].(contains(@, 'wget')): Invalid value: true: Expected value: false
Done
8 changes: 4 additions & 4 deletions test/commands/scan/dockerfile/policy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@ spec:
- name: deny-external-calls
assert:
all:
- message: "HTTP calls are not allowed"
- message: HTTP calls are not allowed
check:
~.(Stages[].Commands[].Args[].Value):
(contains(@, 'https://') || contains(@, 'http://')): false
- message: "HTTP calls are not allowed"
- message: HTTP calls are not allowed
check:
~.(Stages[].Commands[].CmdLine[]):
(contains(@, 'https://') || contains(@, 'http://')): false
- message: "curl is not allowed"
- message: curl is not allowed
check:
~.(Stages[].Commands[].CmdLine[]):
(contains(@, 'curl')): false
- message: "wget is not allowed"
- message: wget is not allowed
check:
~.(Stages[].Commands[].CmdLine[]):
(contains(@, 'wget')): false
2 changes: 1 addition & 1 deletion test/commands/scan/payload-yaml/out.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ 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"}
-> all[0].check.values.tags: Invalid value: map[string]interface {}{"Environment":"Dev", "Name":"My bucket"}: Expected value: map[string]interface {}{"Team":"Kyverno"}
-> assert.all[0].check.values.tags: Invalid value: map[string]interface {}{"Environment":"Dev", "Name":"My bucket"}: Expected value: map[string]interface {}{"Team":"Kyverno"}
Done
18 changes: 9 additions & 9 deletions test/commands/scan/pod-no-latest/out.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ Loading payload ...
Pre processing ...
Running ( evaluating 1 resource against 1 policy ) ...
- 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
-> assert.all[0].check.spec.~foo.containers->foos[0].(at($foos, $foo).image)->foo.(ends_with($foo, $tag)): Invalid value: true: Expected value: false
-> assert.all[0].check.spec.~foo.containers->foos[1].(at($foos, $foo).image)->foo.(ends_with($foo, $tag)): Invalid value: true: Expected value: false
-> assert.all[0].check.spec.~foo.containers->foos[2].(at($foos, $foo).image)->foo.(ends_with($foo, $tag)): Invalid value: true: Expected value: false
-> assert.all[1].check.spec.~.containers->foo[0].image.(ends_with(@, ':latest')): Invalid value: true: Expected value: false
-> assert.all[1].check.spec.~.containers->foo[1].image.(ends_with(@, ':latest')): Invalid value: true: Expected value: false
-> assert.all[1].check.spec.~.containers->foo[2].image.(ends_with(@, ':latest')): Invalid value: true: Expected value: false
-> assert.all[2].check.~index.(spec.containers[*].image)->images[0].(ends_with(@, ':latest')): Invalid value: true: Expected value: false
-> assert.all[2].check.~index.(spec.containers[*].image)->images[1].(ends_with(@, ':latest')): Invalid value: true: Expected value: false
-> assert.all[2].check.~index.(spec.containers[*].image)->images[2].(ends_with(@, ':latest')): Invalid value: true: Expected value: false
Done
2 changes: 1 addition & 1 deletion test/commands/scan/tf-plan/out.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ 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"}
-> all[0].check.values.tags: Invalid value: map[string]interface {}{"Environment":"Dev", "Name":"My bucket"}: Expected value: map[string]interface {}{"Team":"Kyverno"}
-> assert.all[0].check.values.tags: Invalid value: map[string]interface {}{"Environment":"Dev", "Name":"My bucket"}: Expected value: map[string]interface {}{"Team":"Kyverno"}
Done
2 changes: 1 addition & 1 deletion test/commands/scan/tf-s3/out.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ Loading payload ...
Pre processing ...
Running ( evaluating 1 resource against 1 policy ) ...
- s3 / check-tags / FAILED
-> all[0].check.planned_values.root_module.~.resources[0].values.(keys(tags_all)).(contains(@, 'Team')): Invalid value: false: Expected value: true
-> assert.all[0].check.planned_values.root_module.~.resources[0].values.(keys(tags_all)).(contains(@, 'Team')): Invalid value: false: Expected value: true
Done
2 changes: 1 addition & 1 deletion test/commands/scan/wildcard/out.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ Loading payload ...
Pre processing ...
Running ( evaluating 1 resource against 1 policy ) ...
- required-s3-tags / require-team-tag / bucket1 FAILED
-> all[0].check.tags.(wildcard('?*', Team)): Invalid value: true: Expected value: false
-> assert.all[0].check.tags.(wildcard('?*', Team)): Invalid value: true: Expected value: false
Done
2 changes: 1 addition & 1 deletion website/playground/assets/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
{
"category": "Dockerfile",
"name": "check-dockerfile",
"policy": "apiVersion: json.kyverno.io/v1alpha1\nkind: ValidatingPolicy\nmetadata:\n name: check-dockerfile\nspec:\n rules:\n - name: deny-external-calls\n assert:\n all:\n - message: \"HTTP calls are not allowed\"\n check:\n ~.(Stages[].Commands[].Args[].Value):\n (contains(@, 'https://') || contains(@, 'http://')): false\n - message: \"HTTP calls are not allowed\"\n check:\n ~.(Stages[].Commands[].CmdLine[]):\n (contains(@, 'https://') || contains(@, 'http://')): false\n - message: \"curl is not allowed\"\n check:\n ~.(Stages[].Commands[].CmdLine[]):\n (contains(@, 'curl')): false\n - message: \"wget is not allowed\"\n check:\n ~.(Stages[].Commands[].CmdLine[]):\n (contains(@, 'wget')): false",
"policy": "apiVersion: json.kyverno.io/v1alpha1\nkind: ValidatingPolicy\nmetadata:\n name: check-dockerfile\nspec:\n rules:\n - name: deny-external-calls\n assert:\n all:\n - message: HTTP calls are not allowed\n check:\n ~.(Stages[].Commands[].Args[].Value):\n (contains(@, 'https://') || contains(@, 'http://')): false\n - message: HTTP calls are not allowed\n check:\n ~.(Stages[].Commands[].CmdLine[]):\n (contains(@, 'https://') || contains(@, 'http://')): false\n - message: curl is not allowed\n check:\n ~.(Stages[].Commands[].CmdLine[]):\n (contains(@, 'curl')): false\n - message: wget is not allowed\n check:\n ~.(Stages[].Commands[].CmdLine[]):\n (contains(@, 'wget')): false",
"payload": "MetaArgs:\n- DefaultValue: '\"linux/amd64\"'\n Key: BUILD_PLATFORM\n ProvidedValue: null\n Value: '\"linux/amd64\"'\n- DefaultValue: '\"golang:1.20.6-alpine3.18\"'\n Key: BUILDER_IMAGE\n ProvidedValue: null\n Value: '\"golang:1.20.6-alpine3.18\"'\nStages:\n- As: builder\n BaseName: '\"golang:1.20.6-alpine3.18\"'\n Commands:\n - Name: WORKDIR\n Path: /\n - Chmod: \"\"\n Chown: \"\"\n DestPath: ./\n From: \"\"\n Link: false\n Name: COPY\n SourceContents: null\n SourcePaths:\n - .\n - Args:\n - Comment: \"\"\n Key: SIGNER_BINARY_LINK\n Value: '\"https://d2hvyiie56hcat.cloudfront.net/linux/amd64/plugin/latest/notation-aws-signer-plugin.zip\"'\n Name: ARG\n - Args:\n - Comment: \"\"\n Key: SIGNER_BINARY_FILE\n Value: '\"notation-aws-signer-plugin.zip\"'\n Name: ARG\n - CmdLine:\n - wget -O ${SIGNER_BINARY_FILE} ${SIGNER_BINARY_LINK}\n Files: null\n FlagsUsed: []\n Name: RUN\n PrependShell: true\n - CmdLine:\n - apk update \u0026\u0026 apk add unzip \u0026\u0026 unzip -o ${SIGNER_BINARY_FILE}\n Files: null\n FlagsUsed: []\n Name: RUN\n PrependShell: true\n - CmdLine:\n - GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags=\"-w -s\" -o kyverno-notation-aws\n .\n Files: null\n FlagsUsed: []\n Name: RUN\n PrependShell: true\n Comment: \"\"\n From:\n Image: '\"golang:1.20.6-alpine3.18\"'\n Location:\n - End:\n Character: 0\n Line: 4\n Start:\n Character: 0\n Line: 4\n Name: builder\n Platform: $BUILD_PLATFORM\n SourceCode: FROM --platform=$BUILD_PLATFORM $BUILDER_IMAGE as builder\n- BaseName: gcr.io/distroless/static:nonroot\n Commands:\n - Name: WORKDIR\n Path: /\n - Env:\n - Key: PLUGINS_DIR\n Value: /plugins\n Name: ENV\n - Chmod: \"\"\n Chown: \"\"\n DestPath: plugins/com.amazonaws.signer.notation.plugin/notation-com.amazonaws.signer.notation.plugin\n From: builder\n Link: false\n Name: COPY\n SourceContents: null\n SourcePaths:\n - notation-com.amazonaws.signer.notation.plugin\n - Chmod: \"\"\n Chown: \"\"\n DestPath: kyverno-notation-aws\n From: builder\n Link: false\n Name: COPY\n SourceContents: null\n SourcePaths:\n - kyverno-notation-aws\n - CmdLine:\n - /kyverno-notation-aws\n Files: null\n Name: ENTRYPOINT\n PrependShell: false\n Comment: \"\"\n From:\n Image: gcr.io/distroless/static:nonroot\n Location:\n - End:\n Character: 0\n Line: 20\n Start:\n Character: 0\n Line: 20\n Name: \"\"\n Platform: \"\"\n SourceCode: FROM gcr.io/distroless/static:nonroot\n"
},
{
Expand Down

0 comments on commit 123686c

Please sign in to comment.