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
  • EMAIL
  • 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 ExpressionPurpose
names.isSorted()Verify that a list of names is kept in alphabetical order
items.map(x, x.weight).sum() == 1.0Verify 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’) == 1Require 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 RulePurpose
self.minReplicas <= self.replicasValidate an integer field is less than or equal to another integer field
‘Available’ in self.stateCountsValidate 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 == oldSelfValidate that a required field is immutable once it is set
self.created + self.ttl < self.expiredValidate 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 RulePurpose
self == oldSelfFor 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 == oldSelfMake 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 >= oldSelfValidate 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.

Stay up to date