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 (RPCs) hasta la definición de políticas de seguridad. CEL es extensible, independiente de la plataforma, verificable formalmente y optimizado para flujos de trabajo de compilación única y evaluación múltiple.

CEL se diseñó específicamente para que sea seguro ejecutar el código del usuario. 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 CEL de un usuario. Además, como CEL evita el comportamiento que lo 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 esenciales 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 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 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 Google Cloud Identity and Access Management (IAM). Si bien RBAC es el caso común, IAM ofrece expresiones CEL para permitir que los usuarios restrinjan aún más el alcance de la concesión basada 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 daría como resultado una superficie de API complicada con la que es difícil trabajar. En cambio, usar CEL con el control de acceso basado en atributos (ABAC) es una extensión expresiva y potente de RBAC.

Conceptos básicos de CEL

En CEL, una expresión se compila en 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 en el futuro 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, se muestra una mirada más de cerca a algunos de estos conceptos.

Expresiones

Los usuarios escriben expresiones. Las expresiones son similares a los cuerpos de funciones de una sola línea o a las expresiones lambda. La firma de 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 muestra un valor booleano que indica si el token claims sigue siendo válido.

Ejemplo de expresión 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 servicios definen los entornos. 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 expresiones CEL.

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

Ejemplo de declaración de entorno 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 de 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 de una expresión legible por humanos a un AST con un ANTLR y una gramática de analizador. 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 en los metadatos generados durante el análisis y la verificación. El syntax.proto archivo 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 el tipo en el entorno para garantizar que todos los identificadores de variables y funciones de la expresión se hayan declarado y se usen correctamente. El verificador de tipos produce un checked.proto archivo que incluye metadatos de resolución de tipo, variable y función que pueden mejorar drásticamente la eficiencia de la evaluación.

Por último, después de analizar y verificar 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 ASTs o las vinculaciones de funciones que se usan durante la vida útil de un proceso (un caso común).

Verificación formal

Además de la evaluación del tiempo de ejecución, las expresiones y políticas de CEL se pueden verificar formalmente para demostrar matemáticamente su corrección en todas las entradas posibles.

Con tecnología del demostrador de teoremas Z3, el marco de trabajo de verificación formal de CEL en CEL-Java traduce expresiones y políticas de CEL a fórmulas de Satisfiability Modulo Theories (SMT) para hacer lo siguiente:

  • Demostrar invariantes de seguridad: Verifica que las políticas críticas no se puedan omitir en ninguna combinación de entradas con las especificaciones assume y assert.
  • Verificar la equivalencia lógica: Demuestra matemáticamente que las expresiones refactorizadas o generadas por IA se comportan de forma idéntica a la regla original.
  • Aplicar la validez exhaustiva: Garantiza que las barreras de protección (como las políticas de admisión de validación de Kubernetes) se mantengan en todas las entradas o genera contraejemplos concretos cuando se infringen.
  • Eliminar los falsos positivos: Emplea el seguimiento de contaminación de tres pasos para aislar las funciones personalizadas no asignadas, lo que garantiza que las infracciones informadas sean siempre errores reproducibles.

Para obtener una introducción y ejemplos del mundo real, consulta la entrada de blog de código abierto de Google: Securing the era de los agentes: Introducing formal verification for CEL.