Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

chore: add writing policies docs #77

Merged
merged 1 commit into from
Oct 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
26 changes: 26 additions & 0 deletions website/docs/policies/api-version.md
Original file line number Diff line number Diff line change
@@ -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`.
53 changes: 53 additions & 0 deletions website/docs/policies/assertion-trees.md
Original file line number Diff line number Diff line change
@@ -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
```
42 changes: 42 additions & 0 deletions website/docs/policies/escaping.md
Original file line number Diff line number Diff line change
@@ -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`
86 changes: 86 additions & 0 deletions website/docs/policies/explicit-bindings.md
Original file line number Diff line number Diff line change
@@ -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.
6 changes: 5 additions & 1 deletion website/docs/policies/index.md
Original file line number Diff line number Diff line change
@@ -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.
31 changes: 31 additions & 0 deletions website/docs/policies/match.md
Original file line number Diff line number Diff line change
@@ -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.
62 changes: 62 additions & 0 deletions website/docs/policies/modifiers.md
Original file line number Diff line number Diff line change
@@ -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.
7 changes: 7 additions & 0 deletions website/mkdocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down