Skip to main content
Version: Development

CEL in OpenBao

The Common Expression Language (CEL) is used in PKI and JWT to declare validation rules and policies:

The main CEL Expression in a Celprogram is typically one line of code. This can be broken down into multiple CEL expressions by using CelVariable's. The following sections cover how to build a CelProgram in any part of OpenBao.

warning

CEL is an advanced topic for operators. All validation is taken on by the CEL program created by the operator; limited to no validation is performed by OpenBao above and beyond what the CEL program contains. It is up to the operator to craft CEL programs which adequately perform validation for the context.


Table of contents


Main expression

  • This is the main expression that determines whether a CEL program has accepted or rejected a request.

  • The main expression in the CelProgram should return the engine's specific output object on success, for instance:

    • PKI → ValidationOutput
    • JWT/OIDC → pb.Auth
  • A custom string error message or bool should be returned on failure. The error message can be constructed using a CEL expression. For instance:

    require_cn ? 'request should have a common_name' : (validate_ttl ? 'request has invalid TTL' : "Request Rejected")
  • Use a ternary operator when writing a main expression for clarity and to guarantee a value is always produced:

    cond ? SuccessObject : error string/bool

Variables in a CEL Program

  • A variable is a named expression that can be referred later in other variables and expressions.

  • They are useful if an expression is too long or has reusable parts.

  • The order of variables matters since they are added into the CEL environment in the order they are defined. A variable can reference only variables declared before it, not ones that come later.

Definition

// Name of the variable.
Name: string
// CEL expression for the variable
Expression: string

Example

{
"name": "small_ttl",
"expression": `has(request.ttl) && duration(request.ttl) < duration("4h")`,
},

CEL Program

  • A CEL role is made of 2 parts:
    • A list of CelVariable's. The CEL variables are declared in the CEL env so that they can be accessed by other variables as well as the main expression.

    • A main expression which determines whether the CEL program succeeds or fails.

Definition

// List of variables with explicit order (optional)
Variables: []CelVariable
// Required, the main CEL expression
Expression: string

Example

"cel_program": map[string]any{
"variables": []map[string]any{
{
"name": "validate_cn",
"expression": `has(request.common_name) && request.common_name == "example.com"`,
},
{
"name": "small_ttl",
"expression": `has(request.ttl) && duration(request.ttl) < duration("4h")`,
},
{
"name": "cn_value",
"expression": "request.common_name",
},
{
"name": "not_after",
"expression": "now + duration(request.ttl)",
},
{
"name": "cert",
"expression": `CertTemplate{
Subject: PKIX.Name{
CommonName: cn_value,
},
NotBefore: now,
NotAfter: not_after,
}`,
},
{
"name": "output",
"expression": `ValidationOutput{
template: cert,
generate_lease: small_ttl,
no_store: !small_ttl,
}`,
},
{
"name": "err",
"expression": "'Request should have common_name'",
},
},
"expression": "validate_cn ? output : err",
}

The Request Object

  • Every key/value pair in the request body is copied verbatim to the CEL request map. The CEL program (not the endpoint) decides which of these parameters to honour, ignore, or override.

  • To avoid breaking existing automation, consider re-using familiar field names such as common_name or ttl, even though CEL roles are not required to follow the traditional role schema.