Common Expression Language
What is Common Expression Language (CEL)?
Common Expression Language (CEL) is an expression language created by Google. This open-source, non-compete Turing language is used for validating data, defining, and implementing constraints. CEL is used by the Google Cloud Certificate Authority Service, Envoy, and Kubernetes. In Kubernetes specifically, CEL creates CRDs (Custom Resource Definitions).
Compared to C, C++, and Java, Common Expression Language is far more flexible, easy to understand, and hence usable for defining rules/constraints. Rego is another language that is similar to CEL. However, the two differ because the former has strict syntax for the list of comprehensions, and the latter uses optional CEL macros. In Rego, the comprehensions are performed in local functions, and in CEL, the expressions are used as inline within a rule.
CEL Syntax and Structure
CEL is most commonly used for Certificate Authority (CA) applications. While writing CEL code for a CA, certain security measures are taken:
- A flexible dialect is used for the CA pool policy issuance and certificate template.
- A customer dialect is used for IAM (Identity and Access Management).
Structure for CA Pool and Certificate Template
CEL expressions are used as part of certificate identity constraints. This is the case with creating a CA pool or a certificate template. The variables in these types of CEL expressions are as follows:
Subject: subject Subjective Alternative Name (SAN): subject_alt_names
Subject
All fields in a certificate’s subject domain name must be specified and validated. The variable used for doing the validation is type Subject and name subject. The structure of fields are:
Type Name
String common_name String country_code String organization String organizational_unit String locality String province String street_address String postal_code
Subjective Alternative Names (SANs)
The SANs comprise several fields which must be validated while specifying the certificate request.
Type Name
String value []Int32 oid Enum type
The values for the type SubjectAltName include the values:
- DNS
- URI
- IP_ADDRESS
- CUSTOM
How is CEL Used with Kubernetes
CEL can be used with Kubernetes in its API to create and manage validation rules, policies, and constraints. It is convenient for this use case because of its flexibility and is easily understandable. CEL expressions are usually short and can be embedded as inline components in the string fields of Kubernetes API resources.
In Kubernetes, CEL is best known for creating and managing Custom Resource Definition (CRD). CEL is preferable because it can easily be integrated into CRD schemas, and the constraints can be expressed in a rich and understandable manner. CEL also enables CRDs to be self-maintained and ensures that CRD schemas are checked ahead of time and are executed safely.
Expressions Examples
CEL Expression | Purpose |
names.isSorted() | Verify that a list of names is kept in alphabetical order |
items.map(x, x.weight).sum() == 1.0 | Verify that the “weights” of a list of objects sum to 1.0 |
lowPriorities.map(x, x.priority).max() < highPriorities.map(x, x.priority).min() | Verify that two sets of priorities do not overlap |
names.indexOf(‘should-be-first’) == 1 | Require that the first name in a list is a specific value |
Validation Rules
It is important to note that the rules used for the Validating Admission Policy were introduced as an alpha version in Kubernetes 1.26 and continue to be improved in subsequent versions. Once a validation policy is defined, admission requests are validated and the policy is then bound to the appropriate resources. Validation rules enable CRD authors to define how custom objects function. A few examples are given below:
Validation Rule | Purpose |
self.minReplicas <= self.replicas | Validate an integer field is less than or equal to another integer field |
‘Available’ in self.stateCounts | Validate an entry with the ‘Available’ key exists in a map |
self.set1.all(e, !(e in self.set2)) | Validate that the elements of two sets are disjoint |
self == oldSelf | Validate that a required field is immutable once it is set |
self.created + self.ttl < self.expired | Validate that ‘expired’ date is after a ‘create’ date plus a ‘ttl’ duration |
Transition Rules
Transition rules are used for comparing the old states and new states of resources specified in the validation rules. They ensure invalid state transitions are not in the cluster’s API server.
Transition Rule | Purpose |
self == oldSelf | For a required field, make that field immutable once it is set. For an optional field, only allow transitioning from unset to set, or from set to unset. |
(on parent of field) has(self.field) == has(oldSelf.field)on field: self == oldSelf | Make a field immutable: validate that a field, even if optional, never changes after the resource is created (for a required field, the previous rule is simpler). |
self.all(x, x in oldSelf) | Only allow adding items to a field that represents a set (prevent removals). |
self >= oldSelf | Validate that a number is monotonically increasing. |
Example of How CEL is Used to Create a CRD
Save the below Custom Resource Definition to resourcedefinition.yaml
apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: # name must match the spec fields below, and be in the form: <plural>.<group> name: crontabs.stable.example.com spec: # group name to use for REST API: /apis/<group>/<version> group: stable.example.com # list of versions supported by this CustomResourceDefinition versions: - name: v1 # Each version can be enabled/disabled by Served flag. served: true # One and only one version must be marked as the storage version. storage: true schema: openAPIV3Schema: type: object properties: spec: type: object properties: cronSpec: type: string image: type: string replicas: type: integer # either Namespaced or Cluster scope: Namespaced names: # plural name to be used in the URL: /apis/<group>/<version>/<plural> plural: crontabs # singular name to be used as an alias on the CLI and for display singular: crontab # kind is normally the CamelCased singular type. Your resource manifests use this. kind: CronTab # shortNames allow shorter string to match your resource on the CLI shortNames: - ct
Create the CRD using the command:
kubectl apply -f resourcedefinition.yaml
A new RESTful API endpoint is created at a new namespaced:
/apis/stable.example.com/v1/namespaces/*/crontabs/...
The endpoint can be created in a few seconds, and custom objects of Kubernetes can be managed and created with this CRD. The objects will appear on CronTab
, and the CRD, once active, will show as Established
.