JMESPath

The JSON query language behind Kyverno.

JMESPath (pronounced “James path”) is a JSON query language created by James Saryerwinnie and is the language that Kyverno supports to perform more complex selections of fields and values and also manipulation thereof by using one or more filters. If you’re familiar with kubectl and Kubernetes already, this might ring a bell in that it’s similar to JSONPath. JMESPath can be used almost anywhere in Kyverno although is an optional component depending on the type and complexity of a Kyverno policy or rule that is being written. While many policies can be written with simple overlay patterns, others require more detailed selection and transformation. The latter is where JMESPath is useful.

While the complete specifications of JMESPath can be read on the official site’s specifications page, much of the specifics may not apply to Kubernetes use cases and further can be rather thick reading. This page serves as an easier guide and tutorial on how to learn and harness JMESPath for Kubernetes resources for use in crafting Kyverno policies. It should not be a replacement for the official JMESPath documentation but simply a use case specific guide to augment the already comprehensive literature.

Getting Set Up

In order to position yourself for success with JMESPath expressions inside Kyverno policies, a few tools are recommended.

  1. kubectl, the Kubernetes CLI here. While having kubectl is a given, it comes in handy especially when building a JMESPath expression around performing API lookups.

  2. kyverno, the Kyverno CLI here or via krew. Kyverno acts as a webhook (when run in-cluster) but also as a standalone CLI when run outside giving you the ability to test policies and, more recently, to test custom JMESPath filters which are endemic to only Kyverno. With the jp subcommand, it contains the functionality present in the upstream jp CLI tool and also newer capabilities. It effectively allows you to test out JMESPath expressions live in a command line interface by passing in a JSON document and seeing the results without having to repeatedly test Kyverno policies.

  3. yq, the YAML processor here. yq allows reading from a Kubernetes manifest and converting to JSON, which is helpful in order to be piped to jp in order to test expressions. As of Kyverno 1.7.0, the Kyverno CLI jp subcommand’s -f flag also accepts YAML files in addition to JSON.

  4. jq, the JSON processor here. jq is an extremely popular tool for working with JSON documents and has its own filter ability, but it’s also useful in order to format JSON on the terminal for better visuals.

Basics

JMESPath is used when you need fine-grained selection of a document and need to perform some type of query logic against the result. For example, if in a given field you need to refer to the value of another field either in the same resource or in a different one, you’ll need to use JMESPath. This sample policy performs a simple mutation on a Pod to add a new label named appns and set the value of it based on the value of the Namespace in which that Pod is created.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: add-labels
 5spec:
 6  rules:
 7  - name: add-labels
 8    match:
 9      any:
10      - resources:
11          kinds:
12          - Pod
13    mutate:
14      patchStrategicMerge:
15        metadata:
16          labels:
17            appns: "{{request.namespace}}"

JMESPath expressions in most places in Kyverno must be enclosed in double curly braces like {{request.namespace}}. If an expression is used as the value of a field and contains nothing else, the expression needs to be wrapped in quotes: appns: "{{request.namespace}}". If the value field contains other text outside of the expression, then it can be unquoted and treated as a string but this isn’t strictly required: message: The namespace name is {{request.namespace}}.

When building a JMESPath expression, a dot (.) character is called a “sub-expression” and used to descend into nested structures. In the {{request.namespace}} example, this expression is looking for the top-most object the key of which is called request and then looking for a child object the key of which is called namespace. Whatever the value of the namespace key is will be inserted where the expression is written. Given the below AdmissionReview snippet, which will be explained in a moment, the value that would result from the {{request.namespace}} expression is foo.

1{
2    "apiVersion": "admission.k8s.io/v1",
3    "kind": "AdmissionReview",
4    "request": {
5        "namespace": "foo"
6    }
7}

When submitting a Pod, which matches the policy above, the result which gets created after Kyverno has mutated it would then look something like this.

Incoming Pod

1apiVersion: v1
2kind: Pod
3metadata:
4  name: mypod
5spec:
6  containers:
7  - name: busybox
8    image: busybox

Outgoing Pod

 1apiVersion: v1
 2kind: Pod
 3metadata:
 4  name: mypod
 5  labels:
 6    appns: foo
 7spec:
 8  containers:
 9  - name: busybox
10    image: busybox

Notice in the highlighted lines that the new label appns has been added to the Pod and the value set equal to the expression {{request.namespace}} which, in this instance, happened to be foo because it was created in the foo Namespace. Should this Pod be created in another Namespace called bar, as you might guess, the label would be appns: bar. And this is a nice segue into AdmissionReview resources.

AdmissionReview

Kyverno is an example, although there are many others, of an admission controller. As the name implies, these are pieces of software which have some stake in whether a given resource is admitted into the cluster or not. They may be either validating, mutating, or both. The latter applies to Kyverno as it has both capabilities. For a graphical representation of the order in which these requests make their way into Kyverno, see the introduction page.

When a resource that matches the criteria of a selection statement gets sent to the Kubernetes API server, after the API server performs some basic modifications to it, it then gets sent to webhooks which have told the API server via a MutatingWebhookConfiguration or ValidatingWebhookConfiguration resource–which Kyverno creates for you based upon the policies you write–that it wishes to be informed. The API server will “wrap” the matching resource in another resource called an AdmissionReview which contains a bunch of other descriptive data about that request, for example what type or request this is (like a creation or deletion), the user who submitted the request, and, most importantly, the contents of the resource itself. Given the simple Pod example above that a user wishes to be created in the foo Namespace, the AdmissionReview request that hits Kyverno might look like following.

 1{
 2    "kind": "AdmissionReview",
 3    "apiVersion": "admission.k8s.io/v1",
 4    "request": {
 5        "uid": "3d4fc6c1-7906-47d9-b7da-fc2b22353643",
 6        "kind": {
 7            "group": "",
 8            "version": "v1",
 9            "kind": "Pod"
10        },
11        "resource": {
12            "group": "",
13            "version": "v1",
14            "resource": "pods"
15        },
16        "requestKind": {
17            "group": "",
18            "version": "v1",
19            "kind": "Pod"
20        },
21        "requestResource": {
22            "group": "",
23            "version": "v1",
24            "resource": "pods"
25        },
26        "name": "mypod",
27        "namespace": "foo",
28        "operation": "CREATE",
29        "userInfo": {
30            "username": "thomas",
31            "uid": "404d34c4-47ff-4d40-b25b-4ec4197cdf63"
32        },
33        "object": {
34            "kind": "Pod",
35            "apiVersion": "v1",
36            "metadata": {
37                "name": "mypod",
38                "creationTimestamp": null
39            },
40            "spec": {
41                "containers": [
42                    {
43                        "name": "busybox",
44                        "image": "busybox",
45                        "resources": {}
46                    }
47                ]
48            },
49            "status": {}
50        },
51        "oldObject": null,
52        "dryRun": false,
53        "options": {
54            "kind": "CreateOptions",
55            "apiVersion": "meta.k8s.io/v1"
56        }
57    }
58}

As can be seen, the full Pod is represented along with other metadata surrounding its creation.

These AdmissionReview resources serve as the most common source of data when building JMESPath expressions, specifically request.object. For the other data properties which can be consumed via an AdmissionReview resource, refer back to the variables page.

Formatting

Because there are various types of values in differing fields, there are differing ways values must be supplied to JMESPath expressions as inputs in order to generate not only a valid expression but produce the output desired. Specifying values in the correct format is key to this success. Values which are supported but need to be differentiated in formatting are numbers (i.e., an integer like 6 or a floating point like 6.7), a quantity (i.e., a number with a unit of measure like 6Mi), a duration (i.e., a number with a unit of time like 6h), a semver (i.e., a version number like 1.2.3), and others. Because Kyverno (and therefore most custom JMESPath filters built for Kyverno) is designed for Kubernetes, it is Kubernetes aware. Therefore, specifying `6` as an input to a filter is not the same as specifying '6' where the former is interpreted as “the number six” and latter as “six bytes”. The types which map to the possible values are either JSON or string. In JMESPath, these are literal expression and raw string literals. Use the table below to find how to format the type of value which should be supplied.

