Common Expression Language (CEL)

Common Expression Language (CEL) es un lenguaje de expresión de uso general diseñado para ser rápido, portátil y seguro de ejecutar. Puedes usar CEL por sí solo o incorporarlo en un producto más grande. CEL es ideal para una amplia variedad de aplicaciones, desde el enrutamiento de llamadas de procedimiento remoto (RPC) hasta la definición de políticas de seguridad. CEL es extensible, independiente de la plataforma y está optimizado para flujos de trabajo de compilación única y evaluación múltiple.

CEL se diseñó específicamente para ejecutar código del usuario de forma segura. Si bien es peligroso llamar a eval() a ciegas en el código de Python de un usuario, puedes ejecutar de forma segura el código de CEL de un usuario. Además, como CEL evita el comportamiento que la haría menos eficiente, se evalúa de forma segura en nanosegundos o microsegundos. La velocidad y la seguridad de CEL lo hacen ideal para aplicaciones críticas para el rendimiento.

CEL evalúa expresiones similares a funciones de una sola línea o expresiones lambda. Si bien CEL se usa comúnmente para tomar decisiones booleanas, también puedes usarlo para construir objetos más complejos, como mensajes JSON o de búfer de protocolo.

¿Por qué CEL?

Muchos servicios y aplicaciones evalúan las configuraciones declarativas. Por ejemplo, el control de acceso basado en roles (RBAC) es una configuración declarativa que produce una decisión de acceso dado un rol de usuario y un conjunto de usuarios. Si bien las configuraciones declarativas son suficientes para la mayoría de los casos, a veces necesitas más poder expresivo. Ahí es donde entra en juego CEL.

Como ejemplo de cómo extender una configuración declarativa con CEL, considera las capacidades de Identity and Access Management (IAM) de Google Cloud. Si bien el RBAC es el caso común, IAM ofrece expresiones de CEL para permitir que los usuarios restrinjan aún más el alcance del otorgamiento basado en roles según las propiedades del mensaje .proto de la solicitud o los recursos a los que se accede. Describir esas condiciones a través del modelo de datos generaría una superficie de API compleja con la que sería difícil trabajar. En cambio, usar CEL con el control de acceso basado en atributos (ABAC) es una extensión expresiva y potente del RBAC.

Conceptos básicos de CEL

En CEL, una expresión se compila en función de un entorno. El paso de compilación produce un árbol de sintaxis abstracta (AST) en formato de búfer de protocolo. Las expresiones compiladas se almacenan para su uso futuro y para que la evaluación sea lo más rápida posible. Una sola expresión compilada se puede evaluar con muchas entradas diferentes.

A continuación, analizaremos algunos de estos conceptos con más detalle.

Expresiones

Los usuarios escriben las expresiones. Las expresiones son similares a los cuerpos de funciones de una sola línea o a las expresiones lambda. La firma de la función que declara la entrada se escribe fuera de la expresión CEL, y la biblioteca de funciones disponibles para CEL se importa automáticamente.

Por ejemplo, la siguiente expresión CEL toma un objeto de solicitud, y la solicitud incluye un token claims. La expresión devuelve un valor booleano que indica si el token claims sigue siendo válido.

Ejemplo de expresión de CEL para autenticar un token de reclamos

// 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

Si bien los usuarios definen la expresión CEL, los servicios y las aplicaciones definen el entorno en el que se ejecuta.

Entornos

Los entornos se definen por los servicios. Los servicios y las aplicaciones que incorporan CEL declaran el entorno de expresión. El entorno es la colección de variables y funciones que se pueden usar en las expresiones de CEL.

Por ejemplo, el siguiente código de textproto declara un entorno que contiene las variables request y now con el mensaje CompileRequest de un servicio de CEL.

Ejemplo de declaración del entorno de 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" }
  }
}

El verificador de tipos de CEL usa las declaraciones basadas en .proto para garantizar que todas las referencias a identificadores y funciones dentro de una expresión se declaren y usen correctamente.

Fases del procesamiento de expresiones

Las expresiones CEL se procesan en tres fases:

  1. Analizar
  2. Cheque
  3. Evaluar

El patrón de uso más común de CEL es analizar y verificar expresiones en el momento de la configuración, almacenar el AST y, luego, recuperar y evaluar el AST de forma repetida en el tiempo de ejecución.

Ilustración de las fases de procesamiento de CEL

Las expresiones se analizan y verifican en las rutas de configuración, se almacenan y, luego, se evalúan en uno o más contextos en las rutas de lectura.

CEL se analiza a partir de una expresión legible para el usuario en un AST con una gramática de analizador y analizador léxico de ANTLR. La fase de análisis emite un AST basado en .proto en el que cada nodo Expr del AST contiene un ID de número entero que se usa para indexar los metadatos generados durante el análisis y la verificación. El archivo syntax.proto que se produce durante el análisis representa la representación abstracta de lo que se escribió en la forma de cadena de la expresión.

Después de analizar una expresión, se verifica su tipo en el entorno para garantizar que todos los identificadores de variables y funciones de la expresión se hayan declarado y se estén usando correctamente. El verificador de tipos produce un archivo checked.proto que incluye metadatos de resolución de tipos, variables y funciones que pueden mejorar drásticamente la eficiencia de la evaluación.

Por último, después de que se analiza y verifica una expresión, se evalúa el AST almacenado.

El evaluador de CEL necesita tres elementos:

  • Vinculaciones de funciones para cualquier extensión personalizada
  • Vinculaciones de variables
  • Un AST para evaluar

Las vinculaciones de funciones y variables deben coincidir con lo que se usó para compilar el AST. Cualquiera de estas entradas se puede reutilizar en varias evaluaciones, como un AST que se evalúa en muchos conjuntos de vinculaciones de variables, las mismas variables que se usan en muchos AST o las vinculaciones de funciones que se usan durante la vida útil de un proceso (un caso común).