Validate Resources

Check resource configurations for policy compliance.

Validation rules are probably the most common and practical types of rules you will be working with, and the main use case for admission controllers such as Kyverno. In a typical validation rule, one defines the mandatory properties with which a given resource should be created. When a new resource is created by a user or process, the properties of that resource are checked by Kyverno against the validate rule. If those properties are validated, meaning there is agreement, the resource is allowed to be created. If those properties are different, the creation is blocked. The behavior of how Kyverno responds to a failed validation check is determined by the validationFailureAction field. It can either be blocked (enforce) or noted in a policy report (audit). Validation rules in audit mode can also be used to get a report on matching resources which violate the rule(s), both upon initial creation and when Kyverno initiates periodic scans of Kubernetes resources. Resources in violation of an existing rule placed in audit mode will also surface in an event on the resource in question.

To validate resource data, define a pattern in the validation rule. To deny certain API requests define a deny element in the validation rule along with a set of conditions that control when to allow or deny the request.

Basic Validations

As a basic example, consider the below ClusterPolicy which validates that any new Namespace that is created has the label purpose with the value of production.

 1apiVersion: kyverno.io/v1
 2# The `ClusterPolicy` kind applies to the entire cluster.
 3kind: ClusterPolicy
 4metadata:
 5  name: require-ns-purpose-label
 6# The `spec` defines properties of the policy.
 7spec:
 8  # The `validationFailureAction` tells Kyverno if the resource being validated should be allowed but reported (`audit`) or blocked (`enforce`).
 9  validationFailureAction: enforce
10  # The `rules` is one or more rules which must be true.
11  rules:
12  - name: require-ns-purpose-label
13    # The `match` statement sets the scope of what will be checked. In this case, it is any `Namespace` resource.
14    match:
15      any:
16      - resources:
17          kinds:
18          - Namespace
19    # The `validate` statement tries to positively check what is defined. If the statement, when compared with the requested resource, is true, it is allowed. If false, it is blocked.
20    validate:
21      # The `message` is what gets displayed to a user if this rule fails validation and is therefore blocked.
22      message: "You must have label `purpose` with value `production` set on all new namespaces."
23      # The `pattern` object defines what pattern will be checked in the resource. In this case, it is looking for `metadata.labels` with `purpose=production`.
24      pattern:
25        metadata:
26          labels:
27            purpose: production

If a new Namespace with the following definition is submitted to Kyverno, given the ClusterPolicy above, it will be allowed (validated). This is because it contains the label of purpose=production, which is the only pattern being validated in the rule.

1apiVersion: v1
2kind: Namespace
3metadata:
4  name: prod-bus-app1
5  labels:
6    purpose: production

By contrast, if a new Namespace with the below definition is submitted, given the ClusterPolicy above, it will be blocked (invalidated). As you can see, its value of the purpose label differs from that required in the policy. But this isn’t the only way a validation can fail. If, for example, the same Namespace is requested which has no labels defined whatsoever, it too will be blocked for the same reason.

1apiVersion: v1
2kind: Namespace
3metadata:
4  name: prod-bus-app1
5  labels:
6    purpose: development

Save the above manifest as ns.yaml and try to create it with your sample ClusterPolicy in place.

1$ kubectl create -f ns.yaml
2Error from server: error when creating "ns.yaml": admission webhook "validate.kyverno.svc" denied the request:
3
4resource Namespace//prod-bus-app1 was blocked due to the following policies
5
6require-ns-purpose-label:
7  require-ns-purpose-label: 'Validation error: You must have label `purpose` with value `production` set on all new namespaces.; Validation rule require-ns-purpose-label failed at path /metadata/labels/purpose/'

Change the development value to production and try again. Kyverno permits creation of your new Namespace resource.

Validation Failure Action

The validationFailureAction attribute controls admission control behaviors for resources that are not compliant with a policy. If the value is set to enforce, resource creation or updates are blocked when the resource does not comply. When the value is set to audit, a policy violation is logged in a PolicyReport or ClusterPolicyReport but the resource creation or update is allowed. For preexisting resources which violate a newly-created policy set to enforce mode, Kyverno will allow subsequent updates to those resources which continue to violate the policy as a way to ensure no existing resources are impacted. However, should a subsequent update to the violating resource(s) make them compliant, any further updates which would produce a violation are blocked.

Validation Failure Action Overrides

Using validationFailureActionOverrides, you can specify which actions to apply per Namespace. This attribute is only available for ClusterPolicies.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: check-label-app
 5spec:
 6  validationFailureAction: audit
 7  validationFailureActionOverrides:
 8    - action: enforce     # Action to apply
 9      namespaces:       # List of affected namespaces
10        - default
11    - action: audit
12      namespaces:
13        - test
14  rules:
15    - name: check-label-app
16      match:
17        any:
18        - resources:
19            kinds:
20            - Pod
21      validate:
22        message: "The label `app` is required."
23        pattern:
24          metadata:
25            labels:
26              app: "?*"

In the above policy, for Namespace default, validationFailureAction is set to enforce and for Namespace test, it’s set to audit. For all other Namespaces, the action defaults to the validationFailureAction field.

Patterns

A validation rule which checks resource data is defined as an overlay pattern that provides the desired configuration. Resource configurations must match fields and expressions defined in the pattern to pass the validation rule. The following rules are followed when processing the overlay pattern:

  1. Validation will fail if a field is defined in the pattern and if the field does not exist in the configuration.
  2. Undefined fields are treated as wildcards.
  3. A validation pattern field with the wildcard value ‘*’ will match zero or more alphanumeric characters. Empty values are matched. Missing fields are not matched.
  4. A validation pattern field with the wildcard value ‘?’ will match any single alphanumeric character. Empty or missing fields are not matched.
  5. A validation pattern field with the wildcard value ‘?*’ will match any alphanumeric characters and requires the field to be present with non-empty values.
  6. A validation pattern field with the value null or "" (empty string) requires that the field not be defined or has no value.
  7. The validation of siblings is performed only when one of the field values matches the value defined in the pattern. You can use the conditional anchor to explicitly specify a field value that must be matched. This allows writing rules like ‘if fieldA equals X, then fieldB must equal Y’.
  8. Validation of child values is only performed if the parent matches the pattern.

Wildcards

  1. * - matches zero or more alphanumeric characters
  2. ? - matches a single alphanumeric character

For a couple of examples on how wildcards work in rules, see the following.

This policy requires that all containers in all Pods have resource requests and limits defined (CPU limits intentionally omitted):

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: all-containers-need-requests-and-limits
 5spec:
 6  validationFailureAction: enforce
 7  rules:
 8  - name: check-container-resources
 9    match:
10      any:
11      - resources:
12          kinds:
13          - Pod
14    validate:
15      message: "All containers must have CPU and memory resource requests and limits defined."
16      pattern:
17        spec:
18          containers:
19          # Select all containers in the pod. The `name` field here is not specifically required but serves
20          # as a visual aid for instructional purposes.
21          - name: "*"
22            resources:
23              limits:
24                # '?' requires 1 alphanumeric character and '*' means that
25                # there can be 0 or more characters. Using them together
26                # e.g. '?*' requires at least one character.
27                memory: "?*"
28              requests:
29                memory: "?*"
30                cpu: "?*"

The following validation rule checks for a label in Deployment, StatefulSet, and DaemonSet resources:

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: check-label-app
 5spec:
 6  validationFailureAction: enforce
 7  rules:
 8    - name: check-label-app
 9      match:
10        any:
11        - resources:
12            kinds:
13            - Deployment
14            - StatefulSet
15            - DaemonSet
16      validate:
17        message: "The label `app` is required."
18        pattern:
19          spec:
20            template:
21              metadata:
22                labels:
23                  app: "?*"

In order to treat special characters like wildcards as literals, see this section in the JMESPath page.

Operators

Operators in the following support list values as of Kyverno 1.3.6 in addition to scalar values. Many of these operators also support checking of durations (ex., 12h) and semver (ex., 1.4.1).

OperatorMeaning
>greater than
<less than
>=greater than or equals to
<=less than or equals to
!not equals
|logical or
&logical and
-within a range
!-outside a range

An example of using an operator in a pattern style validation rule is shown below.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: validate
 5spec:
 6  validationFailureAction: enforce
 7  rules:
 8    - name: validate-replica-count
 9      match:
10        any:
11        - resources:
12            kinds:
13            - Deployment
14      validate:
15        message: "Replica count for a Deployment must be greater than or equal to 2."
16        pattern:
17          spec:
18            replicas: ">=2"

