Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add webapp docs and fix related issues #163

Merged
merged 6 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading