Applying the DRY Principle to Kyverno Policies

Applying the DRY Principle to Kyverno Policies

DRY Principle 2

The Don’t Repeat Yourself (DRY) principle of software development advocates avoiding repetition of code that is likely to change. Replacing similar code with reusable abstractions makes software easier to maintain, and avoids bugs. 

In this post, I will show you a couple of ways to apply the DRY principle in Kyverno policies, which are written in YAML.

DRY Using Variables 

Kyverno policies can declare and reuse variables. Consider this policy that mutates various container types in a Pod, to add a memory request if one is not specified. The policy rule iterates over a list of containers, initContainers, and ephemeralContainers all of which have the same structure.

Since the policy rule uses a RFC 6902 JSON Patch, the path for the patch is dependent on the type of container. An initial implementation of the policy rule duplicates the patch for each container type:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
 name: add-resources
spec:
 background: false
 rules:
 - name: default-memory
   match:
     any:
     - resources:
         kinds:
         - Pod
         operations:
         - CREATE
   mutate:
     foreach:
     - list: request.object.spec.containers[]
       preconditions:
         all:
         - key: "{{ element.resources.requests.memory || `0` }}"
           operator: Equals
           value: 0
       patchesJson6902: |-
         - path: /spec/containers/{{elementIndex}}/resources/requests/memory
           op: add
           value: 50Mi
     - list: request.object.spec.initContainers[]
       preconditions:
         all:
         - key: "{{ element.resources.requests.memory || `0` }}"
           operator: Equals
           value: 0
       patchesJson6902: |-
         - path: /spec/initContainers/{{elementIndex}}/resources/requests/memory
           op: add
           value: 50Mi
     - list: request.object.spec.ephemeralContainers[]
       preconditions:
         all:
         - key: "{{ element.resources.requests.memory || `0` }}"
           operator: Equals
           value: 0 
       patchesJson6902: |-
         - path: /spec/ephemeralContainers/{{elementIndex}}/resources/requests/memory
           op: add
           value: 50Mi

While this policy works, the patch is duplicated three times, once for each container type. This duplication can be avoided by extracting the patch into a policy variable and then reusing the variable for each container type. Using JMESPath, the index and the container type, can be overridden for each type:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
 name: add-resources
spec:
 background: false
 rules:
 - name: default-memory
   match:
     any:
     - resources:
         kinds:
         - Pod
         operations:
         - CREATE
   context:
   - name: patch
     variable:
       value: |-
         - path: /spec/containers/0/resources/requests/memory
           op: add
           value: 50Mi
   mutate:
     foreach:
     - list: request.object.spec.containers[]
       preconditions:
         all:
         - key: "{{ element.resources.requests.memory || `0` }}"
           operator: Equals
           value: 0
       patchesJson6902: "{{ patch | replace_all(@, '0', '{{elementIndex}}') }}"
     - list: request.object.spec.ephemeralContainers[]
       preconditions:
         all:
         - key: "{{ element.resources.requests.memory || `0` }}"
           operator: Equals
           value: 0 
       patchesJson6902: "{{ patch | replace_all(@,'0','{{elementIndex}}') | replace_all(@,'containers','ephemeralContainers')}}"
     - list: request.object.spec.initContainers[]
       preconditions:
         all:
         - key: "{{ element.resources.requests.memory || `0` }}"
           operator: Equals
           value: 0
       patchesJson6902: "{{ patch | replace_all(@,'0','{{elementIndex}}') | replace_all(@,'containers','initContainers')}}"

DRY Using YAML Anchors and Aliases

The other duplication in the policy rule, is the precondition check. YAML allows reuse using anchors and aliases. We can leverage this YAML feature to remove the duplicated preconditions in the rule logic for each container type:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
 name: add-resources
spec:
 background: false
 rules:
 - name: default-memory
   match:
     any:
     - resources:
         kinds:
         - Pod
         operations:
         - CREATE
   context:
   - name: patch
     variable:
       value: |-
         - path: /spec/containers/0/resources/requests/memory
           op: add
           value: 50Mi
   mutate:
     foreach:
     - list: request.object.spec.containers[]
       preconditions: &pre
         all:
         - key: "{{ element.resources.requests.memory || `0` }}"
           operator: Equals
           value: 0
       patchesJson6902: "{{ patch | replace_all(@, '0', '{{elementIndex}}') }}"
     - list: request.object.spec.ephemeralContainers[]
       preconditions: *pre
       patchesJson6902: "{{ patch | replace_all(@,'0','{{elementIndex}}') | replace_all(@,'containers','ephemeralContainers')}}"
     - list: request.object.spec.initContainers[]
       preconditions: *pre
       patchesJson6902: "{{ patch | replace_all(@,'0','{{elementIndex}}') | replace_all(@,'containers','initContainers')}}"

The &pre declares an anchor named pre that can be subsequently referenced using *pre. YAML anchors also allow overrides, when additional flexibility is required.

Conclusion

The DRY Principle of coding applies to Policy as Code. In this post we used two powerful techniques to reduce duplication of logic that may need to change. Kyverno variables can be used to extract, or “pull-up” common elements and declare them once. And, YAML anchors and aliases allow reusing declarations.

Other techniques to apply DRY can be to use Helm templates, Kustomize, or other IaC tools.

Nirmata is the creator and a maintainer of Kyverno. If you are using Kyverno, and need any assistance, we would love to hear from you!

 

What is Shift Down Security?
Rapid Mitigation of CVE-2023-2878 with Kyverno and Nirmata Control Hub
No Comments

Sorry, the comment form is closed at this time.