通用表达式语言 (CEL) 是一种通用表达式语言,旨在实现快速、可移植且安全地执行。您可以单独使用 CEL,也可以将其嵌入到更大的产品中。CEL 非常适合各种应用,从路由远程过程调用 (RPC) 到定义安全政策,都能胜任。CEL 具有可扩展性,独立于平台,并且针对一次编译/多次评估工作流进行了优化。
CEL 专门设计为可安全执行用户代码。虽然盲目对用户的 Python 代码调用 eval()
很危险,但您可以安全地执行用户的 CEL 代码。由于 CEL 会阻止导致性能下降的行为,因此它可以在纳秒或微秒内安全地进行评估。CEL 的速度和安全性使其成为对性能要求严苛的应用的理想选择。
CEL 会评估类似于单行函数或 lambda 表达式的表达式。虽然 CEL 通常用于布尔值决策,但您也可以使用它来构建更复杂的对象,例如 JSON 或协议缓冲区消息。
为什么选择 CEL?
许多服务和应用都会评估声明式配置。例如,基于角色的访问控制 (RBAC) 是一种声明性配置,可根据用户角色和一组用户生成访问权限决策。虽然声明性配置足以应对大多数情况,但有时您需要更强大的表达能力。这时,CEL 就派上用场了。
下面以使用 CEL 扩展声明性配置为例,介绍 Google Cloud Identity and Access Management (IAM) 的功能。虽然 RBAC 是常见情况,但 IAM 提供 CEL 表达式,允许用户根据请求或所访问资源的 proto 消息属性进一步限制基于角色的授权范围。通过数据模型描述此类条件会导致 API 表面复杂且难以使用。相反,将 CEL 与基于属性的访问权限控制 (ABAC) 搭配使用,可为 RBAC 提供富有表现力且强大的扩展功能。
CEL 的核心概念
在 CEL 中,表达式是针对环境编译的。编译步骤会生成 protocol buffer 格式的抽象语法树 (AST)。编译后的表达式会存储起来以供日后使用,从而尽可能加快评估速度。单个已编译的表达式可以针对许多不同的输入进行评估。
下面我们来详细了解一下其中的一些概念。
表达式
表达式由用户编写。表达式类似于单行函数正文或 lambda 表达式。声明输入的函数签名写在 CEL 表达式之外,并且可供 CEL 使用的函数库会自动导入。
例如,以下 CEL 表达式接受一个请求对象,并且该请求包含一个 claims
令牌。该表达式会返回一个布尔值,用于指示 claims
令牌是否仍然有效。
用于对声明令牌进行身份验证的 CEL 表达式示例
// Check whether a JSON Web Token has expired by inspecting the 'exp' claim.
//
// Args:
// claims - authentication claims.
// now - timestamp indicating the current system time.
// Returns: true if the token has expired.
//
timestamp(claims["exp"]) < now
虽然用户定义了 CEL 表达式,但服务和应用定义了该表达式的运行环境。
环境
环境由服务定义。嵌入 CEL 的服务和应用会声明表达式环境。环境是指可在 CEL 表达式中使用的变量和函数的集合。
例如,以下 textproto
代码使用来自 CEL 服务的 CompileRequest
消息声明了一个包含 request
和 now
变量的环境。
CEL 环境声明示例
# Format: $SOURCE_PATH/service.proto#CompileRequest
declarations {
name: "request"
ident {
type { message_type: "google.rpc.context.AttributeContext.Request" }
}
}
declarations {
name: "now"
ident {
type { we
ll_known: "TIMESTAMP" }
}
}
CEL 类型检查器使用基于 proto 的声明来确保表达式中的所有标识符和函数引用都已正确声明和使用。
表达式处理阶段
CEL 表达式的处理分为三个阶段:
- 解析
- 检查
- 评估
最常见的 CEL 使用模式是在配置时解析并检查表达式、存储 AST,然后在运行时反复检索和评估 AST。
CEL 处理阶段图示
CEL 使用 ANTLR 词法分析器和解析器语法从人类可读的表达式解析为 AST。解析阶段会生成基于 Proto 的 AST,其中 AST 中的每个 Expr
节点都包含一个整数 ID,该 ID 用于索引在解析和检查期间生成的元数据。解析期间生成的 syntax.proto
文件表示以字符串形式输入的表达式的抽象表示形式。
解析表达式后,系统会根据环境对其进行类型检查,以确保表达式中的所有变量和函数标识符都已声明并得到正确使用。类型检查器会生成一个 checked.proto
文件,其中包含可大幅提高评估效率的类型、变量和函数解析元数据。
最后,在解析并检查表达式后,系统会评估存储的 AST。
CEL 评估器需要以下三项内容:
- 任何自定义扩展程序的函数绑定
- 变量绑定
- 要评估的 AST
函数和变量绑定应与用于编译 AST 的绑定相匹配。这些输入中的任何一个都可以在多次评估中重复使用,例如在多组变量绑定中评估的 AST、针对多个 AST 使用的相同变量,或在进程的整个生命周期内使用的函数绑定(常见情况)。