Preconditions

Control policy rule execution based on variables.

Preconditions allow controlling policy rule execution by building expressions 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 (\). For more detailed information on writing JMESPath expressions for use in preconditions, see the JMESPath page here.

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

You may specify multiple statements in the preconditions field and controlling how they operate by using any and all statements.

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      any:
12      - resources:
13          kinds:
14          - Deployment
15    preconditions:
16      any:
17      - key: "{{ request.object.metadata.labels.color || '' }}"
18        operator: Equals
19        value: blue
20      - key: "{{ request.object.metadata.labels.app || '' }}"
21        operator: Equals
22        value: busybox
23      all:
24      - key: "{{ request.object.metadata.labels.animal || '' }}"
25        operator: Equals
26        value: cow
27      - key: "{{ request.object.metadata.labels.env || '' }}"
28        operator: Equals
29        value: qa
30    validate:
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 for precondition evaluation:

  • Equals
  • NotEquals
  • In (deprecated)
  • AnyIn
  • AllIn
  • NotIn (deprecated)
  • AnyNotIn
  • AllNotIn
  • GreaterThan
  • GreaterThanOrEquals
  • LessThan
  • LessThanOrEquals
  • DurationGreaterThan
  • DurationGreaterThanOrEquals
  • DurationLessThan
  • DurationLessThanOrEquals

The set operators, In, AnyIn, AllIn, NotIn, AnyNotIn and AllNotIn 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”] AllIn [“str1”, “str2”, “str3”]). In this case AllIn checks if all of the strings part of the key are in the value set, AnyIn checks if any of the strings part of the key are in the value set, AllNotIn checks if all of the strings part of the key is not in the value set and AnyNotIn checks if any of the strings part of the key is not in the value set. Sets of other types are currently not supported. Old operators In and NotIn work like AllIn and AnyNotIn, these are now deprecated and will be removed in a future version.

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.

Example:

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: resource-quantities
 5spec:
 6  validationFailureAction: enforce
 7  background: false
 8  rules:
 9  - name: memory-limit
10    match:
11      any:
12      - resources:
13          kinds:
14          - Pod
15    preconditions:
16      any:
17      - key: "{{request.object.spec.containers[*].resources.requests.memory}}"
18        operator: LessThan
19        value: 1Gi

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      any:
 4      - resources:
 5          kinds:
 6          - Pods
 7    preconditions:
 8      any:
 9      - key: "{{request.object.spec.template.spec.containers.image}}"
10        operator: Equals
11        value: "bash:*"

Matching requests without a service account

In this example, the rule is only applied to requests from ServiceAccounts (i.e. when the {{serviceAccountName}} 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

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: ["build-default", "build-base"]
Last modified February 19, 2022 at 10:24 AM PST: [main] 1.6 updates (#477) (bc4c364)