Value TypeInput TypeJMESPath TypeFormatting
NumberIntegerLiteralbackticks
QuantityStringRawquotes
DurationStringRawquotes
Labels (map)ObjectLiteralbackticks

Paths in a JMESPath expression may also need escaping or literal quoting depending on the contents. For example, in a ResourceQuota the following schema elements may be present:

1spec:
2  hard:
3    limits.memory: 3750Mi
4    requests.cpu: "5"

To represent the limits.memory field in a JMESPath expression requires literal quoting of the key in order to avoid being interpreted as child nodes limits and memory. The expression would then be {{ spec.hard.\"limits.memory\" }}. A similar approach is needed when individual keys contain special characters, for example a dash (-). Quoting and then escaping is similarly needed there, ex., {{ images.containers.\"my-container\".tag }}.

Quoting of an overall JMESPath expression can also impact how it is evaluated. For fields which only contain a JMESPath expression (ex., key: "{{ request.object.spec.template.spec.containers[].image | contains(@, 'nginx') }}") it is important to use double quotes on the outer expression (as shown) and single quotes for input fields of type string. Even if no JMESPath filters are used, any expression should be wrapped in double quotes to avoid unintended evaluation.

Useful Patterns

When developing policies for Kubernetes resources, there are several patterns which are common where JMESPath can be useful. This section attempts to capture example patterns that have been observed through real world use cases and how to write JMESPath for them.

Flattening Arrays

In many Kubernetes resources, arrays of both objects and strings are very common. For example, in Pod resources, spec.containers[] is an array of objects where each object in the array may optionally specify args[] which is an array of strings. Policy very often must be able to peer into these arrays and match a given pattern with enough flexibility to implement a sufficiently advanced level of control. The JMESPath flatten operator can help to simplify these checks so writing Kyverno policy becomes less verbose and require fewer rules.

Pods may contain multiple containers and in different locations in the Pod spec tree, for example ephemeralContainers[], initContainers[], and containers[]. Regardless of where the container occurs, a container is still a container. And although it’s possible to name each location in the spec individually, this produces rule or expression sprawl. It is often more efficient to collect all the containers together in a single query for processing. Consider the example Pod below.

 1apiVersion: v1
 2kind: Pod
 3metadata:
 4  name: mypod
 5spec:
 6  initContainers:
 7  - name: redis
 8    image: redis
 9  containers:
10  - name: busybox
11    image: busybox
12  - name: nginx
13    image: nginx

Assume this Pod is saved as pod.yaml locally, its containers[] may be queried using a simple JMESPath expression after using yq to output it as a JSON document then piped into the Kyverno CLI.

 1$ yq e pod.yaml -o json | kyverno jp "spec.containers[]"
 2[
 3  {
 4    "image": "busybox",
 5    "name": "busybox"
 6  },
 7  {
 8    "image": "nginx",
 9    "name": "nginx"
10  }
11]

The above output shows the return of an array of objects as expected where each object is the container. But by using a multi-select list, the initContainer[] array may also be parsed.

 1$ yq e pod.yaml -o json | kyverno jp "spec.[initContainers, containers]"
 2[
 3  [
 4    {
 5      "image": "redis",
 6      "name": "redis"
 7    }
 8  ],
 9  [
10    {
11      "image": "busybox",
12      "name": "busybox"
13    },
14    {
15      "image": "nginx",
16      "name": "nginx"
17    }
18  ]
19]

In the above, a multi-select list spec.[initContainers, containers] “wraps” the results of both initContainers[] and containers[] in parent array thereby producing an array consisting of multiple arrays. By using the flatten operator, these results can be collapsed into just a single array.

 1$ yq e pod.yaml -o json | kyverno jp "spec.[initContainers, containers][]"
 2[
 3  {
 4    "image": "redis",
 5    "name": "redis"
 6  },
 7  {
 8    "image": "busybox",
 9    "name": "busybox"
10  },
11  {
12    "image": "nginx",
13    "name": "nginx"
14  }
15]

With just a single array in which all containers, regardless of where they are, occur in a single hierarchy, it becomes easier to process the data for relevant fields and take action. For example, if you wished to write a policy which forbid using the image named busybox in a Pod, by flattening all containers it becomes easier to isolate just the image field. Because it does not matter where busybox may be found, if found the entire Pod must be rejected. Therefore, while loops or other methods may work, a more efficient method is to simply gather all containers across the Pod and flatten them.

1$ yq e pod.yaml -o json | kyverno jp "spec.[initContainers, containers][].image"
2[
3  "redis",
4  "busybox",
5  "nginx"
6]

With all of the images stored in a simple array, the values can be parsed much easier and just one expression written to contain the necessary logic.

1deny:
2  conditions:
3    any:
4    - key: busybox
5      operator: AnyIn
6      value: "{{request.object.spec.[initContainers, containers][].image}}"

Non-Existence Checks

It is common for a JMESPath expression to name a specific field so that its value may be acted upon. For example, in the basics section above, the label appns is written to a Pod via a mutate rule which does not contain it or is set to a different value. A Kyverno validate rule which exists to check the value of that label or any other field is commonplace. Because the schema for many Kubernetes resources is flexible in that many fields are optional, policy rules must contend with the scenario in which a matching resource does not contain the field being checked. When using JMESPath to check the value of such a field, a simple expression might be written {{request.object.metadata.labels.appns}}. If a resource is submitted which either does not contain any labels at all or does not contain a label with the specified key then the expression cannot be evaluated. An error is likely to result similar to JMESPath query failed: Unknown key "labels" in path. In these types of cases, the JMESPath expression should use a non-existence check in the form of the OR expression followed by a “default” value if the field does not exist. The resulting full expression which will correctly evaluate is {{request.object.metadata.labels.appns || ''}}. This expression reads, “take the value of the key request.object.metadata.labels.appns or, if it does not exist, set it to an empty string”. Note that the value on the right side may need to be customized given the ultimate use of the value expected to be produced. This non-existence pattern can be used in almost any JMESPath expression to mitigate scenarios in which the initial query may be invalid.

Matching Special Characters

Kyverno reserves special behavior for wildcard characters such as * and ?. However, certain Kubernetes resources permit wildcards as values in various fields which are treated literally. It may be necessary to construct a policy which validates literal usage of such wildcards. Using the JMESPath contains() filter it is possible to do so. The below policy shows how to use contains() to match on wildcards as literal characters.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: restrict-ingress-wildcard
 5spec:
 6  validationFailureAction: enforce
 7  rules:
 8    - name: block-ingress-wildcard
 9      match:
10        any:
11        - resources:
12            kinds:
13              - Ingress
14      validate:
15        message: "Wildcards are not permitted as hosts."
16        foreach:
17        - list: "request.object.spec.rules"
18          deny:
19            conditions:
20              any:
21              - key: "{{ contains(element.host, '*') }}"
22                operator: Equals
23                value: true

Custom Filters

In addition to the filters available in the upstream JMESPath library which Kyverno uses, there are also many new and custom filters developed for Kyverno’s use found nowhere else. These filters augment the already robust capabilities of JMESPath to bring new functionality and capabilities which help solve common use cases in running Kubernetes. The filters endemic to Kyverno can be used in addition to any of those found in the upstream JMESPath library used by Kyverno and do not represent replaced or removed functionality.

For instructions on how to test these filters in a standalone method (i.e., outside of Kyverno policy), see the documentation on the kyverno jp subcommand.

Information on each subcommand, its inputs and output, and specific usage instructions can be found below along with helpful and common use cases that have been identified.

Add

Expand

The add() filter very simply adds two values and produces a sum. The official JMESPath library does not include most basic arithmetic operators such as add, subtract, multiply, and divide, the exception being sum() as documented here. While sum() is useful in that it accepts an array of integers as an input, add() is useful as a simplified filter when only two individual values need to be summed. Note that add() here is different from the length() filter which is used to obtain a count of a certain number of items. Use add() instead when you have values of two fields you wish to add together.

add() is also value-aware (based on the formatting used for the inputs) and is capable of adding numbers, quantities, and durations without any form of unit conversion.

Arithmetic filters like add() currently accept inputs in the following formats.

  • Number (ex., `10`)
  • Quantity (ex., ‘10Mi’)
  • Duration (ex., ‘10h’)

Note that how the inputs are enclosed determines how Kyverno interprets their type. Numbers enclosed in back ticks are scalar values while quantities and durations are enclosed in single quotes thus treating them as strings. Using the correct enclosing character is important because, in Kubernetes “regular” numbers are treated implicitly as units of measure. The number written `10` is interpreted as an integer or “the number ten” whereas ‘10’ is interpreted as a string or “ten bytes”. See the Formatting section above for more details.

Input 1Input 2Output
NumberNumberNumber
Quantity or NumberQuantity or NumberQuantity
Duration or NumberDuration or NumberDuration

Some specific behaviors to note:
  • If a duration (‘1h’) and a number (`5`) are the inputs, the number will be interpreted as seconds resulting in a sum of 1h0m5s.
  • Because of durations being a string just like resource quantities, and the minutes unit of “m” also present in quantities interpreted as the “milli” prefix, there is no support for minutes.

Example: This policy denies a Pod if any of its containers specify memory requests and limits in excess of 200Mi.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: add-demo
 5spec:
 6  validationFailureAction: enforce
 7  background: false
 8  rules:
 9  - name: add-demo
10    match:
11      any:
12      - resources:
13          kinds:
14          - Pod
15    preconditions:
16      any:
17      - key: "{{ request.operation }}"
18        operator: In
19        value: ["CREATE","UPDATE"]
20    validate:
21      message: "The total memory defined in requests and limits must not exceed 200Mi."
22      foreach:
23      - list: "request.object.spec.containers"
24        deny:
25          conditions:
26            any:
27            - key: "{{ add('{{ element.resources.requests.memory || `0` }}', '{{ element.resources.limits.memory || `0` }}') }}"
28              operator: GreaterThan
29              value: 200Mi

Base64_decode

Expand

The base64_decode() filter takes in a base64-encoded string and produces the decoded output similar to the tool and command base64 --decode. This can be useful when working with Kubernetes Secrets and deciphering their values in order to take action on them in a policy.

Input 1Output
StringString

Some specific behaviors to note:
  • Base64-encoded strings with newline characters will be printed back with them inline.

Example: This policy checks every container, initContainer, and ephemeralContainer in a Pod and decodes a Secret having the path data.license to ensure it does not refer to a prohibited license key.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: base64-decode-demo
 5spec:
 6  background: false
 7  validationFailureAction: enforce
 8  rules:
 9  - name: base64-decode-demo
10    match:
11      any:
12      - resources:
13          kinds:
14          - Pod
15    preconditions:
16      all:
17      - key: "{{ request.object.spec.[containers, initContainers, ephemeralContainers][].env[].valueFrom.secretKeyRef || '' | length(@) }}"
18        operator: GreaterThanOrEquals
19        value: 1
20      - key: "{{request.operation}}"
21        operator: NotEquals
22        value: DELETE
23    validate:
24      message: This license key may not be consumed by a Secret.
25      foreach:
26      - list: "request.object.spec.[containers, initContainers, ephemeralContainers][].env[].valueFrom.secretKeyRef"
27        context:
28        - name: status
29          apiCall:
30            jmesPath: "data.license"
31            urlPath: "/api/v1/namespaces/{{request.namespace}}/secrets/{{element.name}}"
32        deny:
33          conditions:
34            any:
35            - key: "{{ status | base64_decode(@) }}"
36              operator: Equals
37              value: W0247-4RXD3-6TW0F-0FD63-64EFD-38180

Base64_encode

Expand

The base64_encode() filter is the inverse of the base64_decode() filter and takes in a regular, plaintext and unencoded string and produces a base64-encoded output similar to the tool and command base64. This can be useful when working with Kubernetes Secrets by encoding data into the base64 format which is the only acceptable format for Kubernetes Secrets.

Input 1Output
StringString

Example: This policy generates a Secret when a new Namespace is created the contents of which is the value of an annotation named corpkey.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: base64-encode-demo
 5spec:
 6  rules:
 7  - name: gen-supkey
 8    match:
 9      any:
10      - resources:
11          kinds:
12          - Namespace
13    generate:
14      apiVersion: v1
15      kind: Secret
16      name: sup-key
17      namespace: "{{request.object.metadata.name}}"
18      synchronize: false
19      data:
20        data:
21          token: "{{ base64_encode('{{ request.object.metadata.annotations.corpkey }}') }}"

Compare

Expand

The compare() filter is provided as an analog to the inbuilt function to Golang of the same name. It compares two strings lexicographically where the first string is compared against the second. If both strings are equal, the result is 0 (ex., “a” compared to “a”). If the first is in lower lexical order than the second, the result is -1 (ex., “a” compared to “b”). And if the first is in higher order than the second, the result is 1 (ex., “b” compared to “a”). Kyverno also has built-in operators for string comparison where Equals is usually the most common, and in most use cases it is more practical to use the Equals operator in expressions such as preconditions and deny.conditions blocks.

Input 1Input 2Output
StringStringNumber

Example: This policy will write a new label called dictionary into a Service putting into order the values of two annotations if the order of the first comes before the second.

 1apiVersion : kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: compare-demo
 5spec:
 6  background: false
 7  rules:
 8  - name: write-dictionary
 9    match:
10      any:
11      - resources:
12          kinds:
13          - Service
14    preconditions:
15      any:
16      - key: "{{ compare('{{request.object.metadata.annotations.foo}}', '{{request.object.metadata.annotations.bar}}') }}"
17        operator: LessThan
18        value: 0
19    mutate:
20      patchStrategicMerge:
21        metadata:
22          labels:
23            dictionary: "{{request.object.metadata.annotations.foo}}-{{request.object.metadata.annotations.bar}}"

Divide

Expand

The divide() filter performs arithmetic divide capabilities between two input fields and produces an output quotient. Like other arithmetic custom filters, it is input aware based on the type passed and, for quantities, allows auto conversion between units of measure. For example, dividing 10Mi (ten mebibytes) by 5Ki (five kibibytes) results in the value 5120 as units are first normalized and then canceled through division. The divide() filter is currently under development to better account for all permutations of input types, however the below table captures the most common and practical use cases.

Arithmetic filters like divide() currently accept inputs in the following formats.

  • Number (ex., `10`)
  • Quantity (ex., ‘10Mi’)
  • Duration (ex., ‘10h’)

Note that how the inputs are enclosed determines how Kyverno interprets their type. Numbers enclosed in back ticks are scalar values while quantities and durations are enclosed in single quotes thus treating them as strings. Using the correct enclosing character is important because, in Kubernetes “regular” numbers are treated implicitly as units of measure. The number written `10` is interpreted as an integer or “the number ten” whereas ‘10’ is interpreted as a string or “ten bytes”. See the Formatting section above for more details.

Input 1Input 2Output
NumberNumberNumber
QuantityNumberQuantity
QuantityQuantityNumber
DurationNumberDuration
DurationDurationNumber

Example: This policy will check every container in a Pod and ensure that memory limits are no more than 2.5x its requests.

 1apiVersion : kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: enforce-resources-as-ratio
 5spec:
 6  validationFailureAction: audit
 7  rules:
 8  - name: check-memory-requests-limits
 9    match:
10      any:
11      - resources:
12          kinds:
13          - Pod
14    preconditions:
15      any:
16      - key: "{{ request.operation }}"
17        operator: In
18        value:
19        - CREATE
20        - UPDATE
21    validate:
22      message: Limits may not exceed 2.5x the requests.
23      foreach:
24      - list: "request.object.spec.containers"
25        deny:
26          conditions:
27            any:
28              # Set resources.limits.memory equal to zero if not present and resources.requests.memory equal to 1m rather than zero
29              # to avoid undefined division error. No memory request in this case is basically the same as 1m. Kubernetes API server
30              # will automatically set requests=limits if only limits is defined.
31            - key: "{{ divide('{{ element.resources.limits.memory || '0' }}', '{{ element.resources.requests.memory || '1m' }}') }}"
32              operator: GreaterThan
33              value: 2.5

Equal_fold

Expand

The equal_fold() filter is designed to provide text case folding for two sets of strings as inputs. Case folding allows comparing two strings for equivalency where the only differences are letter cases. The return is a boolean (either true or false). For example, comparing “pizza” to “Pizza” results in true because other than title case on “Pizza” the strings are equivalent. Likewise with “pizza” and “pIzZa”. Comparing “pizza” to “APPLE” results in false because even once normalized to the same case, the strings are different.

Input 1Input 2Output
StringStringBoolean

Related filters to equal_fold() are to_upper() and to_lower() which can also be used to normalize text for comparison.


Example: This policy will validate that a ConfigMap with a label named dept and the value of a key under data by the same name have the same case-insensitive value.

 1apiVersion : kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: equal-fold-demo
 5spec:
 6  validationFailureAction: enforce
 7  background: false
 8  rules:
 9  - name: validate-dept-label-data
10    match:
11      any:
12      - resources:
13          kinds:
14          - ConfigMap
15    validate:
16      message: The dept label must equal the data.dept value aside from case.
17      deny:
18        conditions:
19          any:
20          - key: "{{ equal_fold('{{request.object.metadata.labels.dept}}', '{{request.object.data.dept}}') }}"
21            operator: NotEquals
22            value: true

Items

Expand

The items() filter iterates on map keys (ex., annotations or labels) and converts them to an array of objects with key/value attributes with custom names.

For example, given the following map below

1{
2  "team": "apple",
3  "organization": "banana"
4}

the items() filter can transform this into an array of objects which assigns a key and value of arbitrary name to each of the entries in the map.

 1$ echo '{"team" : "apple" , "organization" : "banana" }' | k kyverno jp "items(@, 'key', 'value')"
 2[
 3  {
 4    "key": "organization",
 5    "value": "banana"
 6  },
 7  {
 8    "key": "team",
 9    "value": "apple"
10  }
11]
Input 1Input 2Input 3Output
Map (Object)StringStringArray/Object

Related filter to items() is its inverse, object_from_list().


Example: This policy will take the labels on a Namespace foobar where a Bucket is deployed and add them as key/value elements to the spec.forProvider.tagging.tagSet[] array.

 1apiVersion : kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: test-policy
 5spec:
 6  background: false
 7  rules:
 8  - name: test-rule
 9    match:
10      any:
11      - resources:
12          kinds:
13          - Bucket
14    context:
15    - name: nslabels
16      apiCall:
17        urlPath: /api/v1/namespaces/foobar
18        jmesPath: items(metadata.labels,'key','value')
19    mutate:
20      foreach:
21      - list: "nslabels"
22        patchesJson6902: |-
23          - path: "/spec/forProvider/tagging/tagSet/-1"
24            op: add
25            value: {"key": "{{element.key}}", "value": "{{element.value}}"}          

Given a Namespace which looks like the following

1apiVersion: v1
2kind: Namespace
3metadata:
4  name: foobar
5  labels:
6    team: apple
7    organization: banana

and a Bucket which looks like the below

 1apiVersion: s3.aws.crossplane.io/v1beta1
 2kind: Bucket
 3metadata:
 4  name: lambda-bucket
 5spec:
 6  forProvider:
 7    acl: private
 8    locationConstraint: eu-central-1
 9    accelerateConfiguration:
10      status: Enabled
11    versioningConfiguration:
12      status: Enabled
13    notificationConfiguration:
14      lambdaFunctionConfigurations:
15        - events: ["s3:ObjectCreated:*"]
16          lambdaFunctionArn: arn:aws:lambda:eu-central-1:255932642927:function:lambda
17    paymentConfiguration:
18      payer: BucketOwner
19    tagging:
20      tagSet:
21        - key: s3-bucket
22          value: lambda-bucket
23    objectLockEnabledForBucket: false
24  providerConfigRef:
25    name: default

the final spec.forProvider.tagging.tagSet[] will appear as below. Note that as of Kubernetes 1.21, the immutable label with key kubernetes.io/metadata.name and value equal to that of the Namespace name is automatically added to all Namespaces, hence the discrepancy when comparing Namespace with Bucket resource manifests above.

 1$ k get bucket lambda-bucket -o json | k kyverno jp "spec.forProvider.tagging.tagSet[]"
 2[
 3  {
 4    "key": "s3-bucket",
 5    "value": "lambda-bucket"
 6  },
 7  {
 8    "key": "kubernetes.io/metadata.name",
 9    "value": "foobar"
10  },
11  {
12    "key": "organization",
13    "value": "banana"
14  },
15  {
16    "key": "team",
17    "value": "apple"
18  }
19]

Label_match

Expand

The label_match() filter compares two sets of Kubernetes labels (both key and value) and outputs a boolean response if they are equivalent. This custom filter is useful in that it functions similarly to how the Kubernetes API server associates one resource with another through label selectors. There may be one or multiple labels in each set. Labels may occur in any order. A response of true indicates all the labels (key and value) in the first input are accounted for in the second input. The second input, to which the first is compared, may have additional labels but it must have at minimum all those listed in the first input.

For example, the first collection compared to the second below results in true despite the ordering.

1{
2  "dog": "lab",
3  "color": "tan"
4}
1{
2  "color": "tan",
3  "dog": "lab"  
4}

Likewise, these two below collections also result in true when compared because the entirety of the first is found within the second.

1{
2  "dog": "lab",
3  "color": "tan"
4}
1{
2  "color": "tan",
3  "weight":"chonky",
4  "dog": "lab"  
5}

These last two collections when compared are false because one of the values of one of the labels does not match what is in the first input.

1{
2  "dog": "lab",
3  "color": "tan"
4}
1{
2  "color": "black",
3  "dog": "lab"  
4}
Input 1Input 2Output
Map (Object)Map (Object)Boolean

Example: This policy checks all incoming Deployments to ensure they have a matching, preexisting PodDisruptionBudget in the same Namespace. The label_match() filter is used in a query to count how many PDBs have a label set matching that of the incoming Deployment.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: require-pdb
 5spec:
 6  validationFailureAction: audit
 7  background: false
 8  rules:
 9  - name: require-pdb
10    match:
11      any:
12      - resources:
13          kinds:
14          - Deployment
15    preconditions:
16      any:
17      - key: "{{request.operation}}"
18        operator: Equals
19        value: CREATE
20    context:
21    - name: pdb_count
22      apiCall:
23        urlPath: "/apis/policy/v1beta1/namespaces/{{request.namespace}}/poddisruptionbudgets"
24        jmesPath: "items[?label_match(spec.selector.matchLabels, `{{request.object.spec.template.metadata.labels}}`)] | length(@)"
25    validate:
26      message: "There is no corresponding PodDisruptionBudget found for this Deployment."
27      deny:
28        conditions:
29          any:
30          - key: "{{pdb_count}}"
31            operator: LessThan
32            value: 1

Modulo

Expand

The modulo() filter returns the modulo or remainder between a division of two numbers. For example, the modulo of a division between 10 and 3 would be 1 since 3 can be divided into 10 only 3 times (equaling 9) while producing 1 as a remainder.

Arithmetic filters like modulo() currently accept inputs in the following formats.

  • Number (ex., `10`)
  • Quantity (ex., ‘10Mi’)
  • Duration (ex., ‘10h’)

Note that how the inputs are enclosed determines how Kyverno interprets their type. Numbers enclosed in back ticks are scalar values while quantities and durations are enclosed in single quotes thus treating them as strings. Using the correct enclosing character is important because, in Kubernetes “regular” numbers are treated implicitly as units of measure. The number written `10` is interpreted as an integer or “the number ten” whereas ‘10’ is interpreted as a string or “ten bytes”. See the Formatting section above for more details.

Input 1Input 2Output
NumberNumberNumber

The inputs list is currently under construction.


Example: This policy checks every container and ensures that memory limits are evenly divisible by its requests.

 1apiVersion : kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: modulo-demo
 5spec:
 6  validationFailureAction: audit
 7  rules:
 8  - name: check-memory-requests-limits
 9    match:
10      any:
11      - resources:
12          kinds:
13          - Pod
14    preconditions:
15      any:
16      - key: "{{ request.operation }}"
17        operator: In
18        value:
19        - CREATE
20        - UPDATE
21    validate:
22      message: Limits must be evenly divisible by the requests.
23      foreach:
24      - list: "request.object.spec.containers"
25        deny:
26          conditions:
27            any:
28              # Set resources.limits.memory equal to zero if not present and resources.requests.memory equal to 1m rather than zero
29              # to avoid undefined division error. No memory request in this case is basically the same as 1m. Kubernetes API server
30              # will automatically set requests=limits if only limits is defined.
31            - key: "{{ modulo('{{ element.resources.limits.memory || '0' }}', '{{ element.resources.requests.memory || '1m' }}') }}"
32              operator: GreaterThan
33              value: 0

Multiply

Expand

The multiply() filter performs standard multiplication on two inputs producing an output product. Like other arithmetic filters, it is input aware and will produce output with appropriate units attached.

Arithmetic filters like multiply() currently accept inputs in the following formats.

  • Number (ex., `10`)
  • Quantity (ex., ‘10Mi’)
  • Duration (ex., ‘10h’)

Note that how the inputs are enclosed determines how Kyverno interprets their type. Numbers enclosed in back ticks are scalar values while quantities and durations are enclosed in single quotes thus treating them as strings. Using the correct enclosing character is important because, in Kubernetes “regular” numbers are treated implicitly as units of measure. The number written `10` is interpreted as an integer or “the number ten” whereas ‘10’ is interpreted as a string or “ten bytes”. See the Formatting section above for more details.

Input 1Input 2Output
NumberNumberNumber
QuantityNumberQuantity
DurationNumberDuration

Due to the commutative property of multiplication, the ordering of inputs (unlike with divide()) is irrelevant.

The inputs list is currently under construction.


Example: This policy sets the replica count for a Deployment to a value of two times the current number of Nodes in a cluster.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: multiply-demo
 5spec:
 6  background: false
 7  rules:
 8    - name: multiply-replicas
 9      match:
10        any:
11        - resources:
12            kinds:
13            - Deployment
14      context:
15        - name: nodecount
16          apiCall:
17            urlPath: "/api/v1/nodes"
18            jmesPath: "items[] | length(@)"
19      mutate:
20        patchStrategicMerge:
21          spec:
22            replicas: "{{ multiply( `{{nodecount}}`,`2`) }}"

Object_from_list

Expand

The object_from_list() filter takes an array of objects and, based on the selected keys, produces a map. This is essentially the inverse of the items() filter.

For example, given a Pod definition that looks like the following

 1apiVersion: v1
 2kind: Pod
 3metadata:
 4  name: object-from-list-demo
 5  labels:
 6    foo: bar
 7spec:
 8  containers:
 9  - name: containername01
10    image: containerimage:01
11    env:
12    - name: KEY
13      value: "123-456-789"
14    - name: endpoint
15      value: "licensing.corp.org"

you may want to convert the spec.containers[].env[] array of objects into a map where each entry in the map sets the key to the name and the value to the value fields. Running this through the object_from_list() filter will produce a map containing those entries.

1$ k kyverno jp -f pod.yaml "object_from_lists(spec.containers[].env[].name,spec.containers[].env[].value)"
2{
3  "KEY": "123-456-789",
4  "endpoint": "licensing.corp.org"
5}
Input 1Input 2Output
Array/stringArray/stringMap (Object)

Related filter to object_from_list() is its inverse, items().


Example: This policy converts all the environment variables across all containers in a Pod to labels and adds them to that same Pod. Any existing labels will not be replaced but rather augmented with the converted list.

 1apiVersion : kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: object-from-list-demo
 5  annotations:
 6    pod-policies.kyverno.io/autogen-controllers: none
 7spec:
 8  background: false
 9  rules:
10  - name: object-from-list-rule
11    match:
12      any:
13      - resources:
14          kinds:
15          - Pod
16    context:
17    - name: envs
18      variable: 
19        jmesPath: request.object.spec.containers[].env[]
20    - name: envs_to_labels
21      variable: 
22        jmesPath: object_from_lists(envs[].name, envs[].value)
23    mutate:
24      patchStrategicMerge:
25        metadata:
26          labels:
27            "{{envs_to_labels}}"        

Given an incoming Pod that looks like the following

 1apiVersion: v1
 2kind: Pod
 3metadata:
 4  name: object-from-list-demo
 5  labels:
 6    foo: bar
 7spec:
 8  containers:
 9  - name: containername01
10    image: containerimage:01
11    env:
12    - name: KEY
13      value: "123-456-789"
14    - name: ENDPOINT
15      value: "licensing.corp.org"
16  - name: containername02
17    image: containerimage:02
18    env:
19    - name: ZONE
20      value: "fl-west-03"

after applying the policy the resulting label set on the Pod appears as shown below.

1$ k get pod/object-from-list-demo -o json | k kyverno jp "metadata.labels"
2{
3  "ENDPOINT": "licensing.corp.org",
4  "KEY": "123-456-789",
5  "ZONE": "fl-west-03",
6  "foo": "bar"
7}

Parse_json

Expand

The parse_json() filter takes in a string of any valid encoded JSON and parses it into a fully-formed JSON object. This is useful because it allows Kyverno to access and work with string data that is stored anywhere which accepts strings as if it were “native” JSON data. Primary use cases for this filter include adding anything from snippets to whole documents as the values of labels, annotations, or ConfigMaps which should then be consumed by policy.

Input 1Output
StringAny

Example: This policy uses the parse_json() filter to read a ConfigMap where a specified key contains JSON-encoded data (an array of strings in this case) and sets the supplementalGroups field of a Pod, if not already supplied, to that list.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: parse-json-demo
 5spec:
 6  rules:
 7  - name: parse-supplementalgroups-from-json
 8    match:
 9      any:
10      - resources:
11          kinds:
12          - Pod
13    context:
14    - name: gidsMap
15      configMap:
16        name: user-gids-map
17        namespace: default
18    mutate:
19      patchStrategicMerge:
20        spec:
21          securityContext:
22            +(supplementalGroups): "{{ gidsMap.data.\"{{ request.object.metadata.labels.\"corp.com/service-account\" }}\" | parse_json(@)[*].to_number(@) }}"

The referenced ConfigMap may look similar to the below.

1apiVersion: v1
2kind: ConfigMap
3metadata:
4  name: user-gids-map
5  namespace: default
6data:
7  finance: '["1001","1002"]'

Parse_yaml

Expand

The parse_yaml() filter is the YAML equivalent of the parse_json() filter and takes in a string of any valid YAML document, serializes it into JSON, and parses it so it may be processed by JMESPath. Like parse_json(), this is useful because it allows Kyverno to access and work with string data that is stored anywhere which accepts strings as if it were “native” YAML data. Primary use cases for this filter include adding anything from snippets to whole documents as the values of labels, annotations, or ConfigMaps which should then be consumed by policy.

Input 1Output
StringAny

Example: This policy parses a YAML document as the value of an annotation and uses the filtered value from a JMESPath expression in a variable substitution.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: parse-yaml-demo
 5spec:
 6  validationFailureAction: enforce
 7  background: false
 8  rules:
 9  - name: check-goodbois
10    match:
11      any:
12      - resources:
13          kinds:
14          - Pod
15    validate:
16      message: "Only good bois allowed."
17      deny:
18        conditions:
19        - key: "{{request.object.metadata.annotations.pets | parse_yaml(@).species.isGoodBoi }}"
20          operator: NotEquals
21          value: true

The referenced Pod may look similar to the below.

 1apiVersion: v1
 2kind: Pod
 3metadata:
 4  name: mypod
 5  labels:
 6    app: busybox
 7  annotations:
 8    pets: |-
 9      species:
10        dog: lab
11        name: dory
12        color: black
13        height: 15
14        isGoodBoi: false
15        snacks:
16        - chimken
17        - fries
18        - pizza      
19spec:
20  containers:
21  - name: busybox
22    image: busybox:1.28

Path_canonicalize

Expand

The path_canonicalize() filter is used to normalize or canonicalize a given path by removing excess slashes. For example, a path supplied to the filter may be /var//lib///kubelet which will be canonicalized into /var/lib/kubelet which is how an operating system would interpret the former. This filter is primarily used as a circumvention protection for what would otherwise be strict string matches for paths.

Input 1Output
StringString

Example: This policy uses the path_canonicalize() filter to check the value of each hostPath.path field in a volume block in a Pod to ensure it does not attempt to mount the Containerd host socket.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: path-canonicalize-demo
 5spec:
 6  validationFailureAction: enforce
 7  background: false
 8  rules:
 9  - name: disallow-mount-containerd-sock
10    match:
11      any:
12      - resources:
13          kinds:
14          - Pod
15    validate:
16      foreach:
17      - list: "request.object.spec.volumes[]"
18        deny:
19          conditions:
20            any:
21            - key: "{{ path_canonicalize(element.hostPath.path) }}"
22              operator: Equals
23              value: "/var/run/containerd/containerd.sock"

Pattern_match

Expand

The pattern_match() filter is used to perform a simple, non-regex match by specifying an input pattern and the string or number to which it should be compared. The output is always a boolean response. This filter can be useful when wishing to make simpler comparisons, typically with strings or numbers involved. It avoids many of the complexities of regex while still support wildcards such as * (zero or more characters) and ? (any one character). Note that since Kyverno supports overlay-style patterns and wildcards, use of pattern_match() is typically not needed in these scenarios. This filter is more valuable in dynamic lookup scenarios by using JMESPath variables for one or both inputs as exemplified below.

Input 1Input 2Output
StringStringBoolean
StringNumberBoolean

Example: This policy uses pattern_match() with dynamic inputs by fetching a pattern stored in a ConfigMap against an incoming Namespace label value.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: pattern-match-demo
 5spec:
 6  validationFailureAction: enforce
 7  background: false
 8  rules:
 9  - match:
10      any:
11      - resources:
12          kinds:
13          - Namespace
14    name: dept-billing-check
15    context:
16    - name: deptbillingcodes
17      configMap:
18        name: deptbillingcodes
19        namespace: default
20    validate:
21      message: The department {{request.object.metadata.labels.dept}} must supply a matching billing code.
22      deny:
23        conditions:
24          any:
25            - key: "{{pattern_match('{{deptbillingcodes.data.{{request.object.metadata.labels.dept}}}}', '{{ request.object.metadata.labels.segbill}}') }}"
26              operator: Equals
27              value: false

The ConfigMap used as the source of the patterns may look like below.

1apiVersion: v1
2kind: ConfigMap
3metadata:
4  name: deptbillingcodes
5data:
6  eng_china: 158-6?-3*
7  eng_india: 158-7?-4*
8  busops: 145-0?-9*
9  finops: 145-1?-5*

And a Namespace upon which the above ClusterPolicy may act can look like below.

1apiVersion: v1
2kind: Namespace
3metadata:
4  name: ind-go
5  labels:
6    dept: eng_india
7    segbill: 158-73-417

Random

Expand

The random() filter is used to generate a random sequence of string data based upon the input pattern, expressed as regex. The input it takes is a combination of the composition of the pattern and the length of each pattern. This filter is useful in a variety of ways including generating unique resource names. Some other use cases include creating Pod hashes, auth tokens, license keys, GUIDs, and more.

For example, random('[0-9a-z]{5}') will produce a string output of exactly 5 characters long composed of numbers in the collection 0-9 and lower-case letters in the collection a-z. The output might be "91t6f". More complex random output can be created by chaining multiple pattern and length combinations together. For example, to create a faux license key you could use the expression random('[A-Z0-9]{8}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{12}') which may generate the output "K284DW7Y-7LMT-XHR3-ZZ53-36366O8JVDG9".

Input 1Output
StringString

Example: This policy uses random() to mutate a new Secret to add a label with key randomoutput and the value of which is random- followed by 6 random characters composed of lower-case letters a-z and numbers 0-9.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: ver-test
 5spec:
 6  rules:
 7  - name: test-ver-ver
 8    match:
 9      any:
10      - resources:
11          kinds:
12          - Secret
13    preconditions:
14      all:
15      - key: "{{request.operation}}"
16        operator: In
17        value:
18        - CREATE
19    context:
20    - name: randomtest
21      variable:
22        jmesPath: random('[a-z0-9]{6}')
23    mutate:
24      patchStrategicMerge:
25        metadata:
26          labels:
27            randomoutput: random-{{randomtest}}

Regex_match

Expand

The regex_match() filter is similar to the pattern_match() filter except it accepts standard regular expressions as the comparison format. The first input is the pattern, specified in regex format, while the second is the string compared to the pattern which accepts either string or number. The output is always a boolean response. For example, the following two expressions, which check to ensure a number is in the range of one to seven, both evaluate to true.

regex_match('^[1-7]$',`1`)
regex_match('^[1-7]$','1')
Input 1Input 2Output
StringStringBoolean
StringNumberBoolean

Example: This policy checks that a PersistentVolumeClaim resource contains an annotation named backup-schedule and its value conforms to a standard Cron expression string. Note that the regular expression in the first input has had an additional backslash added to each backslash to be valid YAML. To use this sample regex in other applications, remove one of each double backslash pair.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: regex-match-demo
 5spec:
 6  background: true
 7  validationFailureAction: enforce
 8  rules:
 9  - name: validate-backup-schedule-annotation-cron
10    match:
11      any:
12      - resources:
13          kinds:
14          - PersistentVolumeClaim
15    validate:
16      message: The annotation `backup-schedule` must be present and in cron format.
17      deny:
18        conditions:
19          any:
20          - key: "{{ regex_match('^((?:\\*|[0-5]?[0-9](?:(?:-[0-5]?[0-9])|(?:,[0-5]?[0-9])+)?)(?:\\/[0-9]+)?)\\s+((?:\\*|(?:1?[0-9]|2[0-3])(?:(?:-(?:1?[0-9]|2[0-3]))|(?:,(?:1?[0-9]|2[0-3]))+)?)(?:\\/[0-9]+)?)\\s+((?:\\*|(?:[1-9]|[1-2][0-9]|3[0-1])(?:(?:-(?:[1-9]|[1-2][0-9]|3[0-1]))|(?:,(?:[1-9]|[1-2][0-9]|3[0-1]))+)?)(?:\\/[0-9]+)?)\\s+((?:\\*|(?:[1-9]|1[0-2])(?:(?:-(?:[1-9]|1[0-2]))|(?:,(?:[1-9]|1[0-2]))+)?)(?:\\/[0-9]+)?)\\s+((?:\\*|[0-7](?:-[0-7]|(?:,[0-7])+)?)(?:\\/[0-9]+)?)$', '{{request.object.metadata.annotations.\"backup-schedule\" || ''}}') }}"
21            operator: Equals
22            value: false

Regex_replace_all

Expand

The regex_replace_all() filter is similar to the replace_all() filter only differing by the first and third inputs being a valid regular expression rather than a static string. For literal replacement, see regex_replace_all_literal(). If numbers are supplied for the second and third inputs, they will internally be converted to string. The output is always a string. For example, the expression regex_replace_all('([0-9])([0-9])', 'hello im 42 months old', '${1}1') results in the output hello im 41 months old. The first input provides the regex which should be used to match against the second input, and the third serves as the replacement which, in this case, replaces the first capture group from the end with the number 1.

Input 1Input 2Input 3Output
Regex (String)StringRegex (String)String
Regex (String)NumberNumberString

Example: This policy mutates a Deployment having label named retention to set the last number to 0. For example, an incoming Deployment with the label value of days_37 would result in the value days_30 after mutation.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: regex-replace-all-demo
 5spec:
 6  background: false
 7  rules:
 8  - name: retention-adjust
 9    match:
10      any:
11      - resources:
12          kinds:
13          - Deployment
14    mutate:
15      patchStrategicMerge:
16        metadata:
17          labels:
18            retention: "{{ regex_replace_all('([0-9])([0-9])', '{{ @ }}', '${1}0') }}"

Regex_replace_all_literal

Expand

The regex_replace_all_literal() filter is similar to the regex_replace_all() filter with the third input being a static string used for literal replacement. If numbers are supplied for the second and third inputs, they will internally be converted to string. The output is always a string. For example, the expression regex_replace_all_literal('^(\d{3}-?\d{2}-?\d{4})$', '123-45-6789', 'redacted') would return redacted as the regex filter matches the faux social security number of 123-45-6789.

Input 1Input 2Input 3Output
Regex (String)StringStringString
Regex (String)NumberNumberString

Example: This policy replaces the image registry for each image in every container so it comes from myregistry.corp.com. Note that, for images without an explicit registry such as nginx:latest, Kyverno will internally replace this to be docker.io/nginx:latest and thereby ensuring the regex pattern below matches.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: regex-replace-all-literal-demo
 5spec:
 6  background: false
 7  rules:
 8    - name: replace-image-registry
 9      match:
10        any:
11        - resources:
12            kinds:
13              - Pod
14      mutate:
15        foreach:
16        - list: "request.object.spec.containers"
17          patchStrategicMerge:
18            spec:
19              containers:
20              - name: "{{ element.name }}"
21                image: "{{ regex_replace_all_literal('^[^/]+', '{{element.image}}', 'myregistry.corp.com' )}}"

Replace

Expand

The replace() filter is similar to the replace_all() filter except it takes a fourth input (a number) to specify how many instances of the source string should be replaced with the replacement string in a parent. For example, the expression shown below results in the value Lorem muspi dolor sit amet foo muspi bar ipsum because only two instances of the string ipsum were requested to be replaced. String replacement begins at the left and proceeds to the right halting once the desired count has been reached. If -1 is specified for the four input, it results in all instances of the source string being replaced (effectively the same behavior as replace_all()).

replace('Lorem ipsum dolor sit amet foo ipsum bar ipsum', 'ipsum', 'muspi', `2`)
Input 1Input 2Input 3Input 4Output
StringStringStringNumberString

Example: This policy replaces the rule on an Ingress resource so that the path field will replace the first instance of /cart with /shoppingcart.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: replace-demo
 5spec:
 6  background: false
 7  rules:
 8    - name: replace-path
 9      match:
10        any:
11        - resources:
12            kinds:
13              - Ingress
14      mutate:
15        foreach:
16        - list: "request.object.spec.rules[].http.paths[]"
17          patchStrategicMerge:
18            spec:
19              rules:
20              - http:
21                  paths:
22                  - backend:
23                      service: 
24                        name: kuard
25                        port: 
26                          number: 8080
27                    path: "{{ replace('{{element.path}}', '/cart', '/shoppingcart', `1`) }}"
28                    pathType: ImplementationSpecific

Replace_all

Expand

The replace_all() filter is used to find and replace all instances of one string with another in an overall parent string. Input strings are assumed to be literal and do not support wildcards. For example, the expression replace_all('Lorem ipsum dolor sit amet', 'ipsum', 'muspi') results in the value Lorem muspi dolor sit amet as the string ipsum has been replaced with muspi. If there were multiple instances of ipsum in the parent string, they would all be replaced with muspi.

Input 1Input 2Input 3Output
StringStringStringString

Example: This policy uses replace_all() to replace the string release-name--- with the contents of the annotation meta.helm.sh/release-name in the workingDir field under a container entry within a Deployment.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: replace-all-demo
 5spec:
 6  background: false
 7  rules:
 8    - name: replace-workingdir
 9      match:
10        any:
11        - resources:
12            kinds:
13              - Deployment
14      mutate:
15        patchStrategicMerge:
16          spec:
17            template:
18              spec:
19                containers:
20                  - (name): "*"
21                    workingDir: "{{ replace_all('{{@}}', 'release-name---', '{{request.object.metadata.annotations.\"meta.helm.sh/release-name\"}}') }}"

Semver_compare

Expand

The semver_compare() filter compares two strings which comply with the semantic versioning schema and outputs a boolean response as to the position of the second relative to the first. The first input is the “base” semver string for comparison while the second is the version is compared against the first. The second string accepts an operator prefix and supports AND and OR logic. It also supports the special placeholder variable “x” in any position. For some examples, semver_compare('1.2.3','1.2.4') results in the output false because version 1.2.4 is not equal to version 1.2.3. semver_compare('4.1.3','>=4.1.x') results in the output true because 4.1.3 is greater than or equal to 4.1.x. semver_compare('4.1.3','!4.x.x') returns false because 4.1.3 is equal to 4.x.x. semver_compare('1.8.6','>1.0.0 <2.0.0') returns true because the second input is an AND expression and 1.8.6 is both greater than 1.0.0 and less than 2.0.0. And semver_compare('2.1.5','<2.0.0 || >=3.0.0') returns false because 2.1.5 is neither less than 2.0.0 nor greater than or equal to 3.0.0.

Input 1Input 2Output
StringStringBoolean

Example: This policy uses semver_compare() to check the attestations on a container image and denies it has been built with httpclient greater than version 4.5.0.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: semver-compare-demo
 5spec:
 6  validationFailureAction: enforce
 7  background: false
 8  rules:
 9    - name: check-sbom
10      match:
11        any:
12        - resources:
13            kinds:
14              - Pod
15      verifyImages:
16      - image: "ghcr.io/kyverno/test-verify-image*"
17        key: |-
18          -----BEGIN PUBLIC KEY-----
19          MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHMmDjK65krAyDaGaeyWNzgvIu155
20          JI50B2vezCw8+3CVeE0lJTL5dbL3OP98Za0oAEBJcOxky8Riy/XcmfKZbw==
21          -----END PUBLIC KEY-----          
22        attestations:
23        - predicateType: https://example.com/CycloneDX/v1
24          conditions:
25            - all:
26              - key: "{{ components[?name == 'commons-logging'].version | [0] }}"
27                operator: GreaterThanOrEquals
28                value: "1.2.0"
29              - key: "{{ semver_compare( {{ components[?name == 'httpclient'].version | [0] }}, '>4.5.0') }}"
30                operator: Equals
31                value: true

Split

Expand

The split() filter is used to take in an input string, a character or sequence found within that string, and split the source into an array of strings. For example, the string cat,dog,horse can be split on the comma (,) character resulting in three separate strings in the collection ["cat","dog","horse"]. This filter is often most useful when looping over a number of different strings within a single value and performing some comparison or expression.

Input 1Input 2Output
StringStringArray/string

Example: This policy checks an incoming Ingress to ensure its root path does not conflict with another root path in a different Namespace. It requires that incoming Ingress resources have a single rule with a single path only and assumes the root path is specified explicitly in an existing Ingress rule (ex., when blocking /foo/bar /foo must exist by itself and not part of /foo/baz).

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: split-demo
 5spec:
 6  validationFailureAction: audit
 7  background: false
 8  rules:
 9    - name: check-path
10      match:
11        resources:
12          kinds:
13            - Ingress
14      context:
15        # Looks up the Ingress paths across the whole cluster.
16        - name: allpaths
17          apiCall:
18            urlPath: "/apis/networking.k8s.io/v1/ingresses"
19            jmesPath: "items[].spec.rules[].http.paths[].path"
20        # Looks up the Ingress paths in the same Namespace where the incoming request is targeted.
21        - name: nspath
22          apiCall:
23            urlPath: "/apis/networking.k8s.io/v1/namespaces/{{request.object.metadata.namespace}}/ingresses"
24            jmesPath: "items[].spec.rules[].http.paths[].path"
25      preconditions:
26        - key: "{{request.operation}}"
27          operator: Equals
28          value: "CREATE"
29      validate:
30        message: >-
31          The root path /{{request.object.spec.rules[].http.paths[].path | to_string(@) | split(@, '/') | [1]}}/ exists
32          in another Ingress rule elsewhere in the cluster.          
33        deny:
34          conditions:
35            all:
36              # Deny if the root path of the request exists somewhere else in the cluster other than the same Namespace.
37              - key: /{{request.object.spec.rules[].http.paths[].path | to_string(@) | split(@, '/') | [1]}}/
38                operator: In
39                value: "{{allpaths}}"
40              - key: /{{request.object.spec.rules[].http.paths[].path | to_string(@) | split(@, '/') | [1]}}/
41                operator: NotIn
42                value: "{{nspath}}"

Subtract

Expand

The subtract() filter performs arithmetic subtraction capabilities between two input fields (terms) and produces an output difference. Like other arithmetic custom filters, it is input aware based on the type passed and, for quantities, allows auto conversion between units of measure. For example, subtracting 10Mi (ten mebibytes) minus 5Ki (five kibibytes) results in the value 10235Ki. The subtract() filter is currently under development to better account for all permutations of input types, however the below table captures the most common and practical use cases.

Arithmetic filters like subtract() currently accept inputs in the following formats.

  • Number (ex., `10`)
  • Quantity (ex., ‘10Mi’)
  • Duration (ex., ‘10h’)

Note that how the inputs are enclosed determines how Kyverno interprets their type. Numbers enclosed in back ticks are scalar values while quantities and durations are enclosed in single quotes thus treating them as strings. Using the correct enclosing character is important because, in Kubernetes “regular” numbers are treated implicitly as units of measure. The number written `10` is interpreted as an integer or “the number ten” whereas ‘10’ is interpreted as a string or “ten bytes”. See the Formatting section above for more details.

Input 1Input 2Output
NumberNumberNumber
QuantityQuantityNumber
DurationDurationDuration

Example: This policy sets the value of a new label called lessreplicas to the value of the current number of replicas in a Deployment minus two so long as there are more than two replicas to start with.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: subtract-demo
 5spec:
 6  background: false
 7  rules:
 8  - name: subtract-demo
 9    match:
10      any:
11      - resources:
12          kinds:
13          - Deployment
14    preconditions:
15      any:
16      - key: "{{ request.object.spec.replicas }}"
17        operator: GreaterThan
18        value: 2
19    mutate:
20      patchStrategicMerge:
21        metadata:
22          labels:
23            lessreplicas: "{{ subtract('{{ request.object.spec.replicas }}',`2`) }}"

Time_since

Expand

The time_since() filter is used to calculate the difference between a start and end period of time where the end may either be a static definition or the then-current time. The time formats currently supported are RFC3339 (the same as used by Kubernetes) or a user-definable time format as supported by the time.Parse() Go function as documented here. The output time difference is always given in hours, minutes, and seconds where seconds may either be an integer or floating point. For example, the expression time_since('','2022-04-10T03:14:05-07:00','2022-04-11T03:14:05-07:00') will result in the output of "24h0m0s". The first input for time format defaults to RFC3339. The expression time_since('Mon Jan _2 15:04:05 MST 2006', 'Mon Jan 02 15:04:05 MST 2021', 'Mon Jan 10 03:14:16 MST 2021') uses Unix date format for the inputs (the same as when running the date program) and will result in the output "180h10m11s". Helm time format is also parsable, for example time_since('2006-Jan-02','2020-Jan-14','2020-Jan-17') resulting in "72h0m0s". And the expression time_since('','2022-04-10T03:14:05-07:00','') will result in the difference between the current time and the second input. The output will be given in which seconds is a floating point value, for example "28h0m33.8257394s".

The time format (layout) parameter is optional and will be defaulted to RFC3339 if left empty (i.e., ‘’). It may not be set explicitly to an RFC3339 format. The time end (third input) may be set to an empty string indicating the current time (i.e., now) when the expression is evaluated.

Input 1Input 2Input 3Output
Time format (String)Time start (String)Time end (String)Time difference (String)

Example: This policy uses time_since() to compare the time a container image was created to the present time, blocking if that difference is greater than six months.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: time-since-demo
 5spec:
 6  validationFailureAction: audit 
 7  rules:
 8    - name: block-stale-images
 9      match:
10        any:
11        - resources:
12            kinds:
13            - Pod
14      validate:
15        message: "Images built more than 6 months ago are prohibited."
16        foreach:
17        - list: "request.object.spec.containers"
18          context:
19          - name: imageData
20            imageRegistry:
21              reference: "{{ element.image }}"
22          deny:
23            conditions:
24              all:
25                - key: "{{ time_since('', '{{ imageData.configData.created }}', '') }}"
26                  operator: GreaterThan
27                  value: 4380h

To_lower

Expand

The to_lower() filter takes in a string and outputs the same string with all lower-case letters. It is the opposite of to_upper().

Input 1Output
StringString

Example: This policy sets the value of a label named zonekey to all caps.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: to-lower-demo
 5spec:
 6  rules:
 7  - name: format-deploy-zone
 8    match:
 9      any:
10      - resources:
11          kinds:
12          - Service
13    mutate:
14      patchStrategicMerge:
15        metadata:
16          labels:
17            zonekey: "{{ to_lower('{{@}}') }}"

To_upper

Expand

The to_upper() filter takes in a string and outputs the same string with all upper-case letters. It is the opposite of to_lower().

Input 1Output
StringString

Example: This policy sets the value of a label named deployzone to all caps.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: to-upper-demo
 5spec:
 6  rules:
 7  - name: format-deploy-zone
 8    match:
 9      any:
10      - resources:
11          kinds:
12          - Service
13    mutate:
14      patchStrategicMerge:
15        metadata:
16          labels:
17            deployzone: "{{ to_upper('{{@}}') }}"

Trim

Expand

The trim() filter takes a string containing a “source” string, a second string representing a collection of discrete characters, and outputs the remainder of the source when both ends of the source string are trimmed by characters appearing in the collection. For example, inputs of ¡¡¡Hello, Gophers!!! and will result in the output Hello, Gophers since the characters ¡ and ! are found at the beginning and end of the input string and will be trimmed. In the case of trim('foocorpcom','mo') the output returned is foocorpc since letters m and o are found at the end of foocorpcom. Notice that ordering of the letters in the second input is irrelevant. Interior characters will not be stripped unless exterior characters have also been removed. For example, trim('foocorpcom','o') will return the input of foocorpcom because o does not occur at the beginning or end of the input string. Characters named in the second input will be deduplicated from the source string so long as outside characters have been trimmed first. For example, trim('foocorpcom','mcof') will result in the output of rp since the other four characters in the second input collection can be stripped from the beginning and end of the input string.

This filter is similar to truncate(). The trim() filter can be useful to remove exact portions of a string when they are known literally.

Input 1Input 2Output
StringStringString

Example: This policy uses the trim() filter to remove the domain from an email value set in an annotation.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: trim-demo
 5spec:
 6  rules:
 7  - name: trim-extnameemail
 8    match:
 9      any:
10      - resources:
11          kinds:
12          - Service
13    mutate:
14      patchStrategicMerge:
15        metadata:
16          annotations:
17            extnameemail: "{{ trim('{{@}}','@corp.com') }}"

Truncate

Expand

The truncate() filter takes a string, a number, and shortens (truncates) that string from the beginning to only include the desired number of characters. For example, calling truncate() on the string foobar by the number 3 would result in the output of foo because only three character positions were requested. This can be a useful filter when formulating values of names, labels, annotations, or other pieces of metadata to conform to a given length and to avoid overruns.

Input 1Input 2Output
StringNumberString

Example: This policy truncates the value of a label called buildhash to only take the first twelve characters.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: truncate-demo
 5spec:
 6  rules:
 7  - name: truncate-buildhash
 8    match:
 9      any:
10      - resources:
11          kinds:
12          - Namespace
13    mutate:
14      patchStrategicMerge:
15        metadata:
16          labels:
17            buildhash: "{{ truncate('{{@}}',`12`) }}"

Last modified September 17, 2022 at 5:24 PM PST: 1.8.0 updates (#625) (9f61715)