Preconditions

Control policy rule execution based on variables.

Preconditions allow controlling policy rule execution based on variable values.

While match and exclude allow filtering requests based on resource and user information, preconditions can be used to define custom filters for more granular control of when a rule should be applied.

The primary use case for preconditions is in mutate or generate rules when needing to check and ensure a variable, typically from AdmissionReview data, is not empty. In addition to AdmissionReview variables, written as JMESPath expressions, preconditions can also be used to check against variables from ConfigMap resources. mutate rules which use patchJson6902 should use preconditions as a way to filter out results.

For validate rules, the use of patterns is often preferable since conditionals can be used.

When specifying a JMESPath expression in a preconditions statement which contains a special character (ex. / in the case of Kubernetes annotations), double quote the annotation as a literal string. Escape the double quotes with a backslash character (\).

1{{request.object.spec.template.metadata.annotations.\"foo.k8s.corp.net/bar\"}}

You may specify multiple statements in the preconditions field. The use of multiple preconditions statements function as a logical AND statement.

Any and All Statements

You may further control how preconditions are evaluated by nesting the expressions under any and/or all statements. This gives you further power in building more precise logic for how the rule is triggered. Either or both may be used simultaneously in the same rule with multiple any statements also being possible (multiple all statements would be redundant). For each any/all statement, each block must overall evaluate to TRUE for the precondition to be processed. If any of the any / all statement blocks does not evaluate to TRUE, preconditions will not be satisfied and thus the rule will not be applicable.

For example, consider a Deployment manifest which features many different labels as follows.

 1apiVersion: apps/v1
 2kind: Deployment
 3metadata:
 4  name: busybox
 5  labels:
 6    app: busybox
 7    color: red
 8    animal: cow
 9    food: pizza
10    car: jeep
11    env: qa
12spec:
13  replicas: 1
14  selector:
15    matchLabels:
16      app: busybox
17  template:
18    metadata:
19      labels:
20        app: busybox
21    spec:
22      containers:
23      - image: busybox:1.28
24        name: busybox
25        command: ["sleep", "9999"]

By using any and all blocks in the preconditions statement, it is possible to gain more granular control over when rules are evaluated. In the below sample policy, using an any block will allow the preconditions to work as a logical OR operation. This policy will only perform the validation if labels color=blue OR app=busybox are found. Because the Deployment manifest above specified color=red, using the any statement still allows the validation to occur.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: any-all-preconditions
 5spec:
 6  validationFailureAction: enforce
 7  background: false
 8  rules:
 9  - name: any-all-rule
10    match:
11      resources:
12        kinds:
13        - Deployment
14    preconditions:
15      any:
16      - key: "{{request.object.metadata.labels.color}}"
17        operator: Equals
18        value: blue
19      - key: "{{request.object.metadata.labels.app}}"
20        operator: Equals
21        value: busybox
22    validate:
23      message: "Busybox must be used based on this label combination."
24      pattern:
25        spec:
26          template:
27            spec:
28              containers:
29              - name: "*busybox*"

Adding an all block means that all of the statements within that block must evaluate to TRUE for the whole block to be considered TRUE. In this policy, in addition to the previous any conditions, it checks that all of animal=cow and env=prod but changes the validation to look for a container with name having the string foxes in it. Because the any block and all block evaluate to TRUE, the validation is performed, however the Deployment will fail to create because the name is still busybox. If one of the statements in the all block is changed so the value of the checked label is not among those in the Deployment, the rule will not be processed and the Deployment will be created.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: any-all-preconditions
 5spec:
 6  validationFailureAction: enforce
 7  background: false
 8  rules:
 9  - name: any-all-rule
10    match:
11      resources:
12        kinds:
13        - Deployment
14    preconditions:
15      any:
16      - key: "{{request.object.metadata.labels.color}}"
17        operator: Equals
18        value: blue
19      - key: "{{request.object.metadata.labels.app}}"
20        operator: Equals
21        value: busybox
22      all:
23      - key: "{{request.object.metadata.labels.animal}}"
24        operator: Equals
25        value: cow
26      - key: "{{request.object.metadata.labels.env}}"
27        operator: Equals
28        value: qa
29    validate:
30      message: "Foxes must be used based on this label combination."
31      pattern:
32        spec:
33          template:
34            spec:
35              containers:
36              - name: "*foxes*"

Operators

The following operators are currently supported for precondition evaluation:

  • Equals
  • NotEquals
  • In
  • NotIn
  • GreaterThan
  • GreaterThanOrEquals
  • LessThan
  • LessThanOrEquals
  • DurationGreaterThan
  • DurationGreaterThanOrEquals
  • DurationLessThan
  • DurationLessThanOrEquals

The set operators, In and NotIn support a set of strings as the value (e.g. In [“str1”, “str2”]). They also allow you to specify a set of strings as the key (e.g. [“str1”, “str2”] In [“str1”, “str2”, “str3”]). In this case In checks if all the strings part of the key are in the value set (i.e. key is a subset of value) and NotIn checks if any of the strings part of the key is not in the value set (i.e. key is not a subset of value). Sets of other types are currently not supported.

The duration operators can be used for things such as validating an annotation that is a duration unit. Duration operators expect numeric key or value as seconds or as a string that is a valid Go time duration, eg: “1h”. The string units supported are s (second), m (minute) and h (hour). Full details on supported duration strings are covered by time.ParseDuration.

Wildcard Matches

String values support the use of wildcards to allow for partial matches. The following example matches on pods that have a container using a bash image.

1  - name: match-on-image
2    match:
3      resources:
4        kinds:
5        - Pods
6    preconditions:
7    - key: "{{request.object.spec.template.spec.containers.image}}"
8      operator: Equals
9      value: "bash:*"

Matching requests without a service account

In this example, the rule is only applied to requests from service accounts (i.e. when the {{serviceAccountName}} is not empty).

1  - name: generate-owner-role
2    match:
3      resources:
4        kinds:
5        - Namespace
6    preconditions:
7    - key: "{{serviceAccountName}}"
8      operator: NotEquals
9      value: ""

Matching requests from specific service accounts

In this example, the rule is only applied to requests from a service account with name build-default and build-base.

1  - name: generate-default-build-role
2    match:
3      resources:
4        kinds:
5        - Namespace
6    preconditions:
7    - key: "{{serviceAccountName}}"
8      operator: In
9      value: ["build-default", "build-base"]
Last modified August 09, 2021 at 9:14 AM PST: Fix wording, make descriptions match (af3eea2)