Skip to content

Commit

Permalink
add webapp docs and fix related issues (#163)
Browse files Browse the repository at this point in the history
* add webapp docs and fix related issues

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* update status

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* update chart meta

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* fixes

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix tests

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

---------

Signed-off-by: Jim Bugwadia <jim@nirmata.com>
Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
Co-authored-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
  • Loading branch information
JimBugwadia and eddycharly authored Oct 31, 2023
1 parent e949554 commit 005eb71
Show file tree
Hide file tree
Showing 22 changed files with 253 additions and 39 deletions.
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ body:
description: >-
What version of the Kyverno JSON are you running?
options:
- 1.0.0
- 0.1.0
validations:
required: true
- type: textarea
Expand Down
1 change: 1 addition & 0 deletions charts/kyverno-json/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ sources:
maintainers:
- name: Nirmata
url: https://kyverno.io/
email: cncf-kyverno-maintainers@lists.cncf.io
kubeVersion: ">=1.16.0-0"
2 changes: 1 addition & 1 deletion charts/kyverno-json/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ Kubernetes: `>=1.16.0-0`

| Name | Email | Url |
| ---- | ------ | --- |
| Nirmata | | <https://kyverno.io/> |
| Nirmata | <cncf-kyverno-maintainers@lists.cncf.io> | <https://kyverno.io/> |

----------------------------------------------
Autogenerated from chart metadata using [helm-docs v1.11.0](https://github.com/norwoodj/helm-docs/releases/v1.11.0)
4 changes: 2 additions & 2 deletions pkg/commands/scan/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ func Command() *cobra.Command {
RunE: command.run,
}
cmd.Flags().StringVar(&command.payload, "payload", "", "Path to payload (json or yaml file)")
cmd.Flags().StringSliceVar(&command.preprocessors, "pre-process", nil, "JmesPath expression used to pre process payload")
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")
cmd.Flags().StringVar(&command.identifier, "identifier", "", "JMESPath expression used to identify a resource")
return cmd
}
2 changes: 1 addition & 1 deletion pkg/commands/serve/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ type clusterFlags struct {
}

func (c *options) Run(_ *cobra.Command, _ []string) error {
// initialise gin framework
// initialize gin framework
gin.SetMode(c.ginFlags.mode)
tonic.SetBindHook(tonic.DefaultBindingHookMaxBodyBytes(int64(c.ginFlags.maxBodySize)))
// create router
Expand Down
4 changes: 2 additions & 2 deletions pkg/json-engine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ func New() engine.Engine[JsonEngineRequest, JsonEngineResponse] {
}
errs, err := assert.MatchAssert(ctx, nil, r.rule.Assert, r.value, r.bindings)
if err != nil {
response.Failure = err
} else if err := multierr.Combine(errs...); err != nil {
response.Error = err
} else if err := multierr.Combine(errs...); err != nil {
response.Failure = err
}
return response
}).
Expand Down
2 changes: 2 additions & 0 deletions pkg/server/linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package server
import (
"context"
"fmt"
"log"
"net/http"
"time"
)
Expand All @@ -21,5 +22,6 @@ func Run(_ context.Context, s Server, host string, port int) Shutdown {
panic(err)
}
}()
log.Default().Printf("listening to requests on %s:%d", host, port)
return srv.Shutdown
}
6 changes: 5 additions & 1 deletion pkg/server/scan/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ func newHandler(policyProvider PolicyProvider) (gin.HandlerFunc, error) {
Resources: resources,
Policies: pols,
})
return makeResponse(results...), nil
resp, _ := makeResponse(results...)
// if status != http.StatusOK {
// // TODO: handle HTTP status codes
// }
return resp, nil
}, http.StatusOK), nil
}
63 changes: 53 additions & 10 deletions pkg/server/scan/response.go
Original file line number Diff line number Diff line change
@@ -1,26 +1,69 @@
package scan

import (
"github.com/kyverno/kyverno-json/pkg/apis/v1alpha1"
"net/http"

jsonengine "github.com/kyverno/kyverno-json/pkg/json-engine"
)

type Response struct {
Results []Result `json:"results"`
}

type PolicyResult string

type Result struct {
Policy *v1alpha1.ValidatingPolicy `json:"policy"`
Rule v1alpha1.ValidatingRule `json:"rule"`
Resource interface{} `json:"resource"`
Failure error `json:"failure"`
Error error `json:"error"`
PolicyName string `json:"policy"`
RuleName string `json:"rule"`
Result PolicyResult `json:"status"`
Message string `json:"message"`
}

