From 45e29ad4caca2bd7a0eec587e1a933387a700a74 Mon Sep 17 00:00:00 2001 From: Jim Bugwadia Date: Sun, 29 Oct 2023 12:46:57 -0700 Subject: [PATCH] Docs update (#153) * docs updates Signed-off-by: Jim Bugwadia * docs updates Signed-off-by: Jim Bugwadia * docs updates Signed-off-by: Jim Bugwadia * docs updates Signed-off-by: Jim Bugwadia * update ValidationPolicy | Policy -> ValidatingPolicy Signed-off-by: Jim Bugwadia --------- Signed-off-by: Jim Bugwadia --- .vscode/launch.json | 16 +- README.md | 23 +- pkg/commands/scan/command_test.go | 13 + test/api/README.md | 2 +- test/tf-ec2/ec2.tf | 23 ++ test/tf-ec2/out.txt | 6 + test/tf-ec2/payload.json | 200 ++++++++++++++ test/tf-ec2/policy.yaml | 16 ++ test/tf-s3/bucket.tf | 23 ++ test/tf-s3/out.txt | 6 + test/tf-s3/payload.json | 151 +++++++++++ test/tf-s3/policy.yaml | 18 ++ website/docs/catalog/policies/aws/policy-1.md | 2 +- website/docs/catalog/policies/ecs/policy-1.md | 2 +- .../docs/{ => cli}/commands/kyverno-json.md | 0 .../commands/kyverno-json_completion.md | 0 .../commands/kyverno-json_completion_bash.md | 0 .../commands/kyverno-json_completion_fish.md | 0 .../kyverno-json_completion_powershell.md | 0 .../commands/kyverno-json_completion_zsh.md | 0 .../{ => cli}/commands/kyverno-json_docs.md | 0 .../{ => cli}/commands/kyverno-json_jp.md | 0 .../commands/kyverno-json_jp_function.md | 0 .../commands/kyverno-json_jp_parse.md | 0 .../commands/kyverno-json_jp_query.md | 0 .../commands/kyverno-json_playground.md | 0 .../{ => cli}/commands/kyverno-json_scan.md | 0 .../{ => cli}/commands/kyverno-json_serve.md | 0 .../commands/kyverno-json_version.md | 0 website/docs/cli/index.md | 9 + website/docs/go-library/index.md | 4 + website/docs/install.md | 14 +- website/docs/intro.md | 18 +- website/docs/policies/api-version.md | 26 -- website/docs/policies/assertion-trees.md | 53 ---- website/docs/policies/asserts.md | 248 ++++++++++++++++++ website/docs/policies/escaping.md | 42 --- website/docs/policies/explicit-bindings.md | 86 ------ website/docs/policies/index.md | 7 - website/docs/policies/match.md | 31 --- website/docs/policies/modifiers.md | 62 ----- website/docs/policies/policies.md | 95 +++++++ website/docs/quick-start.md | 114 +++++++- website/docs/static/kyverno-json-logo.pptx | Bin 293605 -> 312275 bytes website/docs/webapp/index.md | 4 + website/mkdocs.base.yaml | 2 +- website/mkdocs.yaml | 54 ++-- 47 files changed, 992 insertions(+), 378 deletions(-) create mode 100644 test/tf-ec2/ec2.tf create mode 100644 test/tf-ec2/out.txt create mode 100644 test/tf-ec2/payload.json create mode 100644 test/tf-ec2/policy.yaml create mode 100644 test/tf-s3/bucket.tf create mode 100644 test/tf-s3/out.txt create mode 100644 test/tf-s3/payload.json create mode 100644 test/tf-s3/policy.yaml rename website/docs/{ => cli}/commands/kyverno-json.md (100%) rename website/docs/{ => cli}/commands/kyverno-json_completion.md (100%) rename website/docs/{ => cli}/commands/kyverno-json_completion_bash.md (100%) rename website/docs/{ => cli}/commands/kyverno-json_completion_fish.md (100%) rename website/docs/{ => cli}/commands/kyverno-json_completion_powershell.md (100%) rename website/docs/{ => cli}/commands/kyverno-json_completion_zsh.md (100%) rename website/docs/{ => cli}/commands/kyverno-json_docs.md (100%) rename website/docs/{ => cli}/commands/kyverno-json_jp.md (100%) rename website/docs/{ => cli}/commands/kyverno-json_jp_function.md (100%) rename website/docs/{ => cli}/commands/kyverno-json_jp_parse.md (100%) rename website/docs/{ => cli}/commands/kyverno-json_jp_query.md (100%) rename website/docs/{ => cli}/commands/kyverno-json_playground.md (100%) rename website/docs/{ => cli}/commands/kyverno-json_scan.md (100%) rename website/docs/{ => cli}/commands/kyverno-json_serve.md (100%) rename website/docs/{ => cli}/commands/kyverno-json_version.md (100%) create mode 100644 website/docs/cli/index.md create mode 100644 website/docs/go-library/index.md delete mode 100644 website/docs/policies/api-version.md delete mode 100644 website/docs/policies/assertion-trees.md create mode 100644 website/docs/policies/asserts.md delete mode 100644 website/docs/policies/escaping.md delete mode 100644 website/docs/policies/explicit-bindings.md delete mode 100644 website/docs/policies/index.md delete mode 100644 website/docs/policies/match.md delete mode 100644 website/docs/policies/modifiers.md create mode 100644 website/docs/policies/policies.md create mode 100644 website/docs/webapp/index.md 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 b45e7fca90cfafd523aea0527b4fcf0a8f6c4c04..d7f2effb487bd44532485b6c42d4aac4fe26c6a5 100644 GIT binary patch delta 24561 zcmcG#by!qi^fx*)4BaV`1E`dAgLHR;goH|WmpB802!bFb(xM>UCDIboC?En-QlipQ zig5QCzrXjr@BQyS&%Hj(oX=T%@3q%n>$BJ1=j?Iqka8l1n%+Pg2T23NgAu@BFg93S zNq1p490n64t)b^YpocuW1u0aI(|(gbZK4$#PyZO#S&(STs!_`Gv?DpARHy1{Ub!gp4dJ)&mgj{`jY~n&SCVh?LT51$V zrz=_S&sq6YmTNUjX(8$Raj9_i64Dq7ksrLIROY`2dxn?CNs7^q%My&R3iC2j2A)lY zmN@Mj8gCC_;o?-IVcARmYWs!u3ax*x3l7jyuyEpSB1Wd1p9^TNAJF1O-FhlmflL1U zU4V4@2Q9ud3uZZ<33{2;SsS~~Op?Itv8V$^lYzm|^5@&6wpq)R!M7a0@IDrbbKNQy zw=;a!_d6@cm&oKVT8^B}Xqihbw!@w7vAnXpqxI9Zk;S3Hnp`n>ORW119Q>%=(z(Cls(A)eSA zCLij}9}gF~t~$zQkjBjrKR-r4kDFnR5xSM0v{@RZGkx{0YwC@p&7vrs5h~&N^rR9q zK90!}J>S&Vwl=NHjGy!D@4wNTq)kw>V_H0uwYsbFT2Gy?lu zBb5p|595%d(HtIoZx{Bykv{stFh3$XyC^z)G5J1a>Q|hfWLosFYWRoAJ%+uMnZF}+ z^~?2r8O7L3|oG5s!%6XKKK5Q)JyLDIXl&nrf*1PLL*g5%)s5c}Lu1 zio4cdfc>W;MUFhGvB^VFSIIN`XpvEzZ=Sw4=Ie^ofb6Lxp+#l8^ho13O$!c`wvnB) zmKFB|LGb(y>o16xDBY_KVqE#^k)}TP8}F&{DKizAN!=FeO~QyHgZib?_yUuuevJ2| z&HmPV$QAw$opXKB;lU043Eblq5-u{~nHH+0>8Y#Lx;wi9GmVv|vB!;P-^Qb;)xAHK zcGGi(KTzz{J8ur5n!w~eY<^D7{$3(M;Kg*!Al%`uUc~Cfq?$PH(-E>gF7AAJrI{>z znQJw4bv8!X%mH;NVSdTWp?VK1y2m)Z=D$5B`9#w*l}$!5%E7Ei@<{+Ge?k)H1rdix zS(T%%9tQ>^B~Q^Kp!>&^pXPH<=ENz&1;)`RlYB30O&V20&+N_?KEx~CB;7xwh0a#R zo~Q;Jfync)n3iCP8Qp5!xKHqXJ0A_Q7!jvYp;woxp6Di!X=RnVO+P^|OKCLJ@|FcQ z1>V0fu-it7JZhu~m@aMbdmVr9eJxSG^_zJSKV`P(h*mi|=da)|-f1!4Dh7N07Mq6Z zrY(OCyo~QTR<1t8hFP;$bNj!jnN*16O^-<}dNie(Sa5nVOpzH=RJaN$tUKBsI>vsI z=d`R!ix6Dd)HezxOdBX`seidC?_5|u#ljzep6ZxbIr54LVlchBw3crs3VXaV%2+oi z=|TQ-`F`L%bW_IdfG^+so)|DnS2DvcM&)k3Eqa2JJW!ppm10v zHf%#b*4GlGD%+cUGmnSbQ)2HKl34Bv1zIw}3I+mvE>mYFd+Y0*F|hqj z>EUnqcATct9#pWJ){JWuXY^vu^)tuBlxggBiVdxLQ8cf!vy>WjbKN6x$@j8P>bzH; z4ksR<9*xC2cU^iBuPEZ85zchg0H3rLc5o%mhr;1H0hOM`a-u=zJ5St>o`<;+QJeB= z$#2Zj)}zjH!%6oEt4lJ`rq@J?n#a_i8|Wz|j!IkiD79jG*z$U6bJ#hK z(Q}zqQ(UrFaLL@&Wms+GZPRC^W}?fRPH%rJW!kb+x<@>Ayk5zqLTaj>-FKw#5PHGn zXVpjjbVESG;)kv3#gX7`(r2VvuUCw`!sOm8y!|}8{ACfE%xJ>^R@avZ7|fj%2BQJ# z-`m??z}wfs&%x8*-rvQ`6Iup-KD*`lQjh|(Xg({0>Zs(ph*DuQo!~D^YcZOv$M^1X zF-S;oPq}vMa9bu2m1M$u2)ogHbQZi`cgJyk`cIst4Ex;yi?~+aOBPXUtz0wOak(ra zGK%RY8MGN_Uo|6t6xA((c68Qsoh5~4MmLcTpZB{}lGn~wHbHX8ZBc3e^pb__5S9kbd;~q z6;-Bfd5N|mm8PXT&U_iYN&H1Q?)rF0&)AP+Pjw9~!DLqAO?v5RV#LRN!caNpsHce! z0Sc3|5#0MqX`o(+OG65~djEC-E8K;LFTWXo^^c}|JAwnHTZk)q8I~E=?J4(!aBuC68JQybX>u{HI z_k&v9>mZ9H)rI{&KQB4|oNd(>#Q9VzE}ZjL*^+8rW_0N=?z4L@D}8MmZp9cMNU*!w zWcIx~NUZ3t>3evy#g;n9Z-Jff91gAD$fr1^y>GI4+CnjE!E(GCel?!i#b7i`hcN(tBq9NKtraH6Lb;PGb(ox>FqBN2(xW?rsFaFiYsW-@rKLJyRDUI9o9yxZh#UI{w z1Zr-}JW5|YvuCi>a9{o)G{&}l+v1s8mPEDpt%1zTNz5E;6-Zsn{GCeNJFcHR(X{CK z+P74pi*s9Pf~7{AmyfePo!q9GNZc~hFUOzrdhH@NSE2G!P2*urRu*Q)=C74&!qzK8 zlE=OF4L5D&*sRLyIA2u^U=E`VX{VOHta$Izj3$w#UES(e72r%1FUZVdnoBk$TrAiYrj_p>pPJiDu)sId*0Q=py^mitKM|$V8M!snTw4)v85T)>B66UYKq_ z;|CL)`y|QZPv}GyxC1z%UMA&7{f5U~Qp}$9Tym6aW2A=3>A!r^&9K6ZlZW~Cme{6s zCm|ijfM^=t6c!8D5exkB)W~SfIhE1o`vBVz2k|QZ@|w-%HxY&c>vb*9wBJ-czmNVl znHQ)GqX?+vNpJWz?+Y`}eH~!hDse^PriJE;?t=C;5)W1_hWcVyj9}U&%NGHH?z%}A z61&^Tv-kH`N=Jzs*8>P2?h+S9F!C4B+~*<{YF&17fBzYzUA5VDD%#u54@}_Y9p1k%V*N3)SY`4r-R!nd-jOtcX(gj z<5>OeoUlO2P&Oq&bAIzyi195x-+^`>p5tgbQ@W%_pKB7l>8MrMNgkH06m-6**}~7oX(fz%H1COh{ z3eEBoZ!nX(*)sM)@_dtB$iv!7Eyq^$yr={@?AVrTX_=kFh6ZQp3F)mqy-SapQ}a_{ zM~bV|-&M!RCGYJkjOO-O=iTPq7AZ+&{yIU?Ynaq2OIxd3{R7opqLTY#e0YyMyvRJM zacRD0Ywh552m17EGmqN;lB~|+4dYLT?yGvqDsVl*dd>cq@2ylQsG_?I+kb?(=qY`j z9H9x4?hhMgzIo$`4D&^&NXFuNB@zbn zpaxINDMH#bwH!$}=#|qaF^%_(=;$Zkes8Ae-D!G$BI{1<*tnMcBXn16@$AxI6N%at z)hL7nCmR7(c1aN4p<)tM6t}AAwPoN^?p}>%`RQnb7gN&rbdSC?opogcAAQf6hqg{e2%T?nQYoz* zPOf&pvecD{BIy;+?9&+3dkwlf$ODq!bJ8Nt8Zr*^m1}E58^Ejkr+J*k{EivuxM4$y zx>%X0o(Odg(`#&_aJ@TgMl2_&W6Hi%6++eL>w4sS9^1ve^UV_ne%C^6vO3gCgP;7G zp=r6gduJ^12G7(jJ;4N~8OyQ=Tjz_Z&ySi^(e*-CTyzsvMvSUrr99_7KeUf^43J&m zV!fK%b3s}+&Pj<2SFQVcT@sEA7F`UiW8t4ez;Ww-3yQ%>X^198AAqNiUxfd;xz0e? z27$Jr2vaQ15^Esg5iBPkAx-<0h%Z%iMD*aJ(ti2qxvj?wQ^sZMh`#n|tKA3R`A7!Y zHGJak8C4oIMy}(tr6#RQ5P2eSn=_%qJ2(SRDVD-ds*|Wtx+zeQ3OnQ;OWiel*nw zQ07)k6c0;7B298IQVUnHVIztIkD10FE5Z)flbI zR&>Kbr{B4GqzID5?KWA*-aV(ubsg8CS+k=DRflT1B?~&7M@rK|Pd8qP@9Sox zcpu}ehDd5KNxUO_l569|+lAltx9tU!k%Y^(Q|Mm_(c@;5x71UQORr}fIU0AoRc=1e zA&l7{941kJFG7vKuuVA0T*HxSC-7*#r@`owJX&^nw-71y_L<)yUD&D|wG7=+&akrm zk{Xu|YOdYkPO6%Vf_rx1XQ^M$ZH#6yL9ZL13FKXzZVhdqSzo*@w*Re^SzGD&GO&Ql zyW(?G+2iLM6IslEGaBN^sv{j(9h6ZI1ftn@pxM&Wu&2TZGVh5Mhr zes1TXrC2oc@>M<+Pmo}+cz(;mdBqbaW|GcrNKCu+@c@am%P zAK}%J+q$zp@QIG`iH3s@qRA&3$8ix&1a|t+KQ`F(*^o{;xP?Cdv;X&Sxa(ps;NoHL z^U}zbewf?k=8g|GOgkf7cX}l9Cd@7PF&V?#i-j-tqGEvo*F4QgXMy>%flk z3Uu&wboUAZja}~Eb?{`DyWwr`?~DRfbwwpnV&cl8BK+duiV_t>i3#(ILRU}#eM1HR zzWzt_Uj;O_{{OUtwESy|ErwJIKF@7nOn255ZC_fMgq9m~jB~X&W{6Z*EQSc=x z1WK@7{0{;$Y^gBl8z2#sD0B-Ds3*h^+KK=f8bEO!0Hq+I7}S9nghWx2;3^2jple|W zi9kpcB_ahi6$7^b0dZ5p+p2hRUiW`A#DJO zqJ+_qIsnB`!czY#N}_}%A(hMXl1*^d?$1oz_5000t$5Kt`$N&pf<2}xmP;QHS^FbG1T zC?QE5a1}!dN$^W510)3@5ug+$Bo34Ti3C9Z)rA}<4tYZe^Z-?ZuALNE8M94-pHv0}2EH!T%P6dESVjRP#4n`-O0h^n86aU48VCV{p`Ji@SjvI!AoBpdfEp+gfL>UWg&_pG z69ff-XjroVf_f5y!VPEz5JU@r{zV2L5GjaT*n25}un~j!gq5HH@t}}{WBBcZD1I9}zV~6)Ywg62~_y4RBhb)HN@J}&h z5PgIGpmg;-xh)CB&=QVdLQDM+;lxCLm%tmOj`P5Su_fD4q~KgaF6>OG0Q!0wpI9RX`Z5BeA*azuo~& zf%_qXfs6=-ibem?4;@#Lg)o2h`R9mVjId5M&cp2I@ja02!7~(6m59 z3ZSuwK_LYQD8qUZJ6EuA!79cQ2=cBtmTpkO0uvZZCXgDia}Z<{G00Ki3K0hzF@Ugj zKq7#631q?$l7Qj~Qj7#Y8iW*zp*WUqpb*MLKmw2y01iX!llYI-kc3}S2gDvE zK#<*(A%;K*%T|zCp~V8k9+dvaV2}!-D*t5IWQ$F@QaZwDXs{BJkP1*8OFVJV@Bd^| z&?yuuYzLB16tOA*!e(iZ{vkuatOSb#nAgexNn#Pi0IUqmPl(}?VD3Vk0ILsnS(L=` z3*4hY1a&|WXaOW0?cOfF%hw9N5L>AA;QcKL~IDnl*qNKrx7fjs#=?w9F|3gk8ZvH;^en0hT6U z{sRbVLkktek$-(aT!G}!(u-XPC7`7jyO?470LvVdCO~i4rBe)|0aht+9d;3g^3H!q z2YUr`N=o@3g8T-N1#&0I{~!>+`vB?yum3~PCIt8xDuz7zZ@vPAow-ohAqns{6n<<5 z0ApNCjnQO zFy&SlxDa}4SopzUGGx#PKHT`?0SqP{psAv05)9wUA-rcYHFGicMkFNxNvSe&g`l2` ztD?2ylc)1u#q9>yE(SJgH^V;#Q|3SLh^~kxSGUqUJFl4i*kUpeS(0xg;m#XS;uYRN z`-}+fihK3>quRKLDF`u~S9{lDlQf&R~>O9b%kM1Rv5#YG&7OGK8VY%L zq7K|g2{%A%Zwy6kO zvY3n}VimMaS35{Y(VF6n+%qFH(}@hWzJylqJoUL-aUu%P6JtCS%J{jxq(W&v=d!-m zA!fqa=XF1nZz)nHAMVJ~GQQN!4c6kzP7yc)OP2_O zm0@I^jN9!x4I!Ua+0+Ct>_Z=%2a4mZ<9%kMLEjM<4t>m+BXSr_C&(^pzL=R{(%SSf zXxl^nIm;w&z1&QIH4eOq5O`Jh%j?ayAmvqJwMVj^=(a8=kK3K}$qn(Ih=S>7%w4`p z8^#V793Q=Orul+?oO#arhlf{f;GW^Z1wc%K73ZB?JTs}K*yxwa-j^$T2Uw)g{ajbxt|(Tmh$L!R&NCo=vwg=ud1FYUwgu5;ivehhsVY&6?8(s{js* zi3`y&^rUY4-H9oOCX%;>E>_L@LW7u>Wj7>r&pP_^K7BYCKu3YpExy1LJ<7Y zhmqx@)d)TiM>w1I7Fs{DHB@!`k$3hVtn0rVPz4hVEM&jqf@@O=yX}rI-Q^cyM|9SMW zmp2ogLqYx=mBa&=#F%KZG0nYYeG;71;;aXM@{=IxPT5N{46mN3j4tKe7;*`Ami<)!LWc!MWMCu#Z zODQo|OpT{nA_QupPA7KH`kqaW(_8+Ig)yNqdA<{HE6Y-z_K`QiQNQQ#Wl${a$J7*G zac7gi5;ClO*1?%2{GFBm$!z#;d5WWDgWt8;=YoO6%Js3$&W{g1SLWp8-MisA@V)Ub z`|c&=$Ez^>Bz~cUZgt)vPN8?zjlSEnl*ZubT__DN6?fyU#&Y6-;ZK_Qo&2*|J-)}x z=v9qIzZ4IN=bzQAY~5Gj?R@-Hb2qQoqy^WK){x7p>8|ywn9h-X1=)WU-=-_qy}t%b;$%NB>dJ zQB9C&ID4U(0gng58|K1sfJS~ldh>J?{WIqWTHD_K8uCbYPB3dBZilO3vb%Vh!*0LErd&A@U1Gl_SpEC;^Mo&N z6Vc5PJQtdOW7G&JPU2xXerIFf)(>sv?{5y(j8%TkB0`RLlqvB`m9KPE37};jk@;98 z3@+M_%hBqut@AD`CuVLMAKdexvxHGOc^$SJmytN$iy{26OT|)P!CV!P5%yjE`ppln zG2j2?>}Ll^y5Jc_W2ehsQ*yTQ!12uep3J9FiR^$dS4N80rHtF{k&!`$c+3;Vs$Dl_ zq#_5u&bDNQE@*Ew1w2A)>yd@UJyAYWlHQ}BLnc$^7ZPtoZHQIs1d>m$UcvCA2oDMa z^iC5K6qc$6c+lFnT5|hI8m~x8`1%!nmoV48{4C{Np88rIw_?u^O@&9DPi3o?o4!DO z59O^7?q9qTaoZ_MYm)j~qGn&YZCO5L+sSflwXX9wz3YM7w|(@otmiT(!CgIt@olve zE>#fcf}gcT_6%Q|ADC!K**4psMYi~o=}nQ$PCK!aBXbMPdh$(HP)r1`loSM4MohZd z;%^|0DxFsiXF4*ogH3ktF~l?H6h*%*=G~|Xs?7Sb6Vh7Y$KfzcP+l;&IJ&|(VRES} zT(fibVL`>gSUb9Yq+hKIu95skUq6H5E;n%IL(waZIGFMnD;wHy+ z3f4;e`7|Suu3Rb@X*Au(%cEg5Mwd&e;Gvg$)bS7>O84`;J2T#P3V_v7&KUr!tAJwQ#Cb8N=+h!Gdwth zH2BXk1zIk-F&e`*%-l#2NuduV_{eatg5H=X?HI?6_|JccFYw{Ggu6>@M6RT1$CMbH z-D0kvw+@FKLsD-fL|EB&1ef9ZbZ!KXL0RYtY8zVa@w2oR%``E?QIJsBA? ztwnHS<(zl!Wp^cERz61p!`1SH*%70^22q+AaU?K3X6JxM$&`p1m*P~mCN5f+4 zD9Jk4hGWf4TQ%n7?v1S7eaZxuh|bw7Pf9PWq{UTt7(;e41E*;fBL7lhaK2r78VuZpIQ*25;3?{A*4KCedNbqW%rl}t`pTfseSc%q z-e(VV%CF+X7WjA7sNyKUWlE{cX>n4CE#nSdyvR7xDtnyP$$!@tGm$G_j6S=CO1P4G zMPUpt{Mw$~?%Jo)IC;;~%-;c5w+swiTAxa1yX?$UoGR%(YEvbH{V9|jX88J78AC^1 zF8pApGeT&ZX!zm-CYm2{fB9iH& z=r9j1?~Mkekn?eI1t+35q%zPXDi1I_g#HZ#!zW|f8v+-$i;O=CBAnrp1aDFD7f!`z z%t(?CvlQQ4DUp!^DrM}6U|-p#6`JMC!UxMzIHIZZT%S?JIfz@}?0gWq@Xm_@7IB@B zpeC-5;J%(r(z%GXrlq<%ap#Ml@?5W-rFqaXOG?)Xyzi%X;aubkMbe@_iMqiC;$fDs z=RQ9~(gQw+o$LzjP<{C*hx7Fs0Y>2nSx%~P*~&BSW`L&0#P6TD-#Or9oe`}?rX82Y zhw~O?Pz>yfFk~o)0`KDi>dD^QKhseon#{R+WsSP&X-FK!HTK zP*7aICi!;GebM-_?#6HQIv)PGL|*;Q$0vEOKRn6Pf5l8msQ^Q|UsaG*W{%&Jnep_y ze^T_2uaW3DR*(cSm-968mRpm?dmK20Mjp&V;Hmb6dev9lvw-W@v`5MLFss(WEiEpA zr!`&qHR3PdKSCl}w>bsjiT*zUuAqPI^KeloKYi8&IvUqnUoE> zj7GYzLq=1*1V{OG(PB5cBVYur3o~YFvN~I>hWW8h*@b^W|e#*v7u%f zdf$g))-5X9ceGZ|$0dg`AU2Zj$Hy5N)&K5m4H(bm48k1+|9T-Vj&%SEMpcpqvL4th6f$GuP^xrplky@re z9#Ai~S*dKfe<1PkF}l4Xl8pqZpx}v6YBjJnzTkV4=5H>uWeGbC!F8D2d@#5;@B464 zq0<;4`sPlUI(j%hxHKv%)=h$Ra_M$_)F~_tL_eDy)#Es!lP%Ci!~~LI{1nMx9Wt$-&^hv3axlZwCT16D|O+$`Tgwj zE2TaKbujzl3D2Zj{in9$@3_$z^5P5ro!#$h^Zc7{SoF?>Vpjy7xf!#?Vl-@5!Gw9A z_!9kxvo|ur4wcUX&-@l)!NTpBYet3v8&iciT%0Zi1yh9RG3#Slh+aV9*4+`Sy>br56qn7oYD|UCoq&xg(vt_98?nlCohNaLYe_`q~bs(aCP#V?%){ zqt&EQoP{K@k(eeV$%BKpnPuz~t)|NDlRzuNN^!^>@q&_|SrLPWM64nU{Lt+3z8q4f zAIAsb7-fk8@0XJri^TR+p& z5TebW8Ktw5n5HU8*=bAES9(nXJUE5H1K1%RZbl7Q`%W!fxqfD+gDM7watr4ndS=Vo#|hP5IEhTT^C?_({A7{JF4Ca_DS;p^@0Kn-5sg6wic87#e3=sUnt2hA6PbNYQ-x> zRtgyWGU4tWKYY6KS^ofw_W0SEk`&PL2p)-3UAL-0M{tq>OCXFVr+b(#FKe$f-tQ>v z!31+)XTqTKw1wSq;BN`N?|=H1qIF;Wwo7BNy!@;rY*0}WhEBJ?iHmqbnIF2+&u99y z_sP|z8aodrj9vNrJD&RHaB0$5d`aD>IxJv@@neZWXIN*7-`1) z;ezQ-I0BDgaEkF9m{afd&fNUy+k<&Rpk2O&f>g^htIy;JlpQ(zI~fBt7m;kM{VW<; zXnVcE2PPWFTFd!^4HpR*P(EdU@7@CC@}Ec1wZy&kKCo1EhMBm2YNSFZGon>zE&8^l zN!(Pq+Ym9Id%){)4q7nl|02tyZ&#SEJ=?md_-N~p#lNZ0cyv5EH0GkqN!AS(4vfMe zGD;)ET8IDnbT!YKBo%V9x`FgG$tr1VVJawaw#CqoVr4 zQE&`V<7tbLd#>0TSLqq+tN7i993C3cZv+TwihPOht&tCi)QfMI3FxEW*68hBmTf<| z?YN7ZN;z2d$+%EZQ6Kc!H!P`L+^xusPmyCP_Yn6~-({v(Q?%-WBJzyQ>2?K$`=#(j zoeRA2$SMINk)I}(uOi}X(~Q35=bDH&CZ9ObU#gn8^Cp*N?2wtT@oJAu;MP4(=QPH^ z@#0`ZomILuJo+^5JEDB(1J)7b1=YNC?BE0iFNqu~i3naA7U;Ebv>jFwmBtn?dr4#AP4}q6E74e@FJ~G$s`k|s!&VS3GBVDv`L--~6>m1hp58|EFr2IVJ3RgbI zzd|L8BcmvIyTuOWO?vIFGGPK3$R$ojN)@7Wx2dZaT8jl2(Rd?c!TQ%|UDh{GeSehKzF^PrBbB}J%sA-E6NUhs79y97f|lmSb2VC%`Kp&^Wcj7H zaSz|=EmR4fM|QqOMmeK@YSi0`-0mXMHw)>I_j8p@but)A)ju`iy!Rm)WA}A*xAphX zz=_UVM%m?S4LF~@r(P+B;1GYSc)}}ob4ydtHI+-&ex#e`Qu^el3r)PbiaUss$NMay z8CnkG_|nZP3f1(5zwSkHrl*d&-J2{l@z7Os#aCdw9v&mUXMLxv7fn$l3jgjHJ{-UE zCf|Qq%PlZbG;iG^C5FE1>`!soK8Z_eB)d2@Mc#OMty(Bs`0U+1iKCw}nUPub-6GJe5qYP~SJErT75diI;nn_APup3{4#4v0wE z-7hVlE$)|{Ra}4i_itv?XhKr|ntj?3x&K%k%=Fc5*S*%?Mdh!Wnclo9J!^$YFmPFv z*w$M26v8J+O{fw>V{Y!%fmyn&P!a;X&q_2g<_=SH(UP5 z$DDbvjck`3xvhDvW&b3@T-B-RUQ{Ldbg0$&{h+GEcJ@d3fS3QKp14NkwaFg|7&}HS z*YD4c1Lw%63xi;2E7l9%kY&F67*a+fP6e;AL__rN*2z2XFMq70Wj?SK*OkIkUPfiviSS3dM>JhUtmK~s%zj3d-wzqcR(({)ol-X}< zYrkl}`Y40pmsGm(?dp|RTl{dhr_bCE&g$6gW^T`C4C?t%stV1UH=&SIKzWhY4qD-|G zFObffzi)YNZ7g<(k49I!*SiPs<7zZ!!K8jZcdN>h?DTvi{NA_^*!1-qUh<1^$rld- zlAlrgI;57QR0!|un{&=C2Yh1-QNA}rWH1Q7*BmxTY zRd1Q_cf6crYC*}4Pl+OtslKq4^zz0&7>j}+O#J~i8rOng|kG2rrh?kBXeNl3jar+ig6 zb5IJyY;^~H1-r{wp@F1Q1fAsoBEda*m6{=KMXC?*l+%V@%?i0a{39R2K=gsqINROrh8aiuX49tcI-q!-C(~) zvE?ie_a^K*ZwW3YhjwDr^OW8HN=9SM-*4`3?_CulO>h>?h>kD&_=wHJz=63lUDnFX z-Y}znjmH7qtJJDIH!l>>D7%JWcT-^Fcg__Hc~t(ru=H6incYr-;FGopqY)R)cVPV} zuD}_e3DkUiXVBG<1GPx7Zkz0j)WqTW0lS`SePx{Vic43dC2A=MF-hrvsy&fNT;l%v zBN=KTJ)aJ9qOwLZe(U&wTJK|jhy1#&^Y?x%WaP}~@Z7;ht}R$OUjgn)naL<@J|HpH z+JhN0+W4pNi{As=7qxs^GBDy+JI4oum-F9lzkvrnswCN0x?Nn^_@~cvw0*%^%ptq< zh-(KH*m@bA?1A7678zo2*rc4Qwfm=%9 z%dq?AbJY*=a4ZXXs0^-|A~+2dq_3I3O?0SpkdeEO>WogFS7`T!O__0E9;?Zxa?HEu zznN6dbW%@cRv8lQxSqu~Cz6mD>`R#+ZQrk2hGy>{C^Ws8Jpr@%Id?{2KhaTMn`_h( zd^>9^!1!wA%aM{=L-Fj$3fQ6~##{IAkbn2btU)@K1X_va;_;Dt%pON!EPnPXpGIz2 zl?3p$`@q78pWR$PR$ABpb5g@IR;VYTe>6Los4KzJtqpc;a2!A4p{vvTu*0)8nDm_? zG(8`YWT(=ZL8Jl(U%*VskTG>~z>LwG`)Z`x(d&{|)jN*1^=7K)-IDSag*uqZTN^Sz z*K0;8dx^8;_C*puSvV-GayfCx2IJmuGs3cp<}G-hhCI|)r3s5I_qK@!-mU@c9SPBLW`rJf*poYFM7_8RH_dY1i=fJl$J63QwHCTR*nJxJY_rDz~YZo-mKAODYUA^ z-!Q{_Rl4`I1e$zioRH30-&}YyR<-f%d?P&AOt`|^zG;w;$=;Xr3g`$)=S$c9gI=k+ zARM>u(PXU56x9gS4&Q_!iXuw#YoE2s{d#$S-$>f)P>x8+HtoWDh;HcG8easQn>xRO z&G&$a_?0d7k~Y|d|8X6ZipCgG<)iVx#eW)nZ_S;it;{`yH-&4+2CxggPK{qqj zlMg6a)@70#pTgQ?7iXj;j2vHTn5&4Hr{y_qZfo7vP|-s8+k_Zs^|o-Y@#3Sap?5X@ z(qp9aKi`=j5*l1yOW;ElX>TUA5GUI>^-4GS`Knu|;O%Ag%lNx$78avjcYGPpE)U15<0llgzdtc<(vzU*-Sq_;F~U`*5t@B(K#`_e-z8h@RGE zur;@8Pc*gikh&d#b2>)jih*V^xL~EC-puH{N^3mcbEDl(T*yHPc?qgEoz3H zoHf5HYB!`zt1~ku#HXkz1^-+AJngfM*bk-%m=)nTj>iaH=)-Gdt)o=#vTfrPI~vt| zYzsAR8!}y$ejH>Bv;uLl*0XV$%u3Y5YIQKi5oD8NT8_$^=c5WD#f*BS&O~eRtHxy( z7g~P>p-uBr3voYwXSBKO)q80;7RaYF6rar_CVJp<&v^oWBX8PFFCxTGzWq)1J6f2% znE3tp>!+NDI~~SNW3~ZcBdTM1yJoPxNpkU$5p0Y?!8`QW)0Z2aaVaby@lhhQ48C{4{w0SGm39|3YYQixZAcyJ{L~X z{<03j*=2^bDxgFVT2h!AUEQ1;bTToP2u2Z+OEpjd40%Kiwmvtttp2q1e2uMh_Hdd; zv*-SOxFkS;eOwPkruxCsLjVQOLnAo{H_Lv_RTMpACIBOWA+{+EKEiJXYi)?h@kE7G zgcT=IL9I?6V`|Ad2k|m5`}6WGfAYo6>xg43hr=m$BQYPzIuVWMGvC%kS$3TSme_P5 zyA7UWz(Qx)`JugR%Q_Ry(xSH(kJ*NP2C^g$Ut`ALy02D{Z#@dA-7yXtSNFtG35CkQ z`!QQd81eb8>fsL2M7D}vzVg&CW&zvm8hjBHSq_}_br2d`Yy81;2t}l3{d`IX*%z$U zX-?M3ZXw+9BEt1TOTUEO2Wtfev%`exy@9qi?!e$h?-*g^!T$jOC1V69V3?xQ^3-jq zp^>@!@z<%qJ0%!=fbirK{N{x6T;Q&s%Z(+mfdklg@ZuJO;Y8j8`;ScZ1+!zHf|q|u z7^t0(qWGzBp+>QUC$sRIU$ie(IqMMw;x!-xwX(?#NTE)Xt1?MU0m|KS83f#;M2E8Vt7*EHflVN z+4yL}&_;9FG0Q;DEcN&lyn5wXJJTQMv2<1=!V}vqZ8!{g&{6r~wxnbj{OVwk&1$Bzw* z7dVmom7KObyH*CmwQ1wIA`H=i3;G4c&h^%&ow;UWKx;JOJ9MbUu@j|nX3N^-Lkww5 zy#~V{S3hdlB5C~P!`K8Q3JNaZMQZq6TW@o1r~gN_wbY0G$Ny(FR*4hU@GpIQkjD+c z4}uf~W~Oh*?6YRqaJPNwa{%HzihSA5+3?$Fty#xT_!Y%Q=om*WNo<^NSF;=cnL?*N zvbj$N{xb51Ve(?0?3y;t0ntVGW!8xmy8D15CrOy4uY|0bX~~*hsY)&s&hP38L*A&T zo@KU#-!G4NBS?ubeIi6 zWA=HweaRp$wewf`~A%@foKF0#QY2LTf*P) z*_S`Eq3z&+=)8&P-WrVhna}7bXOlt3b)9K3%XNL5S3K5MP1Vza3|}xf`8rQV6K-gS zpJyCYDMrM^ey6G)-J~cAu^u=>_v!w%*gK-7#UaLvCI0s2oKWh4&-^>%A^Wz0UVZJ2 zDf#)oEl(C3f?!#d{6pk)Yxk6%C&GQ0^I)ILN#I>n|IX^omlNv7xJQM2`w1UJe~oN& z%Q<~oNz(CfdI7G&`&8aQim8nN$c2J(8 z$nMAwmQxC+*yV#u=Y`dHB^-Pf++tR^gciB~vi}sb3b>n0QC$>XiEeBV8sM|8jes%1 z6zfMX#GYfQt6!iM6p;jDkm>^DNUL_>&4_>Xf6odhoq0S`e-B5^g15_&@;6 zj$F#>+}^|^_cuc#xS|^LWcgOtz^ag5Grvx6{OD@E=KD&|rd_bGbUGj=|N8xbrmMA{ zS=nm6E&jNbX9xJj(^59-Z8BY=wPqP1{xKcdG_Vc*lT#t>!t;|O8TsO5b?^Pnq~<)( z|9z^qv+$ML+U_o8rDq@8*{20ZkUwz~ ze#GR`C+vNvB)&+F6ufR#@LKdI;kcT|mXO~hG~Mei;Sq*|BlK^N^^(J&pBpgw-9}3) zx#3rG2KQefjMN6&EhzH0^B(TDwrJ3?Q;4a8Nyi{IKiEc=cD4nbqSMu94DZXmDlQmJAWN8${Wmc;J?xH{C5+75t0eO` z?I>bWtY?&M&%2gP@S(;9Wxg!hrr=pg)#;dU2%pm0s=@*|nm4BgpiOr|IMXK z>t)iwV^{1nM>e~B-CA5@QYG%hUHTyPbh&#;q(s%553s4>ez5T7yY!OU>*7INFfmD-bavo4_m?Sb6HR!aDV<^mT^Emjl*_oIsKS8aePI7}GUHjCSz&x#G!(8>) zo<@&T9~Rn-q2)2SM!&DBt_qY!G&EWK439!;e?iV-kC&r7?O$8);PYW;1-c;u= zgTka4kzZJbDm$JDI3fv9&?bF8hS4RD&q_1dp^R;X&j4AM4hDS`+vfF-VBY#o3bQ>P z;l-tQddzQ$)&={YW2UKO-7E;bh;~8NB|xfEu|RsqIg!SjFnB7%IpPBLA_6b|Lhq8{ z(Y|QhI#9+T)?|AUC^3N8s(?XKN-!T~k|jHbQeU`1K9sun0*(rsS7b0-7aaEey?09V zQQW$=Ez-Rq;gU9`4i3i$=o6eqWwf4^h_M_&N4n1RiVxhFIR#}0oxu;OgU4v)^^nnAf?~{b(QtzTYjDMS~sooGbDudH~nflJ(7!)HR-t za~Ps^tSNjrU;A=?O@YA`?{vv*IBnsRbx!v;PN$FV-%ohSA;HiUa)61v;96V7R(+6b z=kp+(eobJxN<#@SqeJ~UW+eM6TiApjX+PM@*9BY;i9sEy`pTySFjjdSI^` z&G^h5hP^@vVhvqNS`>GJu2aPID+k6UX2n5H_lL#z$aka{ZyiVl1^AGha$VN|O~KH} zurt+S@9r4uS^PyuxR?2m`Ph+8&Oz(D4f7Q`v1LM0nii@^6cC2Y??1R}CAc?4UYXx{ zu=6mF&of6_+;RWQvhQ|ibV$VYoTQxZbXWIgu%OGac{TiE`O?wF$gCFZur2ZESzv>J zLyE1fB<4zh@hrPaPl8*mFHL=gvkyepP2DwXpTVlHZ(&QBkdAK!A@Dh+&)cRfyvii) zL@2C}W;o|Sa;oX6K-4E}c8CDt<=*Q!)E=s5b|gG;Xd|`piWuW;;Iy0Nrhzor3mUMC z+7gjWPxtyn7uczb!L4QGMnp*NofZ7;n$cFKrIoY|c;sD!^nV7K3(ift;*HZ*Nmm zw+_cq`Z`C=H}hRan?{q1pPAWBCsbZ~ED0(JgH30|L)`tK4e!w!o8y24OXwdpSTP_W zUcOl6ow#~FgX5+aA_&??U?X|(UJ(fqJvZRBN0+#VuFHc2_r6kYt_c*z3LlWvH=7l} zXswn}qq)73%I90L6snS0U1I#f2ul^0viGb|o(+!R2xIZA z8p45l`>m)rgd86$6}0*OacbgCHOKfQhfTET#Ieit$|cxYW9-ODjWp?aD-&?oRAUS- zDCq?!GDUD1@>c0e)hwT&zu3YL@oDeSNxKib)+*CVfwv9eTO@KMv@)g~&;O#agu?}( z7`n-KtO7sMa7uU}4_v4iAlMqb7i5Fd@Asv1UvxKZ@&9In&mp=yR?%r@$JnOy&r%L6 z>BCM#xLvUo!U7tV!gd7|zE8^w!AFM6TNiyM3wDeL2b$yiLSHIYL}VKW(=)W*Q9GUE zIQ>Nan{DR13V$DxSgt-r14$2JL%%2Ecefc|Ql_Zyhwhh!RBS6V;bpQ!0%pk@XMz+z zRIK0JU+4wghOeztN)67xr8_F5Hn?=T;>Sn*5;(RdT16QA$tDGH;>k$lK!n;ZKegG= zMuF##6anTh&Aw5qN&6PB?tr@_Xshe*kM$t$Q{VnTkTG!Gx76`jm`--4kMjhZLnWsw zW5`G?Q^CC}JK~!4DL2aSrz}g6t)c7)E{x4|?~3rZoXPLsMwc!03k5nYM{i8IU6FUZ zZigk6roWOBU6thUjZvW`R|FS_2KV8+@JCRg+Q;?QR!f93k_LBM$id2uI;GjSp9Pr? zl%7`L8qge!4$x5SQm4$F-Rq1V|3n8!fMatiN~XIZ9`{wAp?H#4D3FI&x6%EBM$GGU z(K0N5pj=+co2J0MqWD;<=JhGxh0j>%$OBbBQtkkYsjk;?E?qwMm>88jupZyLV{;6d z^4oVBbJJ!ky}Lq;<6CZBHU4TPSF+a<2h;Th;qEb}oZGJ%F5kii# zNVQLdRQwz@3?{^kqtHgN!X|a*)=vpj-Fn0r(!XYtk!Tu!z;a(UgI{Uok{os|qvTv< zLi0+EgI5`4YCT0ARRJZN^2ywB?G`W^ZxWG5HceT+S{-KHWF)V6sT=jB0-*=e0!QxF}{v%km8=H~-O1iq)Hi{jVcSoE@_IvTB>qRzk*&FT_!zmu#@9?*> zTb4)h33KRF32=-s(TvA|yOG|JI{*@4?ijZ6H6qslNChNZKh?TIz=T*?m;INxzq{d-G-R)HW4I6FbPJ17=CCTW{ zkZkfu%fr6FWon$CEBIaKXZ`IDD7UGWuZ6KGM{;$mai7k8-1%X!z+z*~s}wL1vv}|x zgXlQl=?i-gYsfkI_g#(rVBcaq@2k-{Z%;8kv(?cM`k|L}`whvqt=z{jt7-}(y2C}C zwpy5@OKO2)$iAs1yHx`kbGjp+r~al_9oTZildgZ?`#3w&zGw+Js$oRh7MEXUJw0uPJ*U*G-^c?_6#RD?)(Uk3;>lVR4s%ePx8$ z>|bRMAok6@&HyD0Ykg1MC>(rTj*9{UVTAl-&5^MZMwk8k1IeH{yorKetT=M!)Q+;% z%yG22P7Lg3MTM<|neODY1l$TX)sPEpt%fUZ<@crDXx+05z4EZ4^440HRe`g9Y|;f9 z?HUuF&zbw)%JxfXR=v*sy8JvP3|1|=ck(jiC+V=V5yxX-Bbp*5@qzMNiqGlFGILKd zFu7$(um)OXztv+<$oDr7Go3}WU8B)~w`1aJ8}$9Z8bw$}2eld){QrAnVA6g+!aVy=L*59fb=@`n#D)jZBi1wzEwd4bC^K zb@pUpWpq<#ZQRZhZsa6ZV_mM+z8#!>A-?q5e1#os2dIW?M z8neZH@}*j+7J!M)oHYKbJNY*P&vs2TFlc-7T{y{IbMNM>Z$;J%*$rYXRJWgg!cyz` zr5P#>jGyuvGg*7YaRGd5>% z2BYc*V+BzM09M4#uC?;2>V4ZYDjI{RfXC)WX)RDw&w@)v>~&F2A0pn!aV2KBP?e3A zTsbO{=`+r-7!#t+eJ|efRZBcJC*2I9r10afgQfMv=v?>NCT)ekWS2?{lcVOMJJ51t zF9kooSs1;Xd`-%*Q9S9%-%(kvCpZb-+iz-io%Ti2N-6gp^|hYM@&ssZJY{=oXB2e& z;D>XM44Jt|Gx{2}bqr)KIyNJ`0d6pE!mP{poj9_s_TGWRq8*I7zOw&8MHw=oL4)_+ zvWFtin0$zH_?rg%4(8J00*!QcQHW zB*_1A`N+%7ZT+m;uCBrJwoj?Tu#B_smrT!BxAX2lrwU`hOv2jDObRj8Cf`piRldGd zDStFM)$j7Kte}lLbdpael%`$Xy(xmBpD~cT=1V4gHPiCIeYBJ&oGRr;R--e*Fq%e3 z&nq@Dcs-Vx&MU^vCgoPCt;A!c=9Jg!`n=JdrQH^%iU>G1_Y~3Ll4N{nsRMM#!0*KQt;|Kx!}=#o0^>R{5aX0l$uF$#6I{1^z>-PljJMO-iVR zZkYe;t*Vfg`5`@Vg*2_fUR7yHUaqMmAogs7YN2i zqU66;7*=EircV%s1Aya1hhVUOR2UP_gaB5sI+MdMKr9uKSQiYOfHj#OW_%9@&cfQv z$Z5hQeOjUs*~$}h0;8=c(fNC?X^$czFaQlP6{IVbv{J$(5f(ML=jEzyN$4e|ks143yLm_c6dZm{A?^To}O5aX3js zA&`1-4k7zdzmC)O#HcXP*8j{f;6;G^^);A!-bic<1K5uULxds4^biQDnM^GBF92aT AGXMYp delta 6016 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 R5C`Zv0SKhvfpI#}{{T-CVx<58 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