Common Expression Language(CEL)

Common Expression Language(CEL)は、高速で移植可能、かつ安全に実行できるように設計された汎用の式言語です。CEL は単独で使用することも、大規模なプロダクトに埋め込むこともできます。CEL は、リモート プロシージャ コール(RPC)のルーティングからセキュリティ ポリシーの定義まで、さまざまなアプリケーションに適しています。CEL は拡張可能でプラットフォームに依存せず、一度のコンパイル/評価のワークフロー向けに最適化されています。

CEL は、ユーザーコードを安全に実行できるように特別に設計されています。ユーザーの Python コードで eval() をやみくもに呼び出すことは危険ですが、ユーザーの CEL コードを安全に実行できます。また、CEL はパフォーマンスの低下につながる動作を防止するため、ナノ秒単位またはマイクロ秒単位で安全に評価されます。CEL の速度と安全性は、パフォーマンス重視のアプリケーションに最適です。

CEL では、単一行の関数またはラムダ式に類似した式が評価されます。CEL はブール値の決定によく使用されますが、JSON やプロトコル バッファ メッセージなどのより複雑なオブジェクトを作成することもできます。

CEL を選ぶ理由

多くのサービスやアプリケーションは、宣言型の構成を評価します。たとえば、ロールベースのアクセス制御(RBAC)は、ユーザーロールとユーザーのセットに応じてアクセス決定を生成する宣言型構成です。ほとんどの場合、宣言型の構成で十分ですが、より表現力が必要な場合もあります。ここで CEL が役に立ちます。

CEL を使用して宣言型構成を拡張する例として、Google Cloud の Identity and Access Management(IAM)の機能を考えてみましょう。RBAC は一般的なケースですが、IAM では CEL 式が提供されます。これにより、ユーザーはリクエストまたはアクセスされるリソースの proto メッセージ プロパティに従ってロールベースの付与のスコープをさらに制限できます。このような条件をデータモデルで記述すると、API サーフェスが複雑になり、操作が難しくなります。属性ベースのアクセス制御(ABAC)で CEL を使用すると、RBAC の表現力が高く強力な拡張機能になります。

CEL の基本コンセプト

CEL では、式は環境に対してコンパイルされます。コンパイルのステップでは、プロトコル バッファ形式で抽象構文ツリー(AST)が生成されます。コンパイルされた式は、可能な限り高速に評価を維持できるように、後で使用できるように保存されます。コンパイルされた 1 つの式をさまざまな入力で評価できます。

ここでは、これらの概念の一部を詳しく説明します。

式はユーザーが記述します。式は単一行の関数本体やラムダ式に似ています。入力を宣言する関数シグネチャは 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 { well_known: "TIMESTAMP" }
  }
}

CEL 型チェッカーは、プロトベースの宣言を使用して、式内のすべての識別子参照と関数の参照を正しく宣言して使用できるようにします。

式処理のフェーズ

CEL 式は 3 つのフェーズで処理されます。

  1. 解析
  2. チェック
  3. 評価する

CEL の最も一般的な使用パターンは、設定時に式を解析し、AST を保存した後、実行時に AST の取得と評価を繰り返し行うことです。

CEL 処理フェーズの図

式は構成パスで解析およびチェックされ、保存されてから、読み取りパスの 1 つ以上のコンテキストに対して評価されます。

CEL は、ANTLR レキサーとパーサー文法を使用して、人が読める式から AST に解析されます。解析フェーズでは、プロトベースの AST が出力され、AST の各 Expr ノードには、解析とチェック時に生成されたメタデータへのインデックス登録に使用される整数 ID が含まれます。解析中に生成される syntax.proto ファイルは、式の文字列形式で型付けされた内容の抽象表現を表します。

式が解析されると、環境に対して型チェックが行われ、式内のすべての変数識別子と関数の識別子が宣言され、正しく使用されていることが確認されます。型チェッカーは、型、変数、関数の解決メタデータを含む checked.proto ファイルを生成します。このファイルにより、評価効率が大幅に向上します。

最後に、式が解析されてチェックされた後、保存された AST が評価されます。

CEL エバリュエータには次の 3 つのものが必要です。

  • カスタム拡張機能の関数バインディング
  • 変数のバインディング
  • 評価する AST

関数と変数のバインディングは、AST のコンパイルに使用されたものと一致する必要があります。これらの入力はいずれも、複数の評価で再利用できます。たとえば、AST が多数の変数バインディング セットで評価される、同じ変数が多数の AST に対して使用される、またはプロセスの存続期間全体で使用される関数バインディング(一般的なケース)などです。