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.
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
- PKI →
-
A custom
string
error message orbool
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 CelProgram
-
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")`,
},
CelProgram
- A
CelProgram
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]interface{}{
"variables": []map[string]interface{}{
{
"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
orttl
, even though CEL roles are not required to follow the traditional role schema.