diff --git a/.vscode/launch.json b/.vscode/launch.json index 5b5bf56a..e74ab871 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,6 +10,20 @@ "args": [ "serve" ] - } + }, + { + "name": "CLI", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}/cmd/cli", + "args": [ + "scan", + "--policy", + "/tmp/kube-policy.yaml", + "--payload", + "/tmp/pod.json" + ], + }, ] } \ No newline at end of file diff --git a/README.md b/README.md index 33b020f3..0341f44f 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Policies for this tool belong to the `json.kyverno.io` group, exist only in `v1a ```yaml apiVersion: json.kyverno.io/v1alpha1 -kind: Policy +kind: ValidatingPolicy metadata: name: test spec: @@ -53,7 +53,7 @@ This tool uses [assertion trees](#assertion-trees-replace-pattern-matching) to i ```yaml apiVersion: json.kyverno.io/v1alpha1 -kind: Policy +kind: ValidatingPolicy metadata: name: required-s3-tags spec: @@ -83,7 +83,7 @@ This implementation supports the `let` feature and this tool leverages it to imp ```yaml apiVersion: json.kyverno.io/v1alpha1 -kind: Policy +kind: ValidatingPolicy metadata: name: required-s3-tags spec: @@ -139,7 +139,7 @@ It is now possible to write a validation tree like this: ```yaml apiVersion: json.kyverno.io/v1alpha1 -kind: Policy +kind: ValidatingPolicy metadata: name: test spec: @@ -186,7 +186,7 @@ The policy below does not use the `~` modifier and `foo.bar` array is compared a ```yaml apiVersion: json.kyverno.io/v1alpha1 -kind: Policy +kind: ValidatingPolicy metadata: name: test spec: @@ -208,7 +208,7 @@ The policy below ensures that all elements in the input array are `< 5`: ```yaml apiVersion: json.kyverno.io/v1alpha1 -kind: Policy +kind: ValidatingPolicy metadata: name: test spec: @@ -246,7 +246,7 @@ The following policy will compute a sum and bind the result to the `sum` binding ```yaml apiVersion: json.kyverno.io/v1alpha1 -kind: Policy +kind: ValidatingPolicy metadata: name: test spec: @@ -270,7 +270,7 @@ As a consequence, the policy below is perfectly valid: ```yaml apiVersion: json.kyverno.io/v1alpha1 -kind: Policy +kind: ValidatingPolicy metadata: name: test spec: @@ -290,7 +290,7 @@ Note that all context entries are made available to the rule via bindings: ```yaml apiVersion: json.kyverno.io/v1alpha1 -kind: Policy +kind: ValidatingPolicy metadata: name: required-s3-tags spec: @@ -315,9 +315,10 @@ spec: Finally, we can always access the current payload, policy and rule being evaluated using the builtin `$payload`, `$policy` and `$rule` bindings. No protection is made to prevent you from overriding those bindings though. -#### Escaping projection +#### Escaping projections It can be necessary to prevent a projection under certain circumstances. + Consider the following document: ```yaml @@ -334,7 +335,7 @@ To workaround this issue, you can escape a projection by surrounding it with `\` ```yaml apiVersion: json.kyverno.io/v1alpha1 -kind: Policy +kind: ValidatingPolicy metadata: name: test spec: diff --git a/pkg/commands/scan/command_test.go b/pkg/commands/scan/command_test.go index 196370e4..02c9997b 100644 --- a/pkg/commands/scan/command_test.go +++ b/pkg/commands/scan/command_test.go @@ -79,6 +79,19 @@ func Test_Execute(t *testing.T) { policies: []string{"../../../test/dockerfile/policy.yaml"}, out: "../../../test/dockerfile/out.txt", wantErr: false, + }, { + name: "tf-s3", + payload: "../../../test/tf-s3/payload.json", + policies: []string{"../../../test/tf-s3/policy.yaml"}, + out: "../../../test/tf-s3/out.txt", + wantErr: false, + }, { + name: "tf-ec2", + payload: "../../../test/tf-ec2/payload.json", + preprocessors: []string{"planned_values.root_module.resources"}, + policies: []string{"../../../test/tf-ec2/policy.yaml"}, + out: "../../../test/tf-ec2/out.txt", + wantErr: false, }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/test/api/README.md b/test/api/README.md index 6d657300..ec9f8abb 100644 --- a/test/api/README.md +++ b/test/api/README.md @@ -17,7 +17,7 @@ make install-crds ```bash kubectl apply -f - < 4.16", + "expressions": { + "region": { + "constant_value": "us-west-2" + } + } + } + }, + "root_module": { + "resources": [ + { + "address": "aws_instance.app_server", + "mode": "managed", + "type": "aws_instance", + "name": "app_server", + "provider_config_key": "aws", + "expressions": { + "ami": { + "constant_value": "ami-830c94e3" + }, + "instance_type": { + "constant_value": "t2.micro" + }, + "tags": { + "constant_value": { + "Name": "ExampleAppServerInstance" + } + } + }, + "schema_version": 1 + } + ] + } + }, + "timestamp": "2023-10-26T06:40:48Z" +} diff --git a/test/tf-ec2/policy.yaml b/test/tf-ec2/policy.yaml new file mode 100644 index 00000000..e901ccd9 --- /dev/null +++ b/test/tf-ec2/policy.yaml @@ -0,0 +1,16 @@ +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidationPolicy +metadata: + name: required-ec2-tags +spec: + rules: + - name: require-team-tag + match: + any: + - type: aws_instance + assert: + all: + - check: + values: + tags: + (contains('@', Team)): false diff --git a/test/tf-s3/bucket.tf b/test/tf-s3/bucket.tf new file mode 100644 index 00000000..cd6a23bf --- /dev/null +++ b/test/tf-s3/bucket.tf @@ -0,0 +1,23 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 4.16" + } + } + + required_version = ">= 1.2.0" +} + +provider "aws" { + region = "us-west-2" +} + +resource "aws_s3_bucket" "example" { + bucket = "my-tf-test-bucket" + + tags = { + Name = "My bucket" + Environment = "Dev" + } +} diff --git a/test/tf-s3/out.txt b/test/tf-s3/out.txt new file mode 100644 index 00000000..de8bef93 --- /dev/null +++ b/test/tf-s3/out.txt @@ -0,0 +1,6 @@ +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 diff --git a/test/tf-s3/payload.json b/test/tf-s3/payload.json new file mode 100644 index 00000000..9d23e130 --- /dev/null +++ b/test/tf-s3/payload.json @@ -0,0 +1,151 @@ +{ + "format_version": "1.2", + "terraform_version": "1.5.7", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_s3_bucket.example", + "mode": "managed", + "type": "aws_s3_bucket", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "bucket": "my-tf-test-bucket", + "force_destroy": false, + "tags": { + "Environment": "Dev", + "Name": "My bucket" + }, + "tags_all": { + "Environment": "Dev", + "Name": "My bucket" + }, + "timeouts": null + }, + "sensitive_values": { + "cors_rule": [], + "grant": [], + "lifecycle_rule": [], + "logging": [], + "object_lock_configuration": [], + "replication_configuration": [], + "server_side_encryption_configuration": [], + "tags": {}, + "tags_all": {}, + "versioning": [], + "website": [] + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_s3_bucket.example", + "mode": "managed", + "type": "aws_s3_bucket", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "bucket": "my-tf-test-bucket", + "force_destroy": false, + "tags": { + "Environment": "Dev", + "Name": "My bucket" + }, + "tags_all": { + "Environment": "Dev", + "Name": "My bucket" + }, + "timeouts": null + }, + "after_unknown": { + "acceleration_status": true, + "acl": true, + "arn": true, + "bucket_domain_name": true, + "bucket_prefix": true, + "bucket_regional_domain_name": true, + "cors_rule": true, + "grant": true, + "hosted_zone_id": true, + "id": true, + "lifecycle_rule": true, + "logging": true, + "object_lock_configuration": true, + "object_lock_enabled": true, + "policy": true, + "region": true, + "replication_configuration": true, + "request_payer": true, + "server_side_encryption_configuration": true, + "tags": {}, + "tags_all": {}, + "versioning": true, + "website": true, + "website_domain": true, + "website_endpoint": true + }, + "before_sensitive": false, + "after_sensitive": { + "cors_rule": [], + "grant": [], + "lifecycle_rule": [], + "logging": [], + "object_lock_configuration": [], + "replication_configuration": [], + "server_side_encryption_configuration": [], + "tags": {}, + "tags_all": {}, + "versioning": [], + "website": [] + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "full_name": "registry.terraform.io/hashicorp/aws", + "version_constraint": "~> 4.16", + "expressions": { + "region": { + "constant_value": "us-west-2" + } + } + } + }, + "root_module": { + "resources": [ + { + "address": "aws_s3_bucket.example", + "mode": "managed", + "type": "aws_s3_bucket", + "name": "example", + "provider_config_key": "aws", + "expressions": { + "bucket": { + "constant_value": "my-tf-test-bucket" + }, + "tags": { + "constant_value": { + "Environment": "Dev", + "Name": "My bucket" + } + } + }, + "schema_version": 0 + } + ] + } + }, + "timestamp": "2023-10-26T08:15:54Z" +} diff --git a/test/tf-s3/policy.yaml b/test/tf-s3/policy.yaml new file mode 100644 index 00000000..7ec94886 --- /dev/null +++ b/test/tf-s3/policy.yaml @@ -0,0 +1,18 @@ +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidationPolicy +metadata: + name: s3 +spec: + rules: + - name: check-tags + assert: + all: + - check: + planned_values: + root_module: + ~.resources: + values: + (keys(tags_all)): + (contains(@, 'Environment')): true + (contains(@, 'Name')): true + (contains(@, 'Team')): true \ No newline at end of file diff --git a/website/docs/catalog/policies/aws/policy-1.md b/website/docs/catalog/policies/aws/policy-1.md index 46e479e8..b2df416d 100644 --- a/website/docs/catalog/policies/aws/policy-1.md +++ b/website/docs/catalog/policies/aws/policy-1.md @@ -30,7 +30,7 @@ curl -O https://raw.githubusercontent.com/kyverno/kyverno-json/main/catalog/aws/ ```yaml apiVersion: json.kyverno.io/v1alpha1 -kind: ValidationPolicy +kind: ValidatingPolicy metadata: annotations: description.policy.kyverno.io: Policy 1 diff --git a/website/docs/catalog/policies/ecs/policy-1.md b/website/docs/catalog/policies/ecs/policy-1.md index 3e9aa6f0..ca8e793b 100644 --- a/website/docs/catalog/policies/ecs/policy-1.md +++ b/website/docs/catalog/policies/ecs/policy-1.md @@ -26,7 +26,7 @@ curl -O https://raw.githubusercontent.com/kyverno/kyverno-json/main/catalog/ecs/ ```yaml apiVersion: json.kyverno.io/v1alpha1 -kind: ValidationPolicy +kind: ValidatingPolicy metadata: creationTimestamp: null name: test diff --git a/website/docs/commands/kyverno-json.md b/website/docs/cli/commands/kyverno-json.md similarity index 100% rename from website/docs/commands/kyverno-json.md rename to website/docs/cli/commands/kyverno-json.md diff --git a/website/docs/commands/kyverno-json_completion.md b/website/docs/cli/commands/kyverno-json_completion.md similarity index 100% rename from website/docs/commands/kyverno-json_completion.md rename to website/docs/cli/commands/kyverno-json_completion.md diff --git a/website/docs/commands/kyverno-json_completion_bash.md b/website/docs/cli/commands/kyverno-json_completion_bash.md similarity index 100% rename from website/docs/commands/kyverno-json_completion_bash.md rename to website/docs/cli/commands/kyverno-json_completion_bash.md diff --git a/website/docs/commands/kyverno-json_completion_fish.md b/website/docs/cli/commands/kyverno-json_completion_fish.md similarity index 100% rename from website/docs/commands/kyverno-json_completion_fish.md rename to website/docs/cli/commands/kyverno-json_completion_fish.md diff --git a/website/docs/commands/kyverno-json_completion_powershell.md b/website/docs/cli/commands/kyverno-json_completion_powershell.md similarity index 100% rename from website/docs/commands/kyverno-json_completion_powershell.md rename to website/docs/cli/commands/kyverno-json_completion_powershell.md diff --git a/website/docs/commands/kyverno-json_completion_zsh.md b/website/docs/cli/commands/kyverno-json_completion_zsh.md similarity index 100% rename from website/docs/commands/kyverno-json_completion_zsh.md rename to website/docs/cli/commands/kyverno-json_completion_zsh.md diff --git a/website/docs/commands/kyverno-json_docs.md b/website/docs/cli/commands/kyverno-json_docs.md similarity index 100% rename from website/docs/commands/kyverno-json_docs.md rename to website/docs/cli/commands/kyverno-json_docs.md diff --git a/website/docs/commands/kyverno-json_jp.md b/website/docs/cli/commands/kyverno-json_jp.md similarity index 100% rename from website/docs/commands/kyverno-json_jp.md rename to website/docs/cli/commands/kyverno-json_jp.md diff --git a/website/docs/commands/kyverno-json_jp_function.md b/website/docs/cli/commands/kyverno-json_jp_function.md similarity index 100% rename from website/docs/commands/kyverno-json_jp_function.md rename to website/docs/cli/commands/kyverno-json_jp_function.md diff --git a/website/docs/commands/kyverno-json_jp_parse.md b/website/docs/cli/commands/kyverno-json_jp_parse.md similarity index 100% rename from website/docs/commands/kyverno-json_jp_parse.md rename to website/docs/cli/commands/kyverno-json_jp_parse.md diff --git a/website/docs/commands/kyverno-json_jp_query.md b/website/docs/cli/commands/kyverno-json_jp_query.md similarity index 100% rename from website/docs/commands/kyverno-json_jp_query.md rename to website/docs/cli/commands/kyverno-json_jp_query.md diff --git a/website/docs/commands/kyverno-json_playground.md b/website/docs/cli/commands/kyverno-json_playground.md similarity index 100% rename from website/docs/commands/kyverno-json_playground.md rename to website/docs/cli/commands/kyverno-json_playground.md diff --git a/website/docs/commands/kyverno-json_scan.md b/website/docs/cli/commands/kyverno-json_scan.md similarity index 100% rename from website/docs/commands/kyverno-json_scan.md rename to website/docs/cli/commands/kyverno-json_scan.md diff --git a/website/docs/commands/kyverno-json_serve.md b/website/docs/cli/commands/kyverno-json_serve.md similarity index 100% rename from website/docs/commands/kyverno-json_serve.md rename to website/docs/cli/commands/kyverno-json_serve.md diff --git a/website/docs/commands/kyverno-json_version.md b/website/docs/cli/commands/kyverno-json_version.md similarity index 100% rename from website/docs/commands/kyverno-json_version.md rename to website/docs/cli/commands/kyverno-json_version.md diff --git a/website/docs/cli/index.md b/website/docs/cli/index.md new file mode 100644 index 00000000..ce137f78 --- /dev/null +++ b/website/docs/cli/index.md @@ -0,0 +1,9 @@ +# Usage + +tbd... + +## Pre-processing + +Additionally, 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 analyse. diff --git a/website/docs/go-library/index.md b/website/docs/go-library/index.md new file mode 100644 index 00000000..b878927e --- /dev/null +++ b/website/docs/go-library/index.md @@ -0,0 +1,4 @@ +# Usage + + +tbd... \ No newline at end of file diff --git a/website/docs/install.md b/website/docs/install.md index 28b06197..bfcb6f49 100644 --- a/website/docs/install.md +++ b/website/docs/install.md @@ -12,31 +12,27 @@ go install github.com/kyverno/kyverno-json@latest ## Manually -``` Download the pre-compiled binaries from the [releases page](https://github.com/kyverno/kyverno-json/releases) and copy them to the desired location. -``` -## Compile from sources +## Build from the source code -**clone:** +**clone the repository:** ```bash git clone github.com/kyverno/kyverno-json ``` -**get the dependencies:** +**build the binaries:** ```bash -go mod tidy +cd kyverno-json ``` -**build:** - ```bash make build ``` -**verify it works:** +**verify the build:** ```bash ./kyverno-json version diff --git a/website/docs/intro.md b/website/docs/intro.md index 30ddeb72..0a21d5f3 100644 --- a/website/docs/intro.md +++ b/website/docs/intro.md @@ -1,15 +1,17 @@ # Introduction -`kyverno-json` is a CLI tool very similar to the [Kyverno CLI](https://github.com/kyverno/kyverno/tree/main/cmd/cli/kubectl-kyverno). +`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: -The difference is that `kyverno-json` can apply policies to abitrary json or yaml payloads. +- Terraform files +- Dockerfiles +- Cloud configurations +- Service authorization requests -Policy definition syntax looks a lot like the [Kyverno policy](https://kyverno.io/docs/kyverno-policies/) definition syntax but is more generic and flexible. +Simply convert your runtime or configuration data to JSON, and use Kyverno to audit or enforce policies for security and best practices compliance. -This was needed to allow working with arbitrary payloads, not just [Kubernetes](https://kubernetes.io) ones. +`kyverno-json` can be run as a: -## Pre-processing +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) -Additionally, you can provide preprocessing queries in [jmespath](https://jmespath.site) format to preprocess the input payload before evaluating *resources* against policies. - -This is necessary if the input payload is not what you want to directly analyse. diff --git a/website/docs/policies/api-version.md b/website/docs/policies/api-version.md deleted file mode 100644 index 7d6649b8..00000000 --- a/website/docs/policies/api-version.md +++ /dev/null @@ -1,26 +0,0 @@ -# Api version and kind - -Both [Kyverno policies](https://kyverno.io/docs/kyverno-policies/) and `kyverno-json` policies are defined using [Kubernetes](https://kubernetes.io) manifests. - -They don't use the same `apiVersion` and `kind` though. - -[Kyverno policies](https://kyverno.io/docs/kyverno-policies/) belong to the `kyverno.io` group, exist in multiple versions (`v1`, `v2beta1`) and can be of kind `Policy` or `ClusterPolicy`. - -`kyverno-json` policies belong to the `json.kyverno.io` group, exist only in `v1alpha1` version and can only be of kind `Policy`. - -```yaml -apiVersion: json.kyverno.io/v1alpha1 -kind: Policy -metadata: - name: test -spec: - rules: - - name: foo-bar-4 - validate: - assert: - all: - - foo: - bar: 4 -``` - -The concept of clustered vs namespaced resources exist only in the [Kubernetes](https://kubernetes.io) world and it didn't make sense to reproduce the same pattern in `kyverno-json`. diff --git a/website/docs/policies/assertion-trees.md b/website/docs/policies/assertion-trees.md deleted file mode 100644 index 0177e7eb..00000000 --- a/website/docs/policies/assertion-trees.md +++ /dev/null @@ -1,53 +0,0 @@ -# Assertion trees - -[Kyverno policies](https://kyverno.io/docs/kyverno-policies/) started with a declarative approach but slowly adopted the imperative approach too, because of the limitations in the implemented declarative approach. - -`kyverno-json` tries to be as declarative as possible, for now `forEach`, pattern operators, anchors and wildcards are not supported are not supported. -Hopefully we won't need to adopt an imperative approach anymore. - -Assertion trees can be used to express complex and dynamic conditions by using [jmespath](https://jmespath.site) expressions. - -Those expressions represent projections of the being analysed *resource* and the result of this projection is passed to descendants for further analysis. - -All comparisons happen in the leaves of the assertion tree. - -**Example**: - -Given the input payload below: - -```yaml -foo: - baz: true - bar: 4 - bat: 6 -``` - -It is possible to write a validation rule like this: - -```yaml -apiVersion: json.kyverno.io/v1alpha1 -kind: Policy -metadata: - name: test -spec: - rules: - - name: foo-bar-4 - validate: - assert: - all: - - - # project field `foo` onto itself, the content of `foo` becomes the current object for descendants - foo: - - # evaluate expression `(bar > `3`)`, the result becomes the current object for descendants (in this case the result will be a simple boolean) - # then we hit the `true` leaf, comparison happens and we expect the current value to be `true` - (bar > `3`): true - - # evaluate expression `(!baz)`, the result becomes the current object for descendants (in this case the result will be a simple boolean) - # then we hit the `true` leaf, comparison happens and we expect the current value to be `false` - (!baz): false - - # evaluate expression `(bar + bat)`, the result becomes the current object for descendants (in this case the result will be a number) - # then we hit the `10` leaf, comparison happens and we expect the current value to be `10` - (bar + bat): 10 -``` diff --git a/website/docs/policies/asserts.md b/website/docs/policies/asserts.md new file mode 100644 index 00000000..70c7cd11 --- /dev/null +++ b/website/docs/policies/asserts.md @@ -0,0 +1,248 @@ +# Assertion trees + +Assertion trees can be used to apply complex and dynamic conditional checks using [JMESPath](../jp.md) expressions. + +## Assert + +An `assert` declaration contains an `any` or `all` list in which each entry contains a: + +* `check`: the assertion check +* `message`: an optional message + +A check can contain one or more JMESPath expressions. Expressions represent projections of selected data in the JSON *payload* and the result of this projection is passed to descendants for further analysis. + +All comparisons happen in the leaves of the assertion tree. + +**A simple example**: + +This policy checks that a pod does not use the default service account: + +```yaml +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidatingPolicy +metadata: + name: assert-sample +spec: + rules: + - name: foo-bar + match: + all: + - apiVersion: v1 + kind: Pod + assert: + all: + - message: "serviceAccountName 'default' is not allowed" + check: + spec: + (serviceAccountName == 'default'): false +``` + +**A detailed example**: + +Given the input payload below: + +```yaml +foo: + baz: true + bar: 4 + bat: 6 +``` + +It is possible to write a validation rule like this: + +```yaml +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidatingPolicy +metadata: + name: test +spec: + rules: + - name: foo-bar-4 + validate: + assert: + all: + - message: "..." + check: + # project field `foo` onto itself, the content of `foo` becomes the current object for descendants + foo: + + # evaluate expression `(bar > `3`)`, the boolean result becomes the current object for descendants + # the `true` leaf is compared with the current value `true` + (bar > `3`): true + + # evaluate expression `(!baz)`, the boolean result becomes the current object for descendants + # the leaf `false` is compared with the current value `false` + (!baz): false + + # evaluate expression `(bar + bat)`, the numeric result becomes the current object for descendants + # the leaf `10` is compared with the current value `10` + (bar + bat): 10 +``` + +## Iterating with Projection Modifiers + +Assertion tree expressions support modifiers to influence the way projected values are processed. + +The `~` modifier applies to arrays and maps, it mean the input array or map elements will be processed individually by descendants. + +When the `~` modifier is not used, descendants receive the whole array, not each individual element. + +Consider the following input document: + +```yaml +foo: + bar: + - 1 + - 2 + - 3 +``` + +The policy below does not use the `~` modifier and `foo.bar` array is compared against the expected array: + +```yaml +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidatingPolicy +metadata: + name: test +spec: + rules: + - name: foo-bar + validate: + assert: + all: + - foo: + # the content of the `bar` field will be compared against `[1, 2, 3]` + bar: + - 1 + - 2 + - 3 +``` + +With the `~` modifier, we can apply descendant assertions to all elements in the array individually. +The policy below ensures that all elements in the input array are `< 5`: + +```yaml +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidatingPolicy +metadata: + name: test +spec: + rules: + - name: foo-bar + validate: + assert: + all: + - foo: + # with the `~` modifier all elements in the `[1, 2, 3]` array are processed individually and passed to descendants + ~.bar: + # the expression `(@ < `5`)` is evaluated for every element and the result is expected to be `true` + (@ < `5`): true +``` + +The `~` modifier supports binding the index of the element being processed to a named binding with the following syntax `~index_name.bar`. When this is used, we can access the element index in descendants with `$index_name`. + +When used with a map, the named binding receives the key of the element being processed. + +## Explicit bindings + +Sometimes it can be useful to refer to a parent node in the assertion tree. + +This is possible to add an explicit binding at every node in the tree by appending the `->binding_name` to the key. + +Given the input document: + +```yaml +foo: + bar: 4 + bat: 6 +``` + +The following policy will compute a sum and bind the result to the `sum` binding. A descendant can then use `$sum` and use it: + +```yaml +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidatingPolicy +metadata: + name: test +spec: + rules: + - name: foo-bar + validate: + assert: + all: + - foo: + # evaluate expression `(bar + bat)` and bind it to `sum` + (bar + bat)->sum: + # get the `$sum` binding and compare it against `10` + ($sum): 10 +``` + +All binding are available to descendants, if a descendant creates a binding with a name that already exists the binding will be overridden for descendants only and it doesn't affect the bindings at upper levels in the tree. + +In other words, a node in the tree always sees bindings that are defined in the parents and if a name is reused, the first binding with the given name wins when winding up the tree. + +As a consequence, the policy below will evaluate to true: + +```yaml +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidatingPolicy +metadata: + name: test +spec: + rules: + - name: foo-bar + validate: + assert: + all: + - foo: + (bar + bat)->sum: + ($sum + $sum)->sum: + ($sum): 20 + ($sum): 10 +``` + +Finally, we can always access the current payload, policy and rule being evaluated using the builtin `$payload`, `$policy` and `$rule` bindings. No protection is made to prevent you from overriding those bindings though. + + +## Escaping projection + +It can be necessary to prevent a projection under certain circumstances. + +Consider the following document: + +```yaml +foo: + (bar): 4 + (baz): + - 1 + - 2 + - 3 +``` + +Here the `(bar)` key conflict with the projection syntax. +To workaround this situation, you can escape a projection by surrounding it with `\` characters like this: + +```yaml +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidatingPolicy +metadata: + name: test +spec: + rules: + - name: foo-bar + validate: + assert: + all: + - foo: + \(bar)\: 10 +``` + +In this case, the leading and trailing `\` characters will be erased and the projection won't be applied. + +Note that it's still possible to use the `~` modifier or to create a named binding with and escaped projection. + +Keys like this are perfectly valid: + +- `~index.\baz\` +- `\baz\@foo` +- `~index.\baz\@foo` diff --git a/website/docs/policies/escaping.md b/website/docs/policies/escaping.md deleted file mode 100644 index 867f7864..00000000 --- a/website/docs/policies/escaping.md +++ /dev/null @@ -1,42 +0,0 @@ -# Escaping projection - -It can be necessary to prevent a projection under certain circumstances. - -Consider the following document: - -```yaml -foo: - (bar): 4 - (baz): - - 1 - - 2 - - 3 -``` - -Here the `(bar)` key conflict with the projection syntax. -To workaround this situation, you can escape a projection by surrounding it with `\` characters like this: - -```yaml -apiVersion: json.kyverno.io/v1alpha1 -kind: Policy -metadata: - name: test -spec: - rules: - - name: foo-bar - validate: - assert: - all: - - foo: - \(bar)\: 10 -``` - -In this case, the leading and trailing `\` characters will be erased and the projection won't be applied. - -Note that it's still possible to use the `~` modifier or to create a named binding with and escaped projection. - -Keys like this are perfectly valid: - -- `~index.\baz\` -- `\baz\@foo` -- `~index.\baz\@foo` diff --git a/website/docs/policies/explicit-bindings.md b/website/docs/policies/explicit-bindings.md deleted file mode 100644 index 499c7996..00000000 --- a/website/docs/policies/explicit-bindings.md +++ /dev/null @@ -1,86 +0,0 @@ -# Explicit bindings - -Sometimes it can be useful to refer to a parent node in the assertion tree. - -This is possible to add an explicit binding at every node in the tree by appending the `@binding_name` to the key. - -Given the input document: - -```yaml -foo: - bar: 4 - bat: 6 -``` - -The following policy will compute a sum and bind the result to the `sum` binding. A descendant can then use `$sum` and use it: - -```yaml -apiVersion: json.kyverno.io/v1alpha1 -kind: Policy -metadata: - name: test -spec: - rules: - - name: foo-bar - validate: - assert: - all: - - foo: - # evaluate expression `(bar + bat)` and bind it to `sum` - (bar + bat)@sum: - # get the `$sum` binding and compare it against `10` - ($sum): 10 -``` - -All binding are available to descendants, if a descendant creates a binding with a name that already exists the binding will be overriden for descendants only and it doesn't affect the bindings at upper levels in the tree. - -In other words, a node in the tree always sees bindings that are definied in the parents and if a name is reused, the first binding with the given name wins when winding up the tree. - -As a consequence, the policy below is perfectly valid: - -```yaml -apiVersion: json.kyverno.io/v1alpha1 -kind: Policy -metadata: - name: test -spec: - rules: - - name: foo-bar - validate: - assert: - all: - - foo: - (bar + bat)@sum: - ($sum + $sum)@sum: - ($sum): 20 - ($sum): 10 -``` - -Note that all context entries are made available to the rule via bindings: - -```yaml -apiVersion: json.kyverno.io/v1alpha1 -kind: Policy -metadata: - name: required-s3-tags -spec: - rules: - - name: require-team-tag - match: - any: - - type: aws_s3_bucket - context: - # creates a `expectedTeam` binding automatically - - name: expectedTeam - variable: Kyverno - validate: - message: Bucket `{{ name }}` ({{ address }}) does not have the required Team tag {{ $expectedTeam }} - assert: - all: - - values: - tags: - # use the `$expectedTeam` binding coming from the context - Team: ($expectedTeam) -``` - -Finally, we can always access the current payload, policy and rule being evaluated using the builtin `$payload`, `$policy` and `$rule` bindings. No protection is made to prevent you from overriding those bindings though. diff --git a/website/docs/policies/index.md b/website/docs/policies/index.md deleted file mode 100644 index e7cb4296..00000000 --- a/website/docs/policies/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# Writing policies - -Policy definition syntax is looks a lot like the [Kyverno policy](https://kyverno.io/docs/kyverno-policies/) definition syntax but is more generic and flexible. - -This was needed to allow working with arbitrary payloads, not just [Kubernetes](https://kubernetes.io) ones. - -Those differences are detailed in the sections below. diff --git a/website/docs/policies/match.md b/website/docs/policies/match.md deleted file mode 100644 index 36d59f93..00000000 --- a/website/docs/policies/match.md +++ /dev/null @@ -1,31 +0,0 @@ -# Match and exclude - -Both [Kyverno policies](https://kyverno.io/docs/kyverno-policies/) and `kyverno-json` policies can match and exclude *resources* when being evaluated. - -[Kyverno policies](https://kyverno.io/docs/kyverno-policies/) use [Kubernetes](https://kubernetes.io) specific constructs for that matter that didn't map well with arbitrary payloads. - -`kyverno-json` uses [assertion trees](./assertion-trees.md) to implement `match` and `exclude` statements: - -```yaml -apiVersion: json.kyverno.io/v1alpha1 -kind: Policy -metadata: - name: required-s3-tags -spec: - rules: - - name: require-team-tag - match: - any: - - type: aws_s3_bucket - exclude: - any: - - name: bypass-me - validate: - assert: - all: - - values: - tags: - Team: ?* -``` - -In the example above, every *resource* having `type: aws_s3_bucket` will match, and *resources* having `name: bypass-me` will be excluded. diff --git a/website/docs/policies/modifiers.md b/website/docs/policies/modifiers.md deleted file mode 100644 index 6a8a602b..00000000 --- a/website/docs/policies/modifiers.md +++ /dev/null @@ -1,62 +0,0 @@ -# Projection modifiers - -Assertion tree expressions support modifiers to influence the way projected values are processed. - -The `~` modifier applies to arrays and maps, it mean the input array or map elements will be processed individually by descendants. -When the `~` modifier is not used, descendants receive the whole array, not individual elements. - -Consider the following input document: - -```yaml -foo: - bar: - - 1 - - 2 - - 3 -``` - -The policy below does not use the `~` modifier and `foo.bar` array is compared against the expected array: - -```yaml -apiVersion: json.kyverno.io/v1alpha1 -kind: Policy -metadata: - name: test -spec: - rules: - - name: foo-bar - validate: - assert: - all: - - foo: - # the content of the `bar` field will be compared against `[1, 2, 3]` - bar: - - 1 - - 2 - - 3 -``` - -With the `~` modifier, we can apply descendants to all elements in the array individually. -The policy below ensures that all elements in the input array are `< 5`: - -```yaml -apiVersion: json.kyverno.io/v1alpha1 -kind: Policy -metadata: - name: test -spec: - rules: - - name: foo-bar - validate: - assert: - all: - - foo: - # with the `~` modifier all elements in the `[1, 2, 3]` array are processed individually and passed to descendants - ~.bar: - # the expression `(@ < `5`)` is evaluated for every element and the result is expected to be `true` - (@ < `5`): true -``` - -The `~` modifier supports binding the index of the element being processed to a named binding with the following syntax `~index_name.bar`. When this is used, we can access the element index in descendants with `$index_name`. - -When used with a map, the named binding receives the key of the element being processed. diff --git a/website/docs/policies/policies.md b/website/docs/policies/policies.md new file mode 100644 index 00000000..8db33333 --- /dev/null +++ b/website/docs/policies/policies.md @@ -0,0 +1,95 @@ +# Policy Structure + +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. + +## Resource Scope + +Policies that apply to JSON payloads are always cluster-wide resources. + +## API Group and Kind + +`kyverno-json` policies belong to the `json.kyverno.io` group and can only be of kind `ValidatingPolicy`. + +```yaml +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidatingPolicy +metadata: + name: test +spec: + rules: + - name: foo-bar-4 + validate: + assert: + all: + - foo: + bar: 4 +``` + +## Policy Rules + +A policy can have multiple rules, and rules are processed in order. Evaluation stops at the first rule that fails. + +## Match and Exclude + +Policies that apply to JSON payloads use [assertion trees](./asserts.md) in both the `match`/`exclude` declarations as well as the `validate` rule declaration. + +```yaml +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidatingPolicy +metadata: + name: required-s3-tags +spec: + rules: + - name: require-team-tag + match: + any: + - type: aws_s3_bucket + exclude: + any: + - name: bypass-me + validate: + assert: + all: + - values: + tags: + Team: ?* +``` + +In the example above, every *resource* having `type: aws_s3_bucket` will match, and *payloads* having `name: bypass-me` will be excluded. + +## Context Entries + +A policy rule can contain `context` entries are made available to the rule via bindings: + +```yaml +apiVersion: json.kyverno.io/v1alpha1 +kind: Policy +metadata: + name: required-s3-tags +spec: + rules: + - name: require-team-tag + match: + any: + - type: aws_s3_bucket + context: + # creates a `expectedTeam` binding automatically + - name: expectedTeam + variable: Kyverno + validate: + message: Bucket `{{ name }}` does not have the required Team tag {{ $expectedTeam }} + assert: + all: + - values: + tags: + # use the `$expectedTeam` binding coming from the context + Team: ($expectedTeam) +``` + +## No `forEach`, `pattern operators`, `anchors`, or `wildcards` + +The use of [assertion trees](./asserts.md) addresses some features of Kyverno policies that apply to Kubernetes resources. + +Specifically, [forEach](https://kyverno.io/docs/writing-policies/validate/#foreach), [pattern operators](https://kyverno.io/docs/writing-policies/validate/#operators), [anchors](https://kyverno.io/docs/writing-policies/validate/#anchors), or [wildcards](https://kyverno.io/docs/writing-policies/validate/#wildcards) are not supported for policies that apply to JSON resources. Instead, [assertion trees](./asserts.md) with [JMESPath](../jp.md) expressions are used to achieve the same powerful features. \ No newline at end of file diff --git a/website/docs/quick-start.md b/website/docs/quick-start.md index 8a42c590..1d48d634 100644 --- a/website/docs/quick-start.md +++ b/website/docs/quick-start.md @@ -1,12 +1,102 @@ -# Quick start +# Quick Start -In this example we will create a YAML payload and policy and use `kyverno-json` to run analysis. +## Validate a Terraform Plan -## Create a YAML payload +In this example we will use a Kyverno policy to validate a Terraform plan: + +### Create the payload + +Here is a Terraform plan that creates an AWS S3 bucket: + +```terraform +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 4.16" + } + } + + required_version = ">= 1.2.0" +} + +provider "aws" { + region = "us-west-2" +} + +resource "aws_s3_bucket" "example" { + bucket = "my-tf-test-bucket" + + tags = { + Name = "My bucket" + Environment = "Dev" + } +} +``` + +You can convert this to JSON using the following commands: + +*output the plan:* +```sh +terraform plan -out tfplan.binary +``` +*convert to JSON:* +```sh +terraform show -json tfplan.binary | jq > payload.json +``` + +### Create the policy + +Create a `policy.yaml` file and paste the content below that checks for required labels: + + +```yaml +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidatingPolicy +metadata: + name: s3 +spec: + rules: + - name: check-tags + assert: + all: + - check: + planned_values: + root_module: + ~.resources: + values: + (keys(tags_all)): + (contains(@, 'Environment')): true + (contains(@, 'Name')): true + (contains(@, 'Team')): true +``` + +### Scan the payload + +With the payload and policy above, we can invoke `kyverno-json` with the command below: + +```bash +kyverno-json scan --payload payload.json --policy policy.yaml +``` + +The plan shown above will fail as it does not contain the `Team` tag. + +```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 +``` + +## Validate a Kubernetes Resource For this example we will use a [Kubernetes](https://kubernetes.io) `Pod` payload. -Create a `payload.yaml` file and paste the content below: +### Create the payload + +Create a `payload.yaml` file and paste the Pod declaration below in it: ```yaml apiVersion: v1 @@ -24,17 +114,15 @@ spec: This is a simple `Pod` with one container running the `busybox` latest docker image. -Now, using the `latest` tag of an image is considered a bad practice. - -In the next part of this example we will write a policy to detect such cases. +Using the `latest` tag of an image is a bad practice. Let's write a policy to detect this. -## Create a policy to block `latest` images +### Create the policy -Create a `policy.yaml` file and paste the content below: +Create a `policy.yaml` file and paste the content below to block `latest` images: ```yaml apiVersion: json.kyverno.io/v1alpha1 -kind: Policy +kind: ValidatingPolicy metadata: name: pod-policy spec: @@ -62,13 +150,17 @@ spec: This policy iterates over pod containers, checking that the container image has a tag specified and that the tag being used is not `latest`. -## Run `kyverno-json` scan +### Scan the payload With the payload and policy above, we can invoke `kyverno-json` with the command below: ```bash kyverno-json scan --payload payload.yaml --policy policy.yaml +``` +This produces the output: + +```bash Loading policies ... Loading payload ... Pre processing ... diff --git a/website/docs/static/kyverno-json-logo.pptx b/website/docs/static/kyverno-json-logo.pptx index b45e7fca..d7f2effb 100644 Binary files a/website/docs/static/kyverno-json-logo.pptx and b/website/docs/static/kyverno-json-logo.pptx differ diff --git a/website/docs/webapp/index.md b/website/docs/webapp/index.md new file mode 100644 index 00000000..b878927e --- /dev/null +++ b/website/docs/webapp/index.md @@ -0,0 +1,4 @@ +# Usage + + +tbd... \ No newline at end of file diff --git a/website/mkdocs.base.yaml b/website/mkdocs.base.yaml index 360ee365..7edb1de1 100644 --- a/website/mkdocs.base.yaml +++ b/website/mkdocs.base.yaml @@ -1,7 +1,7 @@ # site site_name: kyverno-json site_url: https://github.io/kyverno/kyverno-json -site_description: Generic policy gouvernance for arbitrary JSON payloads +site_description: Kyverno policies for arbitrary JSON and YAML payloads # repo repo_name: kyverno/kyverno-json diff --git a/website/mkdocs.yaml b/website/mkdocs.yaml index 4844d196..974506e9 100644 --- a/website/mkdocs.yaml +++ b/website/mkdocs.yaml @@ -2,41 +2,41 @@ INHERIT: ./mkdocs.base.yaml nav: - Home: index.md -- Getting Started: +- Documentation: - intro.md - install.md - quick-start.md -- Writing policies: - - policies/index.md - - Basics: - - policies/api-version.md - - policies/match.md - - policies/assertion-trees.md - - policies/modifiers.md - - policies/explicit-bindings.md - - policies/escaping.md - - Command Line Usage: - - commands/kyverno-json.md - - commands/kyverno-json_completion.md - - commands/kyverno-json_completion_bash.md - - commands/kyverno-json_completion_fish.md - - commands/kyverno-json_completion_powershell.md - - commands/kyverno-json_completion_zsh.md - - commands/kyverno-json_docs.md - - commands/kyverno-json_jp.md - - commands/kyverno-json_jp_function.md - - commands/kyverno-json_jp_parse.md - - commands/kyverno-json_jp_query.md - - commands/kyverno-json_playground.md - - commands/kyverno-json_scan.md - - commands/kyverno-json_serve.md - - commands/kyverno-json_version.md + - Writing policies: + - policies/policies.md + - policies/asserts.md + - Command Line: + - cli/index.md + - Command Reference: + - cli/commands/kyverno-json.md + - cli/commands/kyverno-json_completion.md + - cli/commands/kyverno-json_completion_bash.md + - cli/commands/kyverno-json_completion_fish.md + - cli/commands/kyverno-json_completion_powershell.md + - cli/commands/kyverno-json_completion_zsh.md + - cli/commands/kyverno-json_docs.md + - cli/commands/kyverno-json_jp.md + - cli/commands/kyverno-json_jp_function.md + - cli/commands/kyverno-json_jp_parse.md + - cli/commands/kyverno-json_jp_query.md + - cli/commands/kyverno-json_playground.md + - cli/commands/kyverno-json_scan.md + - cli/commands/kyverno-json_serve.md + - cli/commands/kyverno-json_version.md + - Web Application: + - webapp/index.md + - Golang Library: + - go-library/index.md - JMESPath: - Overview: jp.md - Functions: jp/functions.md - APIs: - v1alpha1: apis/kyverno-json.v1alpha1.md -- Policy catalog: +- Policies: - catalog/index.md - All: - catalog/policies/aws/policy-1.md