func makeResponse(responses ...jsonengine.JsonEngineResponse) *Response {
// Status specifies state of a policy result
const (
StatusPass PolicyResult = "pass"
StatusFail PolicyResult = "fail"
StatusWarn PolicyResult = "warn"
StatusError PolicyResult = "error"
StatusSkip PolicyResult = "skip"
)

func makeResponse(responses ...jsonengine.JsonEngineResponse) (*Response, int) {
var response Response
for _, result := range responses {
response.Results = append(response.Results, Result(result))
failCount := 0
errorCount := 0
for _, r := range responses {
status, msg := getStatusAndMessage(r)
if status == StatusError {
errorCount++
} else if status == StatusFail {
failCount++
}

response.Results = append(response.Results, Result{
PolicyName: r.Policy.Name,
RuleName: r.Rule.Name,
Result: status,
Message: msg,
})
}

httpStatus := http.StatusOK
if failCount > 0 {
httpStatus = http.StatusForbidden
} else if errorCount > 0 {
httpStatus = http.StatusNotAcceptable
}

return &response, httpStatus
}

func getStatusAndMessage(r jsonengine.JsonEngineResponse) (PolicyResult, string) {
if r.Error != nil {
return StatusError, r.Error.Error()
}
if r.Failure != nil {
return StatusFail, r.Failure.Error()
}
return &response
return StatusPass, ""
}
3 changes: 3 additions & 0 deletions pkg/server/scan/routes.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package scan

import (
"log"

"github.com/gin-gonic/gin"
)

Expand All @@ -10,5 +12,6 @@ func AddRoutes(group *gin.RouterGroup, policyProvider PolicyProvider) error {
return err
}
group.POST("/scan", handler)
log.Default().Printf("configured route %s/%s", group.BasePath(), "scan")
return nil
}
2 changes: 1 addition & 1 deletion test/commands/scan/dockerfile/out.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ Loading policies ...
Loading payload ...
Pre processing ...
Running ( evaluating 1 resource against 1 policy ) ...
- 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
- check-dockerfile / deny-external-calls / (unknown) ERROR: 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
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 @@ -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"}: all[0].check.values.tags: Invalid value: map[string]interface {}{"Environment":"Dev", "Name":"My bucket"}: Expected value: map[string]interface {}{"Team":"Kyverno"}
- required-s3-tags / require-team-tag / aws_s3_bucket.example ERROR: 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
2 changes: 1 addition & 1 deletion test/commands/scan/pod-no-latest/out.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ Loading policies ...
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]
- test / pod-no-latest / webserver ERROR: [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
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 @@ -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"}: all[0].check.values.tags: Invalid value: map[string]interface {}{"Environment":"Dev", "Name":"My bucket"}: Expected value: map[string]interface {}{"Team":"Kyverno"}
- required-s3-tags / require-team-tag / aws_s3_bucket.example ERROR: 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
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 @@ -2,5 +2,5 @@ Loading policies ...
Loading payload ...
Pre processing ...
Running ( evaluating 1 resource against 1 policy ) ...
- s3 / check-tags / (unknown) FAILED: all[0].check.planned_values.root_module.~.resources[0].values.(keys(tags_all)).(contains(@, 'Team')): Invalid value: false: Expected value: true
- s3 / check-tags / (unknown) ERROR: 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 @@ -2,5 +2,5 @@ Loading policies ...
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
- required-s3-tags / require-team-tag / bucket1 ERROR: all[0].check.tags.(wildcard('?*', Team)): Invalid value: true: Expected value: false
Done
4 changes: 2 additions & 2 deletions website/docs/cli/commands/kyverno-json_scan.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ kyverno-json scan [flags]

```
-h, --help help for scan
--identifier string JmesPath expression used to identify a resource
--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
--pre-process strings JmesPath expression used to pre process payload
--pre-process strings JMESPath expression used to pre process payload
```

### SEE ALSO
Expand Down
93 changes: 88 additions & 5 deletions website/docs/cli/index.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,92 @@
# Usage
# Overview

tbd...
The `kyverno-json` Command Line Interface (CLI) can be used to:

## Pre-processing
* scan JSON or YAML files
* launch a web application with a REST API
* launch a playground

Additionally, you can provide preprocessing queries in [jmespath](https://jmespath.site) format to pre-process the input payload before evaluating *resources* against policies.
Here is an example of scanning an Terraform plan that creates an S3 bucket:

This is necessary if the input payload is not what you want to directly analyse.
```sh
./kyverno-json scan --policy test/commands/scan/tf-s3/policy.yaml --payload test/commands/scan/tf-s3/payload.json
```

The output looks like:

```sh
Loading policies ...
Loading payload ...
Pre processing ...
Running ( evaluating 1 resource against 1 policy ) ...
- s3 / check-tags / (unknown) FAILED: all[0].check.planned_values.root_module.~.resources[0].values.(keys(tags_all)).(contains(@, 'Team')): Invalid value: false: Expected value: true
Done
```

## Installation

See [Install](../install.md) for the available options to install the CLI.

## Pre-processing payloads

You can provide preprocessing queries in [jmespath](https://jmespath.site) format to pre-process the input payload before evaluating *resources* against policies.

This is necessary if the input payload is not what you want to directly analyze.

For example, here is a partial JSON which was produced by converting a Terraform plan that creates an EC2 instance:

[kyverno/kyverno-json/main/test/commands/scan/tf-ec2/payload.json](https://github.com/kyverno/kyverno-json/blob/main/test/commands/scan/tf-ec2/payload.json)

```json
{
"format_version": "1.2",
"terraform_version": "1.5.7",
"planned_values": {
"root_module": {
"resources": [
{
"address": "aws_instance.app_server",
"mode": "managed",
"type": "aws_instance",
"name": "app_server",
"provider_name": "registry.terraform.io/hashicorp/aws",
"schema_version": 1,
"values": {
"ami": "ami-830c94e3",
"credit_specification": [],
"get_password_data": false,
"hibernation": null,
"instance_type": "t2.micro",
"launch_template": [],
"source_dest_check": true,
"tags": {
"Name": "ExampleAppServerInstance"
},
"tags_all": {
"Name": "ExampleAppServerInstance"
},
"timeouts": null,
"user_data_replace_on_change": false,
"volume_tags": null
},

...

```

To directly scan the `resources` element use `--pre-process planned_values.root_module.resources` as follows:

```sh
./kyverno-json scan --policy test/commands/scan/tf-ec2/policy.yaml --payload test/commands/scan/tf-ec2/payload.json --pre-process planned_values.root_module.resources
```

This command will produce the output:

```sh
Loading policies ...
Loading payload ...
Pre processing ...
Running ( evaluating 1 resource against 1 policy ) ...
- required-ec2-tags / require-team-tag / (unknown) PASSED
Done
```
4 changes: 2 additions & 2 deletions website/docs/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@

You can install the pre-compiled binary (in several ways), or compile from source.

## Install using `go install`
## Using `go install`

You can install with `go install` with:

```bash
go install github.com/kyverno/kyverno-json@latest
```

## Manually
## Download binary

Download the pre-compiled binaries from the [releases page](https://github.com/kyverno/kyverno-json/releases) and copy them to the desired location.

Expand Down
5 changes: 2 additions & 3 deletions website/docs/intro.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Introduction

`kyverno-json` allows any data in JSON (or YAML) format data to be validated with Kyverno policies. For example, you can now use Kyverno policies to validate:
`kyverno-json` extends Kyverno policies to perform simple and efficient validation of data in JSON or YAML format. With `kyverno-json`, you can now use Kyverno policies to validate:

- Terraform files
- Dockerfiles
- Cloud configurations
- Service authorization requests
- Authorization requests

Simply convert your runtime or configuration data to JSON, and use Kyverno to audit or enforce policies for security and best practices compliance.

Expand All @@ -14,4 +14,3 @@ Simply convert your runtime or configuration data to JSON, and use Kyverno to au
1. [A Command Line Interface (CLI)](./cli/index.md)
2. [A web application with a REST API](./webapp/index.md)
3. [A Golang library](./go-library/index.md)

4 changes: 2 additions & 2 deletions website/docs/policies/policies.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Kyverno policies are [Kubernetes resources](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) and can be easily managed via Kubernetes APIs, GitOps workflows, and other existing tools.

However, policies that apply to JSON payload have a few differences from Kyverno policies that are applied to Kubernetes resources at admission controls.
Policies that apply to JSON payload have a few differences from Kyverno policies that are applied to Kubernetes resources at admission controls.

## Resource Scope

Expand Down Expand Up @@ -65,7 +65,7 @@ A policy rule can contain `context` entries are made available to the rule via b

```yaml
apiVersion: json.kyverno.io/v1alpha1
kind: Policy
kind: ValidatingPolicy
metadata:
name: required-s3-tags
spec:
Expand Down
Loading

0 comments on commit 005eb71

Please sign in to comment.