Język CEL (Common Expression Language)

CEL (Common Expression Language) to język wyrażeń ogólnego zastosowania, który jest szybki, przenośny i bezpieczny w użyciu. Możesz używać języka CEL samodzielnie lub umieścić go w większym produkcie. Język CEL doskonale sprawdza się w przypadku wielu różnych aplikacji, od kierowania zdalnych wywołań procedur (RPC) po definiowanie zasad zabezpieczeń. Język CEL jest rozszerzalny, niezależny od platformy i zoptymalizowany pod kątem przepływów pracy w trakcie kompilowania/oceny.

Język CEL został zaprojektowany pod kątem bezpiecznego wykonywania kodu użytkownika. Chociaż niebezpiecznie jest wywołać ślepy kod eval() w kodzie Pythona użytkownika, możesz bezpiecznie wykonać jego kod CEL. A ponieważ język CEL zapobiega zachowaniu, które zmniejszyłoby jego wydajność, ocenia on bezpiecznie w nanosekundach lub mikrosekundach. Szybkość i bezpieczeństwo języka CEL sprawia, że jest to idealne rozwiązanie w aplikacjach, które mają kluczowe znaczenie dla wydajności.

CEL ocenia wyrażenia podobne do funkcji jednowierszowych lub wyrażeń lambda. Chociaż język CEL jest powszechnie używany przy podejmowaniu decyzji dotyczących wartości logicznych, można go też używać do tworzenia bardziej złożonych obiektów, takich jak JSON czy komunikaty bufora protokołu.

Dlaczego CEL?

Wiele usług i aplikacji ocenia konfiguracje deklaratywne. Na przykład kontrola dostępu oparta na rolach (RBAC) to konfiguracja deklaratywna, która generuje decyzję o dostępie dla danej roli i zbioru użytkowników. W większości przypadków konfiguracje deklaratywne wystarczają, ale czasami potrzeba więcej mocy. Tu do akcji wkracza CEL.

Przykładem rozszerzenia konfiguracji deklaratywnej za pomocą języka CEL jest zastosowanie funkcji zarządzania tożsamościami i dostępem (IAM) w Google Cloud. Częstym przypadkiem jest RBAC, jednak uprawnienia oferują wyrażenia CEL umożliwiające użytkownikom dalsze ograniczanie zakresu uwierzytelnienia na podstawie roli zgodnie z właściwościami komunikatów proto żądania lub zasobów, do których uzyskiwany jest dostęp. Opisanie takich warunków za pomocą modelu danych prowadziłoby do skomplikowanej platformy interfejsu API, która jest trudna w obsłudze. Zamiast tego zastosowanie języka CEL w połączeniu z kontrolą dostępu opartą na atrybutach (ABAC) to zaawansowane i zaawansowane rozszerzenie RBAC.

Podstawowe pojęcia związane z językiem CEL

W języku CEL wyrażenie jest kompilowane na podstawie środowiska. Etap kompilacji powoduje utworzenie abstrakcyjnego drzewa składni (AST) w formacie bufora protokołu. Skompilowane wyrażenia są przechowywane do wykorzystania w przyszłości, aby ocena była jak najkrótsza. Jedno skompilowane wyrażenie można ocenić z wykorzystaniem wielu różnych danych wejściowych.

Poniżej znajdziesz bliższe omówienie niektórych z tych pojęć.

Wyrażenia

Wyrażenia są zapisywane przez użytkowników. Wyrażenia są podobne do jednowierszowych treści funkcji lub wyrażeń lambda. Podpis funkcji deklarujący dane wejściowe jest zapisywany poza wyrażeniem CEL, a biblioteka funkcji dostępna w języku CEL jest automatycznie importowana.

Na przykład poniższe wyrażenie CEL pobiera obiekt żądania, a żądanie zawiera token claims. Wyrażenie zwraca wartość logiczną, która wskazuje, czy token claims jest nadal prawidłowy.

Przykładowe wyrażenie CEL do uwierzytelniania tokena deklaracji

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

Wyrażenie CEL jest definiowane przez użytkowników, a usługi i aplikacje określają środowisko, w którym ono działa.

Środowiska

Środowiska są definiowane przez usługi. Usługi i aplikacje osadzone w języku CEL deklarują środowisko wyrażeń. Środowisko to zbiór zmiennych i funkcji, których można używać w wyrażeniach CEL.

Na przykład ten kod textproto deklaruje środowisko zawierające zmienne request i now za pomocą komunikatu CompileRequest z usługi CEL.

Przykładowa deklaracja środowiska 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" }
  }
}

Deklaracje oparte na proto są używane przez narzędzie do sprawdzania typu CEL, aby zapewnić, że wszystkie odwołania do identyfikatorów i funkcji w wyrażeniu są zadeklarowane i używane prawidłowo.

Etapy przetwarzania wyrażeń

Wyrażenia CEL są przetwarzane w 3 fazach:

  1. Analizuj poprawność
  2. Kontrola
  3. Oceń

Najpopularniejszym wzorcem użycia CEL jest analizowanie i sprawdzanie wyrażeń w czasie konfiguracji, przechowywanie AST, a następnie wielokrotne pobieranie i ocenianie AST w czasie działania.

Ilustracja przedstawiająca fazy przetwarzania CEL

Wyrażenia są analizowane i sprawdzane na ścieżkach konfiguracji, przechowywane, a następnie oceniane w odniesieniu do co najmniej 1 kontekstu w ścieżkach odczytu.

CEL jest analizowany z wyrażenia zrozumiałego dla człowieka do AST za pomocą leksera i gramatyki parsera ANTLR. Etap analizy generuje kod AST oparty na proto, gdzie każdy Expr węzeł w AST zawiera identyfikator liczby całkowitej generowany podczas analizowania i sprawdzania metadanych. Plik syntax.proto generowany podczas analizy to abstrakcyjna reprezentacja tekstu wpisanego w formie ciągu znaków.

Po przeanalizowaniu wyrażenia wyrażenie jest sprawdzane pod kątem typu w środowisku, aby upewnić się, że wszystkie identyfikatory zmiennych i funkcji w wyrażeniu zostały zadeklarowane i są poprawnie używane. Narzędzie do sprawdzania typu generuje plik checked.proto zawierający metadane typu, zmiennej i rozdzielczości funkcji, które mogą znacznie zwiększyć wydajność oceny.

Na koniec, po przeanalizowaniu i sprawdzeniu wyrażenia, oceniany jest zapisany AST.

Oceniający CEL potrzebuje 3 rzeczy:

  • Powiązania funkcji dla dowolnych rozszerzeń niestandardowych
  • Powiązania zmiennych
  • AST do oceny

Powiązania funkcji i zmiennych powinny być zgodne z informacjami użytymi do skompilowania interfejsu AST. Każde z tych danych wejściowych można wykorzystywać wielokrotnie w wielu ocenach, na przykład AST jest oceniany na wielu zbiorach wiązań zmiennych, te same zmienne używane w wielu AST lub powiązania funkcji używane przez cały czas trwania procesu (częsty przypadek).