kyverno / policies

Kyverno policies for security and best practices
Apache License 2.0
319 stars 229 forks source link

[Enhancement] Update CEL policies to make use of optionals and variables to remove redundant expressions #1058

Closed Chandan-DK closed 3 weeks ago

Chandan-DK commented 2 months ago

Problem Statement

There are redundant expressions in CEL policies that are present in the library. For example, here container.securityContext has been repeated multiple times:

object.spec.containers.all(container, has(container.securityContext) &&
has(container.securityContext.allowPrivilegeEscalation) &&
container.securityContext.allowPrivilegeEscalation == false)

Here, we can remove redundant expressions by using optionals:

object.spec.containers.all(container, container.?securityContext.?allowPrivilegeEscalation.orValue(true) == false)

CEL Playground for the above expressions.

Also, we can optimize some CEL policies by using variables. Example

Solution Description

Update the following CEL policies to make use of optionals and variables to remove redundant expressions.

Note: The below checklist is not exhaustive and more policies will be added here.

pod-security-cel

Other Comments

No response

Slack discussion

No response

Troubleshooting

Chandan-DK commented 2 months ago

cc @JimBugwadia @MariamFahmy98

JimBugwadia commented 1 month ago

So, using these techniques we can simply a policy like this:

https://kyverno.io/policies/pod-security-cel/restricted/require-run-as-nonroot/require-run-as-nonroot/

      validate:
        cel:
          expressions:
            - expression: >-
                (
                    (
                      has(object.spec.securityContext) &&
                      has(object.spec.securityContext.runAsNonRoot) &&
                      object.spec.securityContext.runAsNonRoot == true
                    ) && (
                      (
                          object.spec.containers +
                          (has(object.spec.initContainers) ? object.spec.initContainers : []) +
                          (has(object.spec.ephemeralContainers) ? object.spec.ephemeralContainers : [])
                      ).all(container,
                          !has(container.securityContext) ||
                          !has(container.securityContext.runAsNonRoot) ||
                          container.securityContext.runAsNonRoot == true)
                    )
                ) || (
                    (
                        object.spec.containers +
                        (has(object.spec.initContainers) ? object.spec.initContainers : []) +
                        (has(object.spec.ephemeralContainers) ? object.spec.ephemeralContainers : [])
                    ).all(container,
                        has(container.securityContext) &&
                        has(container.securityContext.runAsNonRoot) &&
                        container.securityContext.runAsNonRoot == true)
                )  

to:

      validate:
        cel:
          variables:
          - name: ctnrs
            expression: >-
              object.spec.containers +
              (has(object.spec.initContainers) ? object.spec.initContainers : []) +
              (has(object.spec.ephemeralContainers) ? object.spec.ephemeralContainers : [])
          expressions:
            - expression: >-
                (object.spec.?securityContext.?runAsNonRoot.orValue(false) == true
                  && variables.ctnrs.all(c, c.?securityContext.?runAsNonRoot.orValue(true) == true))
                  || variables.ctnrs.all(c, c.?securityContext.?runAsNonRoot.orValue(false) == true)
Chandan-DK commented 1 month ago

Here are some more examples of simplifying CEL expressions in policies using optionals:

Example 1

has(object.metadata.annotations) && 'pod.kubernetes.io/lifetime' in object.metadata.annotations

Can be written as

object.metadata.?annotations[?'pod.kubernetes.io/lifetime'].orValue(false)

Playground


Example 2

(object.spec.containers + (has(object.spec.initContainers) ? object.spec.initContainers : []) + (has(object.spec.ephemeralContainers) ? object.spec.ephemeralContainers : []))

Can be written as:

object.spec.containers + object.spec.?initContainers.orValue([]) + object.spec.?ephemeralContainers.orValue([])

Playground


Example 3

has(object.spec.template.metadata) && has(object.spec.template.metadata.labels) && 'foo' in object.spec.template.metadata.labels

Can be written as:

object.spec.template.?metadata.?labels.?foo.hasValue()

Playground


Example 4

has(object.metadata.labels) && 'corp.org/version' in object.metadata.labels && object.metadata.labels['corp.org/version'].matches('^v[0-9].[0-9].[0-9]$')

Can be written as:

object.metadata.?labels[?'corp.org/version'].orValue('default').matches('^v[0-9].[0-9].[0-9]$') 

Playground


Example 5

!has(object.spec.volumes) || object.spec.volumes.all(volume, !has(volume.secret))

Can be written as:

object.spec.?volumes.orValue([]).all(volume, !has(volume.secret))

Playground


lavishpal commented 1 month ago

Hi @JimBugwadia , Could you assign this issue to me, it will be helpful if you provide guidance on how to get started?

JimBugwadia commented 1 month ago

Hi @lavishpal - lets keep this as tracking issue, as there are many policies to update.

Can you select one of the sub-tasks instead?

https://github.com/kyverno/policies/issues/1090 https://github.com/kyverno/policies/issues/1091 https://github.com/kyverno/policies/issues/1092 https://github.com/kyverno/policies/issues/1093 https://github.com/kyverno/policies/issues/1094 https://github.com/kyverno/policies/issues/1095 https://github.com/kyverno/policies/issues/1096 https://github.com/kyverno/policies/issues/1097 https://github.com/kyverno/policies/issues/1098 https://github.com/kyverno/policies/issues/1099 https://github.com/kyverno/policies/issues/1100

lavishpal commented 1 month ago

@Chandan-DK , @JimBugwadia Can I raise the pr for all .