Preconditions
Preconditions allow for more fine-grained selection of resources than the options allowed by match
and exclude
statements. Preconditions consist of one or more expressions which are evaluated after a resource has been successfully matched (and not excluded) by a rule. They are powerful in that they allow you access to variables, JMESPath filters, operators, and other constructs which can be used to precisely select when a rule should be evaluated. When preconditions are evaluated to an overall TRUE result, processing of the rule body begins.
For example, if you wished to apply policy to all Kubernetes Services which were of type NodePort
, since neither the match
/exclude
blocks provide access to fields within a resource’s spec
, a precondition could be used. In the below rule, while all Services are initially selected by Kyverno, only the ones which have the field spec.type
set to NodePort
will go on to be processed to ensure the field spec.externalTrafficPolicy
equals a value of Local
.
1rules:
2- name: validate-nodeport-trafficpolicy
3 match:
4 any:
5 - resources:
6 kinds:
7 - Service
8 preconditions:
9 all:
10 - key: "{{ request.object.spec.type }}"
11 operator: Equals
12 value: NodePort
13 validate:
14 message: "All NodePort Services must use an externalTrafficPolicy of Local."
15 pattern:
16 spec:
17 externalTrafficPolicy: Local
While, in the above snippet, a precondition is used, it would have been possible to also express this desire using multiple types of anchors instead. It is more common for preconditions to exist when needing to perform more advanced comparisons between context data (e.g., results from a stored ConfigMap, Kubernetes API call, service call, etc.) and admission data. In the below snippet, a precondition is used to measure the length of an array of volumes coming from a Pod which are of type hostPath. Preconditions can use context variables, the JMESPath system, and perform comparisons between the two and more.
1rules:
2- name: check-hostpaths
3 match:
4 any:
5 - resources:
6 kinds:
7 - Pod
8 context:
9 - name: hostpathvolnames
10 variable:
11 jmesPath: request.object.spec.volumes[?hostPath].name
12 default: []
13 preconditions:
14 all:
15 - key: "{{ length(hostpathvolnames) }}"
16 operator: GreaterThan
17 value: 0
Preconditions are similar in nature to deny rules in that they are built of the same type of expressions and have the same fields. Also like deny rules, preconditions use short circuiting to stop or continue processing depending on whether they occur in an any
or all
block.
Because preconditions very commonly use variables in JMESPath format (e.g., {{ request.object.spec.type }}
), there are some special considerations when it comes to their formatting. See the JMESPath formatting page for further details.
When preconditions are used in the rule types which support reporting, a result will be scored as a skip
if a resource is matched by a rule but discarded by the combined preconditions. Note that this result differs from if it applies to an exclude
block where the resource is immediately ignored.
Preconditions are also used in mutate rules inside a foreach
loop for more granular selection of array entries to be mutated. See the documentation here for more details.
Any and All Statements
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. 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 background: false
7 rules:
8 - name: any-all-rule
9 match:
10 any:
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 failureAction: Enforce
24 message: "Busybox must be used based on this label combination."
25 pattern:
26 spec:
27 template:
28 spec:
29 containers:
30 - name: "*busybox*"
Note
Since preconditions often consider fields in Kubernetes resources which are optional, it is often necessary to use a JMESPath syntax for non-existence checks (|| ''
). See the JMESPath page here for more details on why these are necessary and how to use them.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=qa
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 background: false
7 rules:
8 - name: any-all-rule
9 match:
10 any:
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 failureAction: Enforce
31 message: "Foxes must be used based on this label combination."
32 pattern:
33 spec:
34 template:
35 spec:
36 containers:
37 - name: "*foxes*"
Operators
The following operators are currently supported in conditional expressions wherever expressions are used:
- Equals
- NotEquals
- AnyIn
- AllIn
- AnyNotIn
- AllNotIn
- GreaterThan
- GreaterThanOrEquals
- LessThan
- LessThanOrEquals
- DurationGreaterThan
- DurationGreaterThanOrEquals
- DurationLessThan
- DurationLessThanOrEquals
Operators Equals
and NotEquals
are only applicable when comparing a single key to a single value and not an array. Set operators should be used instead in those cases.
The set operators, AnyIn
, AllIn
, AnyNotIn
and AllNotIn
, are the most commonly-used and most flexible operators which support one-to-one, one-to-many, many-to-one, and many-to-many comparisons. They support string, integer, and boolean types.
AnyIn
: checks that ANY of the keys are found in the values.AllIn
: checks that ALL of the keys are found in the values.AnyNotIn
: checks that ANY of the keys are NOT found in the values.AllNotIn
: checks that ALL of the keys are NOT found in the values.
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.
The GreaterThan
, GreaterThanOrEquals
, LessThan
and LessThanOrEquals
operators can also be used with Kubernetes resource quantities. Any value handled by resource.ParseQuantity can be used, this includes comparing values that have different scales. Note that these operators can only operate on a single value currently and not an array of values, even if the array contains a single string.
Example:
1apiVersion: kyverno.io/v1
2kind: ClusterPolicy
3metadata:
4 name: resource-quantities
5spec:
6 background: false
7 rules:
8 - name: memory-limit
9 match:
10 any:
11 - resources:
12 kinds:
13 - Pod
14 preconditions:
15 any:
16 - key: "{{request.object.spec.containers[0].resources.requests.memory}}"
17 operator: LessThan
18 value: 1Gi
Wildcard Matches
String values support the use of wildcards to allow for partial matches. The following example matches on Ingress resources where the first rule does not have a host
which ends in .mycompany.com
.
1- name: mutate-rules-host
2 match:
3 resources:
4 kinds:
5 - Ingress
6 preconditions:
7 all:
8 - key: "{{request.object.spec.rules[0].host}}"
9 operator: NotEquals
10 value: "*.mycompany.com"
Matching requests without a service account
Preconditions have access to predefined variables from Kyverno further extending their power.
In this example, the rule is only applied to requests from ServiceAccounts (i.e. when the {{serviceAccountName}}
variable is not empty).
1 - name: generate-owner-role
2 match:
3 any:
4 - resources:
5 kinds:
6 - Namespace
7 preconditions:
8 any:
9 - key: "{{serviceAccountName}}"
10 operator: NotEquals
11 value: ""
Matching requests from specific service accounts
Preconditions support providing key
and value
fields as lists as well as simple strings.
In this example, the rule is only applied to requests from a ServiceAccount with name build-default
and build-base
.
1 - name: generate-default-build-role
2 match:
3 any:
4 - resources:
5 kinds:
6 - Namespace
7 preconditions:
8 any:
9 - key: "{{serviceAccountName}}"
10 operator: AnyIn
11 value:
12 - build-default
13 - build-base
Adding custom messages
Although preconditions do not produce a blocking effect similar to deny rules, they are capable of showing a custom message when an expressions fails. The message will be shown in the Kyverno log. The rule snippet below will print the message specified in the logs for the expression which evaluates to FALSE keeping in mind short circuiting.
1- name: message-rule
2 match:
3 any:
4 - resources:
5 kinds:
6 - ConfigMap
7 preconditions:
8 all:
9 - key: "{{ request.object.data.food }}"
10 operator: Equals
11 value: cheese
12 message: My favorite food is cheese.
13 - key: "{{ request.object.data.day }}"
14 operator: Equals
15 value: monday
16 message: You have a case of the Mondays.
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.