TL;DR
A new version of CEL Playground has been released, now with extra Kubernetes spice! Try out the new CEL modes, and test the Kubernetes ValidatingAdmissionPolicy and WebHook support within focussed environments. Read on for an in-depth introduction to the new features, including walkthrough examples demonstrating the initial Kubernetes support.
Background
The Common Expression Language (CEL) is an open source, non-Turing complete expression language created by Google. CEL is gaining adoption in many communities, including within Kubernetes where it is being used to replace older, heavy weight mechanisms with light weight solutions which can be embedded and executed inline.
CEL Playground is a browser based environment born out of a necessity, the need to have a safe space in which to learn how the Common Expression Language can be used.
Introduction
It is nearly a year since Matheus Faria began searching for a solution to help users learn, experiment with and test CEL expressions. His interest was driven by his work in the marvin open source project, a CLI tool which identifies issues and misconfigurations through the evaluation of CEL expressions against a cluster’s resources, as well as the increasing adoption of CEL within various Kubernetes components.
Matheus looked around to see what was already available, asking others in the Kubernetes community how they were working with CEL expressions, but was unable to find anything satisfying this need. What he did discover was a great deal of community interest in having something fill this gap.
This interest sparked the creation of CEL Playground, and in June 2023 development began. The project quickly took shape over the next month and we soon had a browser based playground where we could learn, experiment with and test CEL expressions.
The project was presented to various communities, including Kubernetes API Machinery and Auth Special Interest Groups, as well as OpenShift Commons, and we sought feedback for how to improve and further develop the playground.
The most popular request by far was to extend the playground with better support for Kubernetes use cases, especially ValidatingAdmissionPolicy and Webhooks.
The project has also been mentioned in a number of KubeCon and Cloud Native Rejekts talks
CEL Playground: the Next Generation
We are very excited to announce the next major release of CEL Playground, introducing many features in support of the Kubernetes use cases. This release focuses primarily on Validating Admission Policy and Admission Webhook Kubernetes use cases, but also introduces improvements in the interface to support the ability to switch modes depending on which use case is desired.
Support for CEL Expression Modes
The updated version of the CEL Playground interface now includes the ability to switch to a different mode depending on your use case. The mode can be changed by selecting the Modes button in the top left of the user interface
This will display a dialog box allowing you to choose your expression evaluation mode
The currently supported modes are:
CEL Expression
This mode supports the original CEL evaluation capabilities, allowing you to execute a CEL expression with a specified set of input values.
Validating Admission Policy
This mode provides support for evaluating Validating Admission Policies, evaluating the CEL expressions within an instance of the ValidatingAdmissionPolicy Resource, and supporting the following contextual input variables
object - The object from the incoming request. The value should be empty for DELETE requests.
oldObject - The existing object. The value should be empty for CREATE requests.
request - Attributes of the API request.
namespaceObject - The namespace object that the incoming object belongs to. The value should be empty for cluster-scoped resources.
authorizer - A mock CEL Authorizer. This can be used to perform authorization checks for the request principal (user or service account).
authorizer.requestResource - The CEL ResourceCheck representing the request resource.
The integration does not currently support parameters, however this is something that will be incorporated in a future release.
Web Hooks
This mode provides support for evaluating admission webhook matchConditions, evaluating the CEL expressions within an instance of the ValidatingWebhookConfiguration or MutatingWebhookConfiguration Resources. This integration supports most of the contextual input variables mentioned in the previous ValidatingAdmissionPolicy section, with the exception of namespaceObject and variables which do not apply.
Time for a Deep Dive:
It is now time to dive into the details of what is currently possible within these new modes, strap in and let’s get going!
Contextual Input Variables
The new modes make use of contextualized input variables, these are located in the top right area of the user interface. The contextualized input variables represent variables which are made available by the specific Kubernetes integrations for evaluating their associated CEL expressions.
Object and Old Object
The Object and Old Object contextual input variables, referenced within CEL expressions through the names object and oldObject, represent the Kubernetes resource associated with the incoming request, with their content related to the operation being performed.
For CREATE operations the Object variable will contain the new value of the resource being applied through the operation, with the Old Object variable being empty.
For UPDATE operations the Old Object variable will contain the current value of the resource, with the Object variable containing the new version being applied through the update operation.
For DELETE operations the Old Object variable will contain the current value of the resource, with the Object variable being empty.
For UPDATE operations the values in Object and Old Object should be consistent, referencing the same resource type and name. As an example, if the policy is being applied to apps/v1/Deployment then both contextual input variables should be of that type.
The Object and Old Object contextual input variables are used in both ValidatingAdmissionPolicy and WebHook modes.
Namespace
The Namespace contextual input variable, referenced within CEL Expressions through the name namespaceObject, contains the Kubernetes namespace resource associated with the resource referenced in Object and Old Object.
The Namespace contextual input variable is used within the ValidatingAdmissionPolicy mode, however it is not required for the Webhook mode.
Request
The Request contextual input variable, referenced within CEL Expressions through the name request, contains the AdmissionRequest part of the admission.k8s.io/v1/AdmissionReview resource, with the exception of the object and oldObject fields which are available separately.
The AdmissionRequest contains information related to
The unique identifier of the request
The incoming object Kind
The target resource and subresource for the operation
The original kind/resource/subresource if the information was converted
The name and namespace of the resource
The operation type
Information about the user making the request
Whether this is a dry-run request
Option information associated with the operation
For more information about the structure of this resource, please refer to the Kubernetes documentation
The Request contextual input variable is used in both ValidatingAdmissionPolicy and WebHook modes.
Authorizer
The Authorizer contextual input variable, referenced within CEL expressions through the name authorizer, provides information that can be used to mock the behaviour of a Kubernetes authorizer. This behaviour includes the creation of decisions based on checks for a specific path, a specific resource and using a specific service account. The decision will provide information on whether the specific check was allowed, the reason for the response or information about any error which may have occurred.
The Authorizer contextual input variable is used in both ValidatingAdmissionPolicy and WebHook modes.
Path Checks
Path checks allow the CEL expression to check the authorization decision for accessing a specific path and a specific HTTP verb
authorizer.path(‘/health’).check(‘get”)
will return a decision based on whether the current Principal can execute a GET operation on the path /health
Resource Checks
Resource checks allow the CEL expressions to check an authorization decision for accessing a specific type of resource. These checks can be further constrained to check for access to a specific subresource, specific namespace, specific name or a combination.
The check is performed for a specific operation on the resourced, for example:
authorizer.group(‘apps’).resource(‘deployment’).check(‘create’)
will return a decision based on whether the current Principal can create a deployment resource
authorizer.group(‘apps’).resource(‘deployment’).subresource(‘scale’). check(‘update’)
will return a decision based on whether the current Principal can create the scale subresource for a deployment
authorizer.group(‘apps’).resource(‘deployment’).namespace(‘default’). check(create’)
will return a decision based on whether the current Principal can create a deployment resource within the default namespace
authorizer.group(‘apps’).resource(‘deployment’).name(‘myresource’). check(‘update’)
will return a decision based on whether the current Principal can update a deployment resource named myresource
Note: subresource, namespace and name can be used in any combination before invoking the check method
Service Accounts
Service Account checks allow the CEL expression to check whether a specific service account is permitted to access paths or resources, this is achieved by specifying the service account information prior to the relevant path or resource check.
authorizer.serviceAccount('default', 'myserviceaccount').group(‘apps’). resource(‘deployment’).check(‘create’)
will return a decision based on whether the service account named myserviceaccount within the default namespace can create a deployment resource
authorizer.serviceAccount('default', 'myserviceaccount').path(‘/health’). check(‘get”)
will return a decision based on whether the service account named myserviceaccount within the default namespace can execute a GET operation on the path /health.
Decisions
A decision will be returned from the check calls in the previous sections, this decision can be tested to see whether the check was successful. There are four methods which can be invoked on the decision, these are:
authorizer.group(‘apps’).resource(‘deployment’).check(‘create’).allowed()
will return true if the current Principal can create a deployment resource
authorizer.group(‘apps’).resource(‘deployment’).check(‘create’).reason()
will return a string explaining the decision for the check being requested
authorizer.group(‘apps’).resource(‘deployment’).check(‘create’).errored()
will return true if the check resulted in an error
authorizer.group(‘apps’).resource(‘deployment’).check(‘create’).error()
will return a string explaining the error if one occurred
Authorizer Contextual Input Format
The format of the Authorizer contextual input is specific to CEL Playground, and mimics the information and method invocations supported by the authorizer in reaching a decision. The value must follow this structure
Note: Angle brackets are used to represent names for specific items, which can also be repeated as long as the names remain distinct. The <namespace> and <name> values within the checks sections can also use an empty string to indicate they are not required.
Examples
Now that we’ve covered the input contextual variables we can take a look at some specific examples.
Validating Admission Policy Examples
CEL Playground supports the evaluation of CEL expressions within the following ValidatingAdmissionPolicy sections
match conditions
variables
validations and their message expressions
audit annotations
We will now look in more detail at some examples of these sections, head over to CEL Playground and choose the ValidatingAdmissionPolicy mode
Match Conditions
Match Conditions determine whether the ValidatingAdmissionPolicy applies to a particular request, their rules are as follows
If any matchCondition evaluates to false the policy will be skipped
If all matchConditions evaluate to true the policy will be evaluated
If matchCondition evaluates to an error then the outcome is determined by the failurePolicy
Note: at this time we do not check the failurePolicy when errors occur, we will return information about the error and base the decision on the other matchCondition results
We will now take a look at an example of the Match Conditions in action, choose the Match Conditions option from the example list to begin
This example contains two matchConditions and one validation, the matchConditions are
exclude-leases
This matchCondition will evaluate to true if the request.resource.group is not “coordination.k8s.io” or the request.resource.resource is not “leases”
exclude-kubelet-requests
This matchCondition will evaluate to true if request.userInfo.groups does not contain the “system:nodes” group
We will now run the evaluation and see what happens. If we expand each of the sections within the output, we get the following results
These results show that both matchConditions evaluated to true, and as a result the policy is applied to the request and the validation rules are also evaluated.
We can see the cost of each evaluation, as returned by the CEL library, and the total cost of evaluating all the expressions.
Let us now cause one of the matchConditions to fail and see what happens. In order to do this we will cause the exclude-kubelet-requests matchCondition to evaluate to false, which is achieved by adding the “system:nodes” group to the request’s user information.
Choose the Request conditional input
Scroll down to the userInfo section
Add system:nodes to the groups list
If we now run the evaluation we should get the following results
As we can see the exclude-kubelet-requests matchCondition now evaluates to false and the policy validations are no longer evaluated.
Variables
Variables can be defined within a ValidatingAdmissionPolicy, these allow expressions to be evaluated on demand and reused within other expressions.
We will now take a look at an example of the variables in action, choose the Variables in Validation option from the example list to begin
This example contains four variables and one validation, the variables are
environment
If namespaceObject.metadata.labels contains a label named “environment” then the variable will take the value of that label, if that label does not exist then the variable will default to “prod”
exempt
This variable will be true if object.metadata.labels contains a label named “exempt” and the value of that label is “true”
containers
This variables will contain a list of the containers which are specific within object.spec.template.spec.containers
containersToCheck
This variable will contain a list of all containers which include “example.com/” within the image name. Note the expression for this variable makes use of the preceding containers variable
We will now run the evaluation and see what happens. If we expand each of the sections within the output, we get the following results
And
We can see the evaluation of all the variables, their respective costs and the evaluation of the validation expression.
We will now demonstrate the on demand nature of the variable evaluation.
If we look at the expression used within the validation we can see that it first references the exempt variable, then uses the containersToCheck variable, which uses the container variable, to check if all the image names start with the value of the environment variable followed by a period. We can also determine that if the exempt variable returns true, then CEL will not evaluate the right hand side of the logical or (||) expression, since that can only return true.
Let us now cause the exclude variable to evaluate to true and see what happens, we achieve this by setting an exempt label within the Object
Choose the Object conditional input
Scroll down to the label section and find the exempt label
Set the value of the label to true
If we now run the evaluation we should get the following results
As we can see, only the exempt variable is now being evaluated as that is sufficient to determine the value of the validation expression. As the other variables are never access their expressions will not be evaluated.
Validations and their Message Expressions
Validations have the option of returning a meaningful message to the client which issues the request if the validation fails. To demonstrate this behaviour we are going to use the same Variables in Validation, please select it once more to reset the example to its default values.
From the previous section we can see that the validation will return true if the exempt variable returns true or if all the containers within the containersToCheck variable have an image name which starts with the value of the environment variable followed by a period. The value of the environment variable is derived from the labels within namespaceObject, which currently evaluates to “prod”.
In order to cause the validation to fail, we need to ensure that the image name begins with the name of a different environment. In this example we will choose “demo”.
Choose the Object conditional input
Scroll down to the containers section and find the image name
Change the image name so it begins with demo rather than prod
If we now run the evaluation we should get the following results
And
If we look at the validation response we can see that it is now returning a message to the caller, this provides the caller with a clearer description of why the validation has failed. This message is derived from an expression, and in this example the expression references the environment variable, prod, and the namespaceObject.metadata.name to generate the message.
Audit Annotations
When a ValidatingAdmissionPolicy allows an operation to proceed, i.e. when the validations are successful, Kubernetes will generate an audit event for the operation. The ValidatingAdmissionPolicy has the ability to augment the audit event and include annotations which provide more contextual information within the audit trail.
We will now take a look at an example of the audit annotations in action, choose the Audit Annotations option from the example list to begin
If we run the evaluation we should get the following results
We can see from these results that the validation succeeded and that we are generating an audit annotation with information derived from the request, which in this example makes use of the value of object.spec.replicas.
We will now take a look at the behaviour when the validation fails. We can see from the validation expression that it expects the replica count to be greater than 50, to cause the validation to fail we will make the following changes
Choose the Object conditional input
Scroll down to thespec section and find the replicas count
Change the count to 3
If we now run the evaluation we should get the following results
From the validation response we can see that the validation failed and returned a message describing the failure, we can also see that no audit annotation was evaluated.
Webhook Examples
CEL Playground supports the evaluation of CEL expressions within the match condition sections of ValidatingWebHookConfigurations and MutatingWebhookConditions resources.
We will now look in more detail at matchConditions, head over to CEL Playground and choose the Web Hooks mode.
Match Conditions determine whether the webhooks will be invoked for a particular request, the rules are similar to those used within the ValidatingAdmissionPolicy. If the matchConditions evaluate to true then the Kubernetes API server will pass the request across to the webhook for further processing, otherwise the webhook will not be invoked.
Note: at this time we do not check the failurePolicy when errors occur, we will return information about the error and the results of the other matchConditions.
We will now take a look at an example of the Match Conditions in action, choose the Authorizer Ignore breakglass option from the example list to begin
This example contains a single matchCondition, “breakglass”. The matchCondition will evaluate to true if the authorizer determines the current Principal does not have the breakglass permission on the ValidatingWebhookCondition named "rbac.my-webhook.example.com". If the matchCondition evaluates to true, the webhook service will be invoked by the Kubernetes API server.
We will now run the evaluation and see what happens. If we expand each of the sections within the output, we get the following results
In this example the matchCondition did not match, because the current Principal does have the breakglass permission, and the webhook will not be invoked.
Let us now modify the Authorizer input variable information so that the matchCondition evaluates to true, resulting in the webhook being invoked by the Kubernetes API server. We will make the following changes
Choose the Authorizer conditional input
Scroll down to the breakglass verb within the named resources
Change the decision to deny
Note: the decision is currently checked to see if it matches the value “allow”, so while we do expect the mocked authorizer to explicitly use allow and deny in the future it is possible to use other values to deny the permission.
If we now run the evaluation we should get the following results
We can now see that the matchCondition evaluates to true, and would therefore expect the Kubernetes API server to invoke the webhook for further processing.
Help Wanted
The introduction of the new CEL Playground capabilities has taken a lot of effort, however we are far from complete. There are still additional Kubernetes use cases to be considered and many improvements that can be made to the existing modes. These tasks include
Determine how to implement parameters resources for evaluating the ValidatingAdmissionPolicy expressions
Introduce structured editors for the Contextual Input variables
Introduce more error checking to match the Kubernetes behaviour
There are many other ways to improve CEL Playground, in addition to the above, and feedback from the community is a crucial part of that process. Please try out the new capabilities and leave us feedback, this feedback can include requests for improvements, additional features you would like to see implemented or anything else you can think of.
We would be very excited to see new contributors to the project, if you are interested in helping with the development effort please let us know.
If you want more information, or would like to get involved, please
Check out our GitHub repository
Check out our slack channel
Join our Community Calls calls, they are open to all
And Finally
We would like to express our immense gratitude to everyone who has helped with the development of CEL Playground and these new Kubernetes capabilities. We received fantastic feedback from those within the Kubernetes API Machinery SIG and Kubernetes Auth SIG, especially Jordan Liggitt and Cici Huang.
We would like to include your name within this list, so please join us and help to improve CEL Playground in whatever way you have time to do.
Further Information
The following links provide more information on these Kubernetes use cases
The following recordings may be of interest, they demonstrate, and show the growth of, CEL Playground
Presentation of CEL Playground to the Kubernetes API Machinery SIG
Presentation of CEL Playground to the Kubernetes Auth SIG
Presentation of CEL and CEL Playground’s early Kubernetes support to OpenShift Commons