From acf7aaf308b2af2f3cd3916e988ef330af8aabdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Charles-Edouard=20Br=C3=A9t=C3=A9ch=C3=A9?= Date: Mon, 9 Oct 2023 17:28:19 +0200 Subject: [PATCH] chore: add writing policies docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Charles-Edouard Brétéché --- README.md | 2 +- website/docs/policies/api-version.md | 26 +++++++ website/docs/policies/assertion-trees.md | 53 +++++++++++++ website/docs/policies/escaping.md | 42 +++++++++++ website/docs/policies/explicit-bindings.md | 86 ++++++++++++++++++++++ website/docs/policies/index.md | 6 +- website/docs/policies/match.md | 31 ++++++++ website/docs/policies/modifiers.md | 62 ++++++++++++++++ website/mkdocs.yaml | 7 ++ 9 files changed, 313 insertions(+), 2 deletions(-) create mode 100644 website/docs/policies/api-version.md create mode 100644 website/docs/policies/assertion-trees.md create mode 100644 website/docs/policies/escaping.md create mode 100644 website/docs/policies/explicit-bindings.md create mode 100644 website/docs/policies/match.md create mode 100644 website/docs/policies/modifiers.md diff --git a/README.md b/README.md index 85c8fc6b..6cff6f15 100644 --- a/README.md +++ b/README.md @@ -232,7 +232,7 @@ When used with a map, the named binding receives the key of the element being pr Sometimes it can be useful to refer to a parent node in the assertion tree. -This is possible to add an explicit at every node in the tree by appending the `@binding_name` to the key. +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: diff --git a/website/docs/policies/api-version.md b/website/docs/policies/api-version.md new file mode 100644 index 00000000..7d6649b8 --- /dev/null +++ b/website/docs/policies/api-version.md @@ -0,0 +1,26 @@ +# 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 new file mode 100644 index 00000000..0177e7eb --- /dev/null +++ b/website/docs/policies/assertion-trees.md @@ -0,0 +1,53 @@ +# 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/escaping.md b/website/docs/policies/escaping.md new file mode 100644 index 00000000..b8ab0387 --- /dev/null +++ b/website/docs/policies/escaping.md @@ -0,0 +1,42 @@ +# 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 new file mode 100644 index 00000000..8b30b5db --- /dev/null +++ b/website/docs/policies/explicit-bindings.md @@ -0,0 +1,86 @@ +# 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 resource, policy and rule being evaluated using the builtin `$resource`, `$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 index 142dada3..e7cb4296 100644 --- a/website/docs/policies/index.md +++ b/website/docs/policies/index.md @@ -1,3 +1,7 @@ # Writing policies -TODO +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 new file mode 100644 index 00000000..36d59f93 --- /dev/null +++ b/website/docs/policies/match.md @@ -0,0 +1,31 @@ +# 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 new file mode 100644 index 00000000..6a8a602b --- /dev/null +++ b/website/docs/policies/modifiers.md @@ -0,0 +1,62 @@ +# 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/mkdocs.yaml b/website/mkdocs.yaml index c505d771..a9c625b4 100644 --- a/website/mkdocs.yaml +++ b/website/mkdocs.yaml @@ -79,6 +79,13 @@ nav: - 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