Linguagem de expressão comum (CEL, na sigla em inglês)

A Common Expression Language (CEL) é uma linguagem de expressão de uso geral projetada para ser rápida, portátil e segura de executar. É possível usar a CEL sozinha ou incorporá-la em um produto maior. A CEL é uma ótima opção para uma ampla variedade de aplicativos, desde o roteamento de chamadas de procedimento remoto (RPCs, na sigla em inglês) até a definição de políticas de segurança. A CEL é extensível, independente de plataforma e otimizada para fluxos de trabalho de compilação uma vez/avaliação de muitos.

A CEL foi projetada especificamente para ser segura para executar códigos do usuário. Embora seja perigoso chamar eval() cegamente no código Python de um usuário, você pode executar o código CEL de um usuário com segurança. Como a CEL impede comportamentos que a tornam menos eficiente, ela é avaliada com segurança em nanossegundos ou microssegundos. Devido à velocidade e à segurança da CEL, ela é ideal para aplicativos essenciais para o desempenho.

A CEL avalia expressões que são semelhantes a funções de linha única ou expressões lambda. Embora a CEL seja comumente usada para decisões booleanas, ela também pode ser usada para criar objetos mais complexos, como mensagens JSON ou de buffer de protocolo.

Por que usar a CEL?

Muitos serviços e aplicativos avaliam configurações declarativas. Por exemplo, o controle de acesso baseado em papéis (RBAC, na sigla em inglês) é uma configuração declarativa que produz uma decisão de acesso considerando um papel do usuário e um conjunto de usuários. Embora as configurações declarativas sejam suficientes na maioria dos casos, às vezes você precisa de uma potência mais expressiva. É aí que entra a CEL.

Como exemplo de extensão de uma configuração declarativa com CEL, considere os recursos do Identity and Access Management (IAM) do Google Cloud. Embora o RBAC seja o caso comum, o IAM oferece expressões CEL para permitir que os usuários restrinjam ainda mais o escopo da concessão baseada em papéis de acordo com as propriedades de mensagem proto da solicitação ou dos recursos que estão sendo acessados. Descrever essas condições usando o modelo de dados resultaria em uma superfície de API complicada e difícil de trabalhar. Em vez disso, o uso da CEL com controle de acesso baseado em atributos (ABAC, na sigla em inglês) é uma extensão expressiva e eficiente do RBAC.

Conceitos básicos de CEL

Na CEL, uma expressão é compilada em um ambiente. A etapa de compilação produz uma árvore de sintaxe abstrata (AST, na sigla em inglês) no formato de buffer de protocolo. As expressões compiladas são armazenadas para uso futuro a fim de manter a avaliação o mais rápida possível. Uma única expressão compilada pode ser avaliada com muitas entradas diferentes.

Veja mais detalhes sobre alguns desses conceitos.

Expressões

As expressões são escritas pelos usuários. As expressões são semelhantes aos corpos de funções de linha única ou expressões lambda. A assinatura de função que declara a entrada é gravada fora da expressão CEL, e a biblioteca de funções disponíveis para CEL é importada automaticamente.

Por exemplo, a expressão CEL a seguir usa um objeto de solicitação, que inclui um token claims. A expressão retorna um valor booleano que indica se o token claims ainda é válido.

Exemplo de expressão CEL para autenticar um token de declarações

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

Enquanto os usuários definem a expressão CEL, os serviços e aplicativos definem o ambiente em que ela é executada.

Ambientes

Ambientes são definidos por serviços. Os serviços e aplicativos que incorporam CEL declaram o ambiente de expressão. O ambiente é a coleção de variáveis e funções que podem ser usadas em expressões CEL.

Por exemplo, o código textproto a seguir declara um ambiente contendo as variáveis request e now usando a mensagem CompileRequest de um serviço CEL.

Exemplo de declaração de ambiente 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" }
  }
}

As declarações baseadas em proto são usadas pelo verificador de tipos CEL para garantir que todas as referências de identificador e função em uma expressão sejam declaradas e usadas corretamente.

Fases do processamento de expressões

As expressões CEL são processadas em três fases:

  1. Parse
  2. Verificação
  3. Avaliar

O padrão mais comum do uso da CEL é analisar e verificar expressões no momento da configuração, armazenar o AST e, em seguida, recuperar e avaliar o AST repetidamente no momento da execução.

Ilustração das fases de processamento da CEL

As expressões são analisadas e verificadas em caminhos de configuração, armazenadas e
avaliadas em relação a um ou mais contextos em caminhos de leitura.

A CEL é analisada a partir de uma expressão legível por humanos para um AST usando uma gramática lexer e de analisador ANTLR. A fase de análise emite um AST baseado em protótipos, em que cada nó Expr na AST contém um ID de número inteiro que é usado para indexar os metadados gerados durante a análise e a verificação. O arquivo syntax.proto produzido durante a análise representa a representação abstrata do que foi digitado na forma de string da expressão.

Depois que uma expressão é analisada, ela passa por uma verificação de tipo no ambiente para garantir que todos os identificadores de variáveis e funções na expressão foram declarados e estão sendo usados corretamente. O verificador de tipos produz um arquivo checked.proto que inclui metadados de resolução de tipo, variável e função que podem melhorar muito a eficiência da avaliação.

Finalmente, depois que uma expressão é analisada e verificada, o AST armazenado é avaliado.

O avaliador do CEL precisa de três coisas:

  • Vinculações de funções para todas as extensões personalizadas
  • Vinculações de variáveis
  • Um AST para avaliar

As vinculações de função e variável precisam corresponder ao que foi usado para compilar o AST. Qualquer uma dessas entradas pode ser reutilizada em várias avaliações, como a avaliação de um AST em muitos conjuntos de vinculações de variáveis, as mesmas variáveis usadas em muitos ASTs ou as vinculações de funções usadas durante o ciclo de vida de um processo (um caso comum).