Anchors

Anchors allow conditional processing (i.e. “if-then-else”) and other logical checks in validation patterns. The following types of anchors are supported:

AnchorTagBehavior
Conditional()If tag with the given value (including child elements) is specified, then peer elements will be processed.
e.g. If image has tag latest then imagePullPolicy cannot be IfNotPresent.
    (image): “*:latest”
    imagePullPolicy: “!IfNotPresent”
Equality=()If tag is specified, then processing continues. For tags with scalar values, the value must match. For tags with child elements, the child element is further evaluated as a validation pattern.
e.g. If hostPath is defined then the path cannot be /var/lib
    =(hostPath):
        path: “!/var/lib”
Existence^()Works on the list/array type only. If at least one element in the list satisfies the pattern. In contrast, a conditional anchor would validate that all elements in the list match the pattern.
e.g. At least one container with image nginx:latest must exist.
    ^(containers):
    - image: nginx:latest
NegationX()The tag cannot be specified. The value of the tag is not evaluated (use exclamation point to negate a value). The value should ideally be set to "null" (quotes surrounding null).
e.g. Hostpath tag cannot be defined.
    X(hostPath): “null”
Global<()The content of this condition, if false, will cause the entire rule to be skipped. Valid for both validate and strategic merge patches.

Anchors and child elements: Conditional and Equality

Child elements are handled differently for conditional and equality anchors.

For conditional anchors, the child element is considered to be part of the “if” clause, and all peer elements are considered to be part of the “then” clause. For example, consider the following ClusterPolicy pattern statement:

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: conditional-anchor-dockersock
 5spec:
 6  validationFailureAction: enforce
 7  background: false
 8  rules:
 9  - name: conditional-anchor-dockersock
10    match:
11      any:
12      - resources:
13          kinds:
14          - Pod
15    validate:
16      message: "If a hostPath volume exists and is set to `/var/run/docker.sock`, the label `allow-docker` must equal `true`."
17      pattern:
18        metadata:
19          labels:
20            allow-docker: "true"
21        (spec):
22          (volumes):
23          - (hostPath):
24              path: "/var/run/docker.sock"

This reads as “If a hostPath volume exists and the path equals /var/run/docker.sock, then a label “allow-docker” must be specified with a value of true.” In this case, the conditional checks the spec.volumes and spec.volumes.hostPath map. The child element of spec.volumes.hostPath is the path key and so the check ends the “If” evaluation at path. The entire metadata object is a peer element to the spec object because these reside at the same hierarchy within a Pod definition. Therefore, conditional anchors can not only compare peers when they are simple key/value, but also when peers are objects or YAML maps.

For equality anchors, a child element is considered to be part of the “then” clause. Now, consider the same ClusterPolicy as above but using equality anchors:

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: equality-anchor-no-dockersock
 5spec:
 6  validationFailureAction: enforce
 7  background: false
 8  rules:
 9  - name: equality-anchor-no-dockersock
10    match:
11      any:
12      - resources:
13          kinds:
14          - Pod
15    validate:
16      message: "If a hostPath volume exists, it must not be set to `/var/run/docker.sock`."
17      pattern:
18        =(spec):
19          =(volumes):
20          - =(hostPath):
21              path: "!/var/run/docker.sock"

This is read as “If a hostPath volume exists, then the path must not be equal to /var/run/docker.sock”. In this sample, the object spec.volumes.hostPath is being checked, which is where the “If” evaluation ends. Similar to the conditional example above, the path key is a child to hostPath and therefore is the one being evaluated under the “then” check.

Existence anchor: At Least One

The existence anchor is used to check that, in a list of elements, at least one element exists that matches the pattern. This is done by using the ^() notation for the field. The existence anchor only works on array/list type data.

For example, this pattern will check that at least one container is using an image named nginx:latest:

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: existence-anchor-at-least-one-nginx
 5spec:
 6  validationFailureAction: enforce
 7  rules:
 8  - name: existence-anchor-at-least-one-nginx
 9    match:
10      any:
11      - resources:
12          kinds:
13          - Pod
14    validate:
15      message: "At least one container must use the image `nginx:latest`."
16      pattern:
17        spec:
18          ^(containers):
19            - image: nginx:latest

Contrast this existence anchor, which checks for at least one instance, with a wildcard which checks for every instance.

1      pattern:
2        spec:
3          containers:
4          - name: "*"
5            image: nginx:latest

This snippet above instead states that every entry in the array of containers, regardless of name, must have the image set to nginx:latest.

Global Anchor

The global anchor, new in Kyverno 1.4.3, is a way to use a condition anywhere in a resource to base a decision. If the condition enclosed in the global anchor is true, the rest of the rule must apply. If the condition enclosed in the global anchor is false, the rule is skipped.

In this example, a container image coming from a registry called corp.reg.com is required to mount an imagePullSecret called my-registry-secret.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: sample
 5spec:
 6  validationFailureAction: enforce
 7  rules:
 8  - name: check-container-image
 9    match:
10      any:
11      - resources:
12          kinds:
13          - Pod
14    validate:
15      message: Images coming from corp.reg.com must use the correct imagePullSecret.
16      pattern:
17        spec:
18          containers:
19          - name: "*"
20            <(image): "corp.reg.com/*"
21          imagePullSecrets:
22          - name: my-registry-secret

The below Pod has a single container which meets the global anchor’s specifications, but the rest of the pattern does not match. The Pod is therefore blocked.

 1apiVersion: v1
 2kind: Pod
 3metadata:
 4  name: static-web
 5  labels:
 6    role: myrole
 7spec:
 8  containers:
 9    - name: web
10      image: corp.reg.com/nginx
11      ports:
12        - name: web
13          containerPort: 80
14          protocol: TCP
15  imagePullSecrets:
16  - name: other-secret

anyPattern

In some cases, content can be defined at different levels. For example, a security context can be defined at the Pod or Container level. The validation rule should pass if either one of the conditions is met.

The anyPattern tag is a logical OR across multiple validation patterns and can be used to check if any one of the patterns in the list match.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: require-run-as-non-root
 5spec:
 6  background: true
 7  validationFailureAction: enforce
 8  rules:
 9  - name: check-containers
10    match:
11      any:
12      - resources:
13          kinds:
14          - Pod
15    validate:
16      message: >-
17        Running as root is not allowed. The fields spec.securityContext.runAsNonRoot,
18        spec.containers[*].securityContext.runAsNonRoot, and
19        spec.initContainers[*].securityContext.runAsNonRoot must be `true`.                
20      anyPattern:
21      # spec.securityContext.runAsNonRoot must be set to true. If containers and/or initContainers exist which declare a securityContext field, those must have runAsNonRoot also set to true.
22      - spec:
23          securityContext:
24            runAsNonRoot: true
25          containers:
26          - =(securityContext):
27              =(runAsNonRoot): true
28          =(initContainers):
29          - =(securityContext):
30              =(runAsNonRoot): true
31      # All containers and initContainers must define (not optional) runAsNonRoot=true.
32      - spec:
33          containers:
34          - securityContext:
35              runAsNonRoot: true
36          =(initContainers):
37          - securityContext:
38              runAsNonRoot: true

The anyPattern method is best suited for validation cases which do not use a negated condition. In the above example, only one of the spec contents must be valid. The same is true of negated conditions, however in the below example, this is slightly more difficult to reason about in that when negated, the anyPattern option allows any resource to pass so long as it doesn’t have at least one of the patterns.

1validate:
2  message: Cannot use Flux v1 annotation.
3  anyPattern:
4  - metadata:
5      =(annotations):
6        X(fluxcd.io/*): "*?"
7  - metadata:
8      =(annotations):
9        X(flux.weave.works/*): "*?"

If the desire is to state, “neither annotation named fluxcd.io/ nor flux.weave.works/ may be present”, then this would need two separate rules to express as including either one would mean the other is valid and therefore the resource is allowed.

Deny rules

In addition to applying patterns to check resources, a validation rule can deny a request based on a set of conditions written as expressions. A deny condition, unlike a pattern overlay, is constructed of key, operator, and value combination and is useful for applying fine-grained access controls that cannot otherwise be performed using native Kubernetes RBAC, or when wanting to explicitly deny requests based upon operations performed against existing objects.

You can use match and exclude to select when the rule should be applied and then use additional conditions in the deny declaration to apply fine-grained controls.

Also see using Preconditions for matching rules based on variables. deny statements can similarly use any and all blocks like those available to preconditions.

In addition to admission review request data, user information, and built-in variables, deny rules and preconditions can also operate on ConfigMap data, data from API server lookups, etc.

Deny DELETE requests based on labels

This policy denies delete requests for objects with the label app.kubernetes.io/managed-by: kyverno and for all users who do not have the cluster-admin role.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: deny-deletes
 5spec:
 6  validationFailureAction: enforce
 7  background: false
 8  rules:
 9  - name: block-deletes-for-kyverno-resources
10    match:
11      any:
12      - resources:
13          selector:
14            matchLabels:
15              app.kubernetes.io/managed-by: kyverno
16    exclude:
17      any:
18      - clusterRoles:
19        - cluster-admin
20    validate:
21      message: "Deleting {{request.oldObject.kind}}/{{request.oldObject.metadata.name}} is not allowed"
22      deny:
23        conditions:
24          any:
25          - key: "{{request.operation}}"
26            operator: Equals
27            value: DELETE

Block changes to a custom resource

This policy denies admission review requests for updates or deletes to a custom resource, unless the request is from a specific ServiceAccount or matches specified Roles.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: block-updates-to-custom-resource
 5spec:
 6  validationFailureAction: enforce
 7  background: false
 8  rules:
 9  - name: block-updates-to-custom-resource
10    match:
11      any:
12      - resources:
13          kinds:
14          - SomeCustomResource
15    exclude:
16      any:
17      - subjects:
18        - kind: ServiceAccount
19          name: custom-controller
20      - clusterRoles:
21        - custom-controller:*
22        - cluster-admin
23    validate:
24      message: "Modifying or deleting this custom resource is forbidden."
25      deny: {}

Prevent changing NetworkPolicy resources

This policy prevents users from changing NetworkPolicy resources with names that end with -default.

 1apiVersion: kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: deny-netpol-changes
 5spec:
 6  validationFailureAction: enforce
 7  background: false
 8  rules:
 9  - name: deny-netpol-changes
10    match:
11      any:
12      - resources:
13          kinds:
14          - NetworkPolicy
15          name: "*-default"
16    exclude:
17      any:
18      - clusterRoles:
19        - cluster-admin
20    validate:
21      message: "Changing default network policies is not allowed."
22      deny: {}

foreach

The foreach declaration simplifies validation of sub-elements in resource declarations, for example containers in a Pod.

A foreach declaration can contain multiple entries to process different sub-elements e.g. one to process a list of containers and another to process the list of initContainers in a Pod.

Each foreach entry must contain a list attribute, written as a JMESPath expression without braces, that defines the sub-elements it processes. For example, iterating over the list of containers in a Pod is performed using this list declaration:

1list: request.object.spec.containers

When a foreach is processed, the Kyverno engine will evaluate list as a JMESPath expression to retrieve zero or more sub-elements for further processing. The value of the list field may also resolve to a simple array of strings, for example as defined in a context variable. The value of the list field should not be enclosed in braces even though it is a JMESPath expression.

A variable element is added to the processing context on each iteration. This allows referencing data in the element using element.<name> where name is the attribute name. For example, using the list request.object.spec.containers when the request.object is a Pod allows referencing the container image as element.image within a foreach.

The following child declarations are permitted in a foreach:

In addition, each foreach declaration can contain the following declarations:

  • Context: to add additional external data only available per loop iteration.
  • Preconditions: to control when a loop iteration is skipped
  • elementScope: controls whether to use the current list element as the scope for validation. Defaults to “true” if not specified.

Here is a complete example to enforce that all container images are from a trusted registry:

 1apiVersion : kyverno.io/v1
 2kind: ClusterPolicy
 3metadata:
 4  name: check-images
 5spec:
 6  validationFailureAction: enforce
 7  background: false
 8  rules:
 9  - name: check-registry
10    match:
11      any:
12      - resources:
13          kinds:
14          - Pod
15    preconditions:
16      any:
17      - key: "{{request.operation}}"
18        operator: NotEquals
19        value: DELETE
20    validate:
21      message: "unknown registry"  
22      foreach:
23      - list: "request.object.spec.initContainers"
24        pattern:
25          image: "trusted-registry.io/*"      
26      - list: "request.object.spec.containers"
27        pattern:
28          image: "trusted-registry.io/*"

Note that the pattern is applied to the element and hence does not need to specify spec.containers and can directly reference the attributes of the element, which is a container in the example above.

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