CEL-Go Codelab: Hızlı, güvenli, yerleştirilmiş ifadeler

1. Giriş

CEL nedir?

CEL; hızlı, taşınabilir ve güvenli bir şekilde yürütülmesi amacıyla tasarlanmış, tur dışındaki bir tam ifade dilidir. CEL tek başına veya daha büyük bir ürüne yerleştirilebilir.

CEL, kullanıcı kodunun çalıştırılabileceği bir dil olarak tasarlanmıştır. Kullanıcının python kodunda kör bir şekilde eval() çağrısı yapmak tehlikeli olsa da kullanıcının CEL kodunu güvenli bir şekilde yürütebilirsiniz. CEL, performansının düşmesine neden olacak davranışları önlediği için nanosaniye ile mikrosaniye arasındaki sıraya göre güvenli bir şekilde değerlendirme yapar. performans açısından kritik uygulamalar için idealdir.

CEL, tek satırlı işlevlere veya lambda ifadelerine benzeyen ifadeleri değerlendirir. CEL, boole kararlarında yaygın olarak kullanılsa da JSON veya protobuf mesajları gibi daha karmaşık nesneleri oluşturmak için de kullanılabilir.

CEL, projeniz için uygun mu?

CEL, AST'deki ifadeleri nanosaniye veya mikrosaniye cinsinden değerlendirdiğinden, performans açısından kritik yollara sahip uygulamalar CEL için ideal kullanım alanıdır. Kritik yollarda CEL kodunun AST'de derlenmesi önerilmez; ideal uygulamalar, yapılandırmanın sık sık yürütüldüğü ve nispeten seyrek değiştirildiği uygulamalardır.

Örneğin, bir hizmete yapılan her HTTP isteğiyle birlikte bir güvenlik politikası yürütmek, CEL için ideal bir kullanım alanıdır çünkü güvenlik politikası nadiren değişir ve CEL, yanıt süresi üzerinde kayda değer bir etkiye sahip olmaz. Bu durumda CEL, isteğe izin verilmesi gerekiyorsa boole döndürür, ancak daha karmaşık bir mesaj döndürebilir.

Bu Codelab'de neler ele alınıyor?

Bu codelab'in ilk adımında, CEL'i ve Temel Kavramları kullanma motivasyonu açıklanmaktadır. Diğer kısımlar, yaygın kullanım alanlarını kapsayan alıştırmalar kodlamaya ayrılmıştır. Dil, anlambilim ve özellikler hakkında daha ayrıntılı bilgi edinmek için GitHub'daki CEL Dil Tanımı ve CEL Go Dokümanlar sayfalarını inceleyin.

Bu codelab'in hedef kitlesi CEL'i destekleyen hizmetleri kullanmak için CEL hakkında bilgi edinmek isteyen geliştiricilere yöneliktir. Bu codelab'de, CEL'i kendi projenize nasıl entegre edeceğiniz ele alınmamaktadır.

Neler öğreneceksiniz?

  • CEL'deki temel kavramlar
  • Hello, World: Bir Dizeyi değerlendirmek için CEL'i kullanma
  • Değişkenleri oluşturma
  • Mantıksal VE/VEYA işlemlerinde CEL'in kısa devresini anlama
  • JSON derlemek için CEL'i kullanma
  • Proto arabellek oluşturmak için CEL'i kullanma
  • Makro Oluşturma
  • CEL ifadelerinizi ayarlamanın yolları

Gerekenler

Ön koşullar

Bu codelab'de, Protokol Arabellekleri ve Go Lang ile ilgili temel bilgiler temel alınmıştır.

Protokol Arabellekleri hakkında bilginiz yoksa, ilk alıştırma CEL'in nasıl çalıştığına dair bir fikir verecektir. Ancak daha ileri düzey örneklerde CEL'e giriş olarak Protokol Arabellekleri kullanıldığından bunların anlaşılması daha zor olabilir. Öncelikle bu eğitici videolardan birini uygulamayı düşünebilirsiniz. CEL'i kullanmak için Protokol Arabelleklerinin gerekli olmadığını ancak bu codelab'de yaygın olarak kullanıldığını unutmayın.

Go'nun yüklü olup olmadığını şu komutu çalıştırarak test edebilirsiniz:

go --help

2. Temel kavramlar

Uygulamalar

CEL genel amaçlıdır ve RPC'leri yönlendirmeden güvenlik politikasını tanımlamaya kadar çeşitli uygulamalarda kullanılmaktadır. CEL genişletilebilir, uygulamadan bağımsızdır ve birçok iş akışını bir kez derlemek için optimize edilmiştir.

Birçok hizmet ve uygulama, bildirimli yapılandırmaları değerlendirir. Örneğin, Rol Tabanlı Erişim Denetimi (RBAC), bir role ve bir dizi kullanıcıya göre erişim kararı oluşturan bildirim temelli bir yapılandırmadır. Bildirim temelli yapılandırmalar% 80 kullanım alanıysa CEL, kullanıcıların daha fazla ifade gücüne ihtiyacı olduğunda kalan% 20'yi yuvarlamak için yararlı bir araçtır.

Derleme

Bir ifade, bir ortama göre derlenir. Derleme adımı, protobuf biçiminde bir Soyut Söz Dizimi Ağacı (AST) üretir. Derlenen ifadeler, değerlendirmenin mümkün olduğunca hızlı olmasını sağlamak için genellikle ileride kullanılmak üzere saklanır. Tek bir derlenmiş ifade, birçok farklı girişle değerlendirilebilir.

İfadeler

Kullanıcılar ifadeleri tanımlar; hizmetler ve uygulamalar çalıştığı ortamı tanımlar. Bir işlev imzası, girişleri tanımlar ve CEL ifadesinin dışına yazılır. CEL tarafından kullanılabilen işlevler kitaplığı otomatik olarak içe aktarılır.

Aşağıdaki örnekte ifade bir istek nesnesini alır ve istek bir talep jetonu içerir. İfade, hak talepleri jetonunun hâlâ geçerli olup olmadığını gösteren bir boole döndürür.

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

Ortam

Ortamlar hizmetler tarafından tanımlanır. CEL'i yerleştiren hizmet ve uygulamalar, ifade ortamını bildirir. Ortam, ifadelerde kullanılabilecek değişkenlerin ve işlevlerin koleksiyonudur.

Proto tabanlı bildirimler, bir ifadedeki tüm tanımlayıcı ve işlev referanslarının doğru şekilde tanımlandığından ve kullanıldığından emin olmak için CEL tür denetleyicisi tarafından kullanılır.

Bir ifadeyi ayrıştırmanın üç aşaması

İfade işleme üç aşamadan oluşur: ayrıştırma, kontrol ve değerlendirme. CEL için en yaygın model, kontrol düzleminin yapılandırma sırasında ifadeleri ayrıştırıp kontrol etmesi ve AST'yi depolamasıdır.

c71fc08068759f81.png

Çalışma zamanında, veri düzlemi AST'yi tekrar tekrar alıp değerlendirir. CEL, çalışma zamanı verimliliği için optimize edilmiştir ancak gecikmeli kritik kod yollarında ayrıştırma ve kontrol yapılmamalıdır.

49ab7d8517143b66.png

CEL, ANTLR sözlüğü / ayırıcı dil bilgisi kullanılarak insan tarafından okunabilen bir ifadeden soyut bir söz dizimi ağacına ayrıştırılır. Ayrıştırma aşaması, proto temelli bir soyut söz dizimi ağacı yayar. Burada, AST'deki her Expr düğümü, ayrıştırma ve kontrol sırasında oluşturulan meta verileri dizine eklemek için kullanılan bir tam sayı kimliği içerir. Ayrıştırma sırasında üretilen syntax.proto, ifadenin dize biçiminde yazılan soyut temsilini temsil eder.

Bir ifade ayrıştırıldıktan sonra, ifadedeki tüm değişken ve işlev tanımlayıcılarının belirtildiğinden ve doğru şekilde kullanıldığından emin olmak için ortama göre kontrol edilebilir. Tür, değişken ve işlev çözünürlüğü meta verilerini içeren bir checked.proto dosyası oluşturur. Bu meta veriler, değerlendirme verimliliğini büyük ölçüde iyileştirebilir.

CEL değerlendiricisinin 3 şeye ihtiyacı vardır:

  • Tüm özel uzantılar için işlev bağlamaları
  • Değişken bağlamalar
  • Değerlendirme için bir AST

İşlev ve değişken bağlamaları, AST'yi derlemek için kullanılanlarla eşleşmelidir. Bu girişlerin herhangi biri, birçok değişken bağlama grubunda değerlendirilen AST veya birçok AST'de kullanılan aynı değişkenler ya da sürecin ömrü boyunca kullanılan işlev bağlamaları gibi birden fazla değerlendirmede yeniden kullanılabilir (yaygın bir durum).

3. Kur

Bu codelab'in kodu, cel-go deposunun codelab klasöründe bulunur. Çözüm, aynı deponun codelab/solution klasöründe bulunabilir.

Depoya klonlayın ve cd'ye ekleyin:

git clone https://github.com/google/cel-go.git 
cd cel-go/codelab

go run kullanarak kodu çalıştırın:

go run .

Aşağıdaki çıkışı göreceksiniz:

=== Exercise 1: Hello World ===

=== Exercise 2: Variables ===

=== Exercise 3: Logical AND/OR ===

=== Exercise 4: Customization ===

=== Exercise 5: Building JSON ===

=== Exercise 6: Building Protos ===

=== Exercise 7: Macros ===

=== Exercise 8: Tuning ===

CEL paketleri nerede?

Düzenleyicinizde codelab/codelab.go dosyasını açın. Bu codelab'de alıştırmaların yürütülmesini sağlayan ana işlevi ve ardından üç yardımcı işlev bloğunu göreceksiniz. İlk yardımcılar CEL değerlendirmesinin aşamalarında yardımcı olur:

  • Compile işlevi: Bir ortama göre ayrıştırma, denetim ve giriş ifadesini ayrıştırır
  • Eval işlevi: Derlenmiş bir programı bir girişe göre değerlendirir
  • Report işlevi: Değerlendirme sonucunun kolayca yazdırılmasını sağlar

Ayrıca, çeşitli alıştırmalarda giriş oluşturmaya yardımcı olmak için request ve auth yardımcıları sağlanmıştır.

Alıştırmalar, paketlere kısa paket adlarıyla atıfta bulunacaktır. Daha ayrıntılı bilgi edinmek isterseniz google/cel-go deposundaki paketten kaynak konumuna eşleme aşağıdaki gibidir:

Paket

Kaynak Konumu

Açıklama

cel

cel-go/cel

Üst düzey arayüzler

referans

cel-go/common/types/ref

Referans arayüzler

türler

cel-go/common/types

Çalışma zamanı türü değerleri

4. Merhaba Dünya!

Tüm programlama dilleri geleneğine göre ilk olarak "Hello World!" ifadesini oluşturup değerlendireceğiz.

Ortamı yapılandırma

Düzenleyicinizde, exercise1 beyanını bulun ve ortamı ayarlamak için aşağıdaki alanları doldurun:

// exercise1 evaluates a simple literal expression: "Hello, World!"
//
// Compile, eval, profit!
func exercise1() {
    fmt.Println("=== Exercise 1: Hello World ===\n")
    // Create the standard environment.
    env, err := cel.NewEnv()
    if err != nil {
        glog.Exitf("env error: %v", err)
    }
    // Will add the parse and check steps here
}

CEL uygulamaları, bir ifadeyi bir ortama göre değerlendirir. env, err := cel.NewEnv(), standart ortamı yapılandırır.

Görüşmeye cel.EnvOption seçenekleri sağlanarak ortam özelleştirilebilir. Bu seçenekler, makroları devre dışı bırakabilir, özelleştirilebilen değişkenleri ve işlevleri bildirebilir vb.

Standart CEL ortamı, dil spesifikasyonunda tanımlanan tüm türleri, operatörleri, işlevleri ve makroları destekler.

İfadeyi ayrıştırıp kontrol edin

Ortam yapılandırıldıktan sonra ifadeler ayrıştırılabilir ve kontrol edilebilir. Fonksiyonunuza aşağıdakini ekleyin:

// exercise1 evaluates a simple literal expression: "Hello, World!"
//
// Compile, eval, profit!
func exercise1() {
    fmt.Println("=== Exercise 1: Hello World ===\n")
    // Create the standard environment.
    env, err := cel.NewEnv()
    if err != nil {
        glog.Exitf("env error: %v", err)
    }
    // Check that the expression compiles and returns a String.
    ast, iss := env.Parse(`"Hello, World!"`)
    // Report syntactic errors, if present.
    if iss.Err() != nil {
        glog.Exit(iss.Err())
    }
    // Type-check the expression for correctness.
    checked, iss := env.Check(ast)
    // Report semantic errors, if present.
    if iss.Err() != nil {
        glog.Exit(iss.Err())
    }
    // Check the output type is a string.
    if !reflect.DeepEqual(checked.OutputType(), cel.StringType) {
        glog.Exitf(
            "Got %v, wanted %v output type",
            checked.OutputType(), cel.StringType,
        )
    }
    // Will add the planning step here
}

Parse ve Check çağrılarının döndürdüğü iss değeri, hata olabilecek sorunların bir listesidir. iss.Err() değeri nil değilse söz diziminde veya anlamda hata vardır ve program devam edemez. İfade doğru şekilde biçimlendirildiğinde, bu çağrıların sonucu yürütülebilir bir cel.Ast olur.

İfadeyi değerlendirin

İfade ayrıştırılıp cel.Ast olarak kontrol edildikten sonra, işlev bağlamaları ve değerlendirme modları işlevsel seçeneklerle özelleştirilebilen değerlendirilebilir bir programa dönüştürülebilir. cel.CheckedExprToAst veya cel.ParsedExprToAst işlevlerini kullanarak protondan bir cel.Ast okunabileceğini unutmayın.

cel.Program planlandıktan sonra Eval çağrısı yapılarak girişe göre değerlendirilebilir. Eval sonucu; sonucu, değerlendirme ayrıntılarını ve hata durumunu da içerir.

Planlama ekleyip eval numaralı telefonu arayın:

// exercise1 evaluates a simple literal expression: "Hello, World!"
//
// Compile, eval, profit!
func exercise1() {
    fmt.Println("=== Exercise 1: Hello World ===\n")
    // Create the standard environment.
    env, err := cel.NewEnv()
    if err != nil {
        glog.Exitf("env error: %v", err)
    }
    // Check that the expression compiles and returns a String.
    ast, iss := env.Parse(`"Hello, World!"`)
    // Report syntactic errors, if present.
    if iss.Err() != nil {
        glog.Exit(iss.Err())
    }
    // Type-check the expression for correctness.
    checked, iss := env.Check(ast)
    // Report semantic errors, if present.
    if iss.Err() != nil {
        glog.Exit(iss.Err())
    }
    // Check the output type is a string.
    if !reflect.DeepEqual(checked.OutputType(), cel.StringType) {
        glog.Exitf(
            "Got %v, wanted %v output type",
            checked.OutputType(), cel.StringType)
    }
    // Plan the program.
    program, err := env.Program(checked)
    if err != nil {
        glog.Exitf("program error: %v", err)
    }
    // Evaluate the program without any additional arguments.
    eval(program, cel.NoVars())
    fmt.Println()
}

Gelecekteki alıştırmalarda, yukarıda yer alan hata durumları kısaca yer almayacaktır.

Kodu çalıştır

Komut satırında kodu yeniden çalıştırın:

go run .

Aşağıdaki çıkışı ve gelecekteki alıştırmalar için yer tutucuları göreceksiniz.

=== Exercise 1: Hello World ===

------ input ------
(interpreter.emptyActivation)

------ result ------
value: Hello, World! (types.String)

5. Fonksiyonda değişkenleri kullanma

Çoğu CEL uygulaması, ifadelerde başvurulabilecek değişkenleri tanımlar. Değişkenlerin bildirimleri bir adı ve türü belirtir. Bir değişkenin türü, CEL yerleşik türü, iyi bilinen bir protokol arabelleği türü veya açıklayıcısı CEL'ye de sağlandığı sürece herhangi bir protobuf mesaj türü olabilir.

İşlevi ekleme

Düzenleyicinizde exercise2 beyanını bulun ve aşağıdakileri ekleyin:

// exercise2 shows how to declare and use variables in expressions.
//
// Given a request of type google.rpc.context.AttributeContext.Request
// determine whether a specific auth claim is set.
func exercise2() {
  fmt.Println("=== Exercise 2: Variables ===\n")
   env, err := cel.NewEnv(
    // Add cel.EnvOptions values here.
  )
  if err != nil {
    glog.Exit(err)
  }
  ast := compile(env, `request.auth.claims.group == 'admin'`, cel.BoolType)
  program, _ := env.Program(ast)

  // Evaluate a request object that sets the proper group claim.
  claims := map[string]string{"group": "admin"}
  eval(program, request(auth("user:me@acme.co", claims), time.Now()))
  fmt.Println()
}

Yeniden çalıştırıp hatayı anlayın

Programı yeniden çalıştırın:

go run .

Aşağıdaki çıkışı göreceksiniz:

ERROR: <input>:1:1: undeclared reference to 'request' (in container '')
 | request.auth.claims.group == 'admin'
 | ^

Tür denetleyicisi, istek nesnesi için hatanın ortaya çıktığı kaynak snippet'ini kolayca içeren bir hata üretir.

6. Değişkenleri bildirme

EnvOptions ekle

Düzenleyicide, istek nesnesi için google.rpc.context.AttributeContext.Request türünde bir mesaj şeklinde bir bildirim sağlayarak ortaya çıkan hatayı şu şekilde düzeltelim:

// exercise2 shows how to declare and use variables in expressions.
//
// Given a `request` of type `google.rpc.context.AttributeContext.Request`
// determine whether a specific auth claim is set.
func exercise2() {
  fmt.Println("=== Exercise 2: Variables ===\n")
  env, err := cel.NewEnv(
    cel.Variable("request",
      cel.ObjectType("google.rpc.context.AttributeContext.Request"),
    ),
  )
  if err != nil {
    glog.Exit(err)
  }
  ast := compile(env, `request.auth.claims.group == 'admin'`, cel.BoolType)
  program, _ := env.Program(ast)

  // Evaluate a request object that sets the proper group claim.
  claims := map[string]string{"group": "admin"}
  eval(program, request(auth("user:me@acme.co", claims), time.Now()))
  fmt.Println()
}

Yeniden çalıştırıp hatayı anlayın

Programı yeniden çalıştırma:

go run .

Şu hatayı görmeniz gerekir:

ERROR: <input>:1:8: [internal] unexpected failed resolution of 'google.rpc.context.AttributeContext.Request'
 | request.auth.claims.group == 'admin'
 | .......^

Protobuf mesajlarına atıfta bulunan değişkenleri kullanmak için tür denetleyicisinin tür tanımlayıcısını da bilmesi gerekir.

Fonksiyonunuzdaki isteğin tanımlayıcısını belirlemek için cel.Types öğesini kullanın:

// exercise2 shows how to declare and use variables in expressions.
//
// Given a `request` of type `google.rpc.context.AttributeContext.Request`
// determine whether a specific auth claim is set.
func exercise2() {
  fmt.Println("=== Exercise 2: Variables ===\n")
   env, err := cel.NewEnv(
    cel.Types(&rpcpb.AttributeContext_Request{}), 
    cel.Variable("request",
      cel.ObjectType("google.rpc.context.AttributeContext.Request"),
    ),
  )

  if err != nil {
    glog.Exit(err)
  }
  ast := compile(env, `request.auth.claims.group == 'admin'`, cel.BoolType)
  program, _ := env.Program(ast)

  // Evaluate a request object that sets the proper group claim.
  claims := map[string]string{"group": "admin"}
  eval(program, request(auth("user:me@acme.co", claims), time.Now()))
  fmt.Println()
}

Başarıyla yeniden çalıştır!

Programı tekrar çalıştırın:

go run .

Aşağıdaki ekranı görmeniz gerekir:

=== Exercise 2: Variables ===

request.auth.claims.group == 'admin'

------ input ------
request = time: <
  seconds: 1569255569
>
auth: <
  principal: "user:me@acme.co"
  claims: <
    fields: <
      key: "group"
      value: <
        string_value: "admin"
      >
    >
  >
>

------ result ------
value: true (types.Bool)

İncelemek amacıyla, hata için bir değişken tanımladık, bu değişkene tür tanımlayıcı atadık ve ardından ifade değerlendirmesinde değişkene başvuruda bulunduk.

7. Mantıksal VE/VEYA

CEL'in diğer benzersiz özelliklerinden biri de dönüşlü mantıksal operatörleri kullanmasıdır. Koşullu dalın her iki tarafı, hatalar veya kısmi giriş durumunda bile değerlendirmeyi kısa devreye sokabilir.

Başka bir deyişle CEL, mümkün olduğunda sonuç veren bir değerlendirme sırası bulur. Bu sipariş sırasında hataları göz ardı eder ve diğer değerlendirme siparişlerinde ortaya çıkabilecek eksik verileri bile göz ardı eder. Uygulamalar değerlendirme maliyetini en aza indirmek için bu özellikten yararlanabilir ve bunlar olmadan sonuca ulaşılabildiğinde pahalı girdilerin toplanmasını erteleyebilir.

Bir VE/VEYA örneği ekleyip CEL kısa devrelerinin nasıl değerlendirildiğini anlamak için bu örneği farklı bir girişle deneyeceğiz.

İşlevi oluşturma

Düzenleyicinizde, 3. alıştırmaya aşağıdaki içeriği ekleyin:

// exercise3 demonstrates how CEL's commutative logical operators work.
//
// Construct an expression which checks if the `request.auth.claims.group`
// value is equal to admin or the `request.auth.principal` is
// `user:me@acme.co`. Issue two requests, one that specifies the proper 
// user, and one that specifies an unexpected user.
func exercise3() {
  fmt.Println("=== Exercise 3: Logical AND/OR ===\n")
  env, _ := cel.NewEnv(
    cel.Types(&rpcpb.AttributeContext_Request{}),
    cel.Variable("request",
      cel.ObjectType("google.rpc.context.AttributeContext.Request"),
    ),
  )
}

Ardından, kullanıcı admin grubunun üyesiyse veya belirli bir e-posta tanımlayıcısına sahipse true değerini döndürecek şu VEYA ifadesini ekleyin:

// exercise3 demonstrates how CEL's commutative logical operators work.
//
// Construct an expression which checks if the `request.auth.claims.group`
// value is equal to admin or the `request.auth.principal` is
// `user:me@acme.co`. Issue two requests, one that specifies the proper 
// user, and one that specifies an unexpected user.
func exercise3() {
  fmt.Println("=== Exercise 3: Logical AND/OR ===\n")
  env, _ := cel.NewEnv(
    cel.Types(&rpcpb.AttributeContext_Request{}),
    cel.Variable("request",
      cel.ObjectType("google.rpc.context.AttributeContext.Request"),
    ),
  )

  ast := compile(env,
    `request.auth.claims.group == 'admin'
        || request.auth.principal == 'user:me@acme.co'`,
    cel.BoolType)
  program, _ := env.Program(ast)
}

Son olarak, kullanıcıyı boş bir hak talebi grubuyla değerlendiren eval destek kaydını ekleyin:

// exercise3 demonstrates how CEL's commutative logical operators work.
//
// Construct an expression which checks whether the request.auth.claims.group
// value is equal to admin or the request.auth.principal is
// user:me@acme.co. Issue two requests, one that specifies the proper user,
// and one that specifies an unexpected user.
func exercise3() {
  fmt.Println("=== Exercise 3: Logical AND/OR ===\n")
  env, _ := cel.NewEnv(
    cel.Types(&rpcpb.AttributeContext_Request{}),
    cel.Variable("request",
      cel.ObjectType("google.rpc.context.AttributeContext.Request"),
    ),
  )

  ast := compile(env,
    `request.auth.claims.group == 'admin'
        || request.auth.principal == 'user:me@acme.co'`,
    cel.BoolType)
  program, _ := env.Program(ast)

  emptyClaims := map[string]string{}
  eval(program, request(auth("user:me@acme.co", emptyClaims), time.Now()))
}

Boş hak talebi ayarıyla kodu çalıştırın

Program yeniden çalıştırıldığında aşağıdaki yeni çıktı gösterilir:

=== Exercise 3: Logical AND/OR ===

request.auth.claims.group == 'admin'
    || request.auth.principal == 'user:me@acme.co'

------ input ------
request = time: <
  seconds: 1569302377
>
auth: <
  principal: "user:me@acme.co"
  claims: <
  >
>

------ result ------
value: true (types.Bool)

Değerlendirme durumunu güncelleme

Ardından değerlendirme durumunu, boş hak talebi grubuyla farklı bir ana hesap iletecek şekilde güncelleyin:

// exercise3 demonstrates how CEL's commutative logical operators work.
//
// Construct an expression which checks whether the request.auth.claims.group
// value is equal to admin or the request.auth.principal is
// user:me@acme.co. Issue two requests, one that specifies the proper user,
// and one that specifies an unexpected user.
func exercise3() {
  fmt.Println("=== Exercise 3: Logical AND/OR ===\n")
  env, _ := cel.NewEnv(
    cel.Types(&rpcpb.AttributeContext_Request{}),
    cel.Variable("request",
      cel.ObjectType("google.rpc.context.AttributeContext.Request"),
    ),
  )

  ast := compile(env,
    `request.auth.claims.group == 'admin'
        || request.auth.principal == 'user:me@acme.co'`,
    cel.BoolType)
  program, _ := env.Program(ast)

  emptyClaims := map[string]string{}
  eval(program, request(auth("other:me@acme.co", emptyClaims), time.Now()))
}

Kodu bir zamanla çalıştırma

Programı tekrar yürütmek

go run .

şu hatayı görmeniz gerekir:

=== Exercise 3: Logical AND/OR ===

request.auth.claims.group == 'admin'
    || request.auth.principal == 'user:me@acme.co'

------ input ------
request = time: <
  seconds: 1588989833
>
auth: <
  principal: "user:me@acme.co"
  claims: <
  >
>

------ result ------
value: true (types.Bool)

------ input ------
request = time: <
  seconds: 1588989833
>
auth: <
  principal: "other:me@acme.co"
  claims: <
  >
>

------ result ------
error: no such key: group

Protobufda, hangi alanların ve türlerin beklediğini biliriz. Map ve json değerlerinde bir anahtarın olup olmayacağını bilmiyoruz. Eksik bir anahtar için güvenli bir varsayılan değer olmadığından CEL varsayılan olarak hata değerini alır.

8. Özel İşlevler

CEL birçok yerleşik işlev içerir ancak özel işlevin kullanışlı olduğu durumlar da vardır. Örneğin, özel işlevler genel koşullar için kullanıcı deneyimini iyileştirmek veya bağlama duyarlı durumu göstermek amacıyla kullanılabilir

Bu alıştırmada, yaygın olarak kullanılan kontrolleri bir arada sunmak için bir işlevi nasıl kullanıma sunacağınızı keşfedeceğiz.

Özel işlevleri çağırma

Öncelikle, bir anahtarın haritada var olup olmadığını ve belirli bir değere sahip olup olmadığını belirleyen contains adlı bir geçersiz kılmayı ayarlamak için kodu oluşturun. İşlev tanımı ve işlev bağlaması için yer tutucular bırakın:

// exercise4 demonstrates how to extend CEL with custom functions.
// Declare a `contains` member function on map types that returns a boolean
// indicating whether the map contains the key-value pair.
func exercise4() {
  fmt.Println("=== Exercise 4: Customization ===\n")
  // Determine whether an optional claim is set to the proper value. The
  // custom map.contains(key, value) function is used as an alternative to:
  //   key in map && map[key] == value
  env, _ := cel.NewEnv(
    cel.Types(&rpcpb.AttributeContext_Request{}),
    // Declare the request.
    cel.Variable("request",
      cel.ObjectType("google.rpc.context.AttributeContext.Request"),
    ),
    // Add the custom function declaration and binding here with cel.Function()
  )
  ast := compile(env,
    `request.auth.claims.contains('group', 'admin')`,
    cel.BoolType)

  // Construct the program plan and provide the 'contains' function impl.
  // Output: false
  program, _ := env.Program(ast)
  emptyClaims := map[string]string{}
  eval(
    program,
    request(auth("user:me@acme.co", emptyClaims), time.Now()),
  )
  fmt.Println()
}  

Kodu çalıştırın ve hatayı anlayın

Kodu yeniden çalıştırdığınızda aşağıdaki hatayı görürsünüz:

ERROR: <input>:1:29: found no matching overload for 'contains' applied to 'map(string, dyn).(string, string)'
 | request.auth.claims.contains('group', 'admin')
 | ............................^

Hatayı düzeltmek için halihazırda istek değişkenini tanımlayan bildirimler listesine contains işlevini eklememiz gerekir.

Aşağıdaki 3 satırı ekleyerek parametre haline getirilmiş bir tür tanımlayın. (Bu durum, herhangi bir işlev aşırı yüklemesinin CEL için olacağı kadar karmaşıktır)

// exercise4 demonstrates how to extend CEL with custom functions.
// Declare a `contains` member function on map types that returns a boolean
// indicating whether the map contains the key-value pair.
func exercise4() {
  fmt.Println("=== Exercise 4: Customization ===\n")
  // Determine whether an optional claim is set to the proper value. The custom
  // map.contains(key, value) function is used as an alternative to:
  //   key in map && map[key] == value

  // Useful components of the type-signature for 'contains'.
  typeParamA := cel.TypeParamType("A")
  typeParamB := cel.TypeParamType("B")
  mapAB := cel.MapType(typeParamA, typeParamB)

  env, _ := cel.NewEnv(
    cel.Types(&rpcpb.AttributeContext_Request{}),
    // Declare the request.
    cel.Variable("request",
      cel.ObjectType("google.rpc.context.AttributeContext.Request"),
    ),
    // Add the custom function declaration and binding here with cel.Function()
  )
  ast := compile(env,
    `request.auth.claims.contains('group', 'admin')`,
    cel.BoolType)

  // Construct the program plan.
  // Output: false
  program, _ := env.Program(ast)
  emptyClaims := map[string]string{}
  eval(program, request(auth("user:me@acme.co", emptyClaims), time.Now()))
  fmt.Println()
}

Özel işlevi ekleme

Şimdi, parametre haline getirilmiş türleri kullanacak yeni bir şunu içerir: işlevi ekleyeceğiz:

// exercise4 demonstrates how to extend CEL with custom functions.
// Declare a `contains` member function on map types that returns a boolean
// indicating whether the map contains the key-value pair.
func exercise4() {
  fmt.Println("=== Exercise 4: Customization ===\n")
  // Determine whether an optional claim is set to the proper value. The custom
  // map.contains(key, value) function is used as an alternative to:
  //   key in map && map[key] == value

  // Useful components of the type-signature for 'contains'.
  typeParamA := cel.TypeParamType("A")
  typeParamB := cel.TypeParamType("B")
  mapAB := cel.MapType(typeParamA, typeParamB)
 
  // Env declaration.
  env, _ := cel.NewEnv(
    cel.Types(&rpcpb.AttributeContext_Request{}),
    // Declare the request.
    cel.Variable("request",
      cel.ObjectType("google.rpc.context.AttributeContext.Request"),
    ),   
    // Declare the custom contains function and its implementation.
    cel.Function("contains",
      cel.MemberOverload(
        "map_contains_key_value",
        []*cel.Type{mapAB, typeParamA, typeParamB},
        cel.BoolType,
        // Provide the implementation using cel.FunctionBinding()
      ),
    ),
  )
  ast := compile(env,
    `request.auth.claims.contains('group', 'admin')`,
    cel.BoolType)

  // Construct the program plan and provide the 'contains' function impl.
  // Output: false
  program, _ := env.Program(ast)
  emptyClaims := map[string]string{}
  eval(program, request(auth("user:me@acme.co", emptyClaims), time.Now()))
  fmt.Println()
}

Hatayı anlamak için programı çalıştırın

Egzersizi yapın. Eksik çalışma zamanı işleviyle ilgili aşağıdaki hatayı görmeniz gerekir:

------ result ------
error: no such overload: contains

cel.FunctionBinding() işlevini kullanarak işlev uygulamasını NewEnv bildirimine ekleyin:

// exercise4 demonstrates how to extend CEL with custom functions.
//
// Declare a contains member function on map types that returns a boolean
// indicating whether the map contains the key-value pair.
func exercise4() {
  fmt.Println("=== Exercise 4: Customization ===\n")
  // Determine whether an optional claim is set to the proper value. The custom
  // map.contains(key, value) function is used as an alternative to:
  //   key in map && map[key] == value

  // Useful components of the type-signature for 'contains'.
  typeParamA := cel.TypeParamType("A")
  typeParamB := cel.TypeParamType("B")
  mapAB := cel.MapType(typeParamA, typeParamB)

  // Env declaration.
  env, _ := cel.NewEnv(
    cel.Types(&rpcpb.AttributeContext_Request{}),
    // Declare the request.
    cel.Variable("request",
      cel.ObjectType("google.rpc.context.AttributeContext.Request"),
    ),   
    // Declare the custom contains function and its implementation.
    cel.Function("contains",
      cel.MemberOverload(
        "map_contains_key_value",
        []*cel.Type{mapAB, typeParamA, typeParamB},
        cel.BoolType,
        cel.FunctionBinding(mapContainsKeyValue)),
    ),
  )
  ast := compile(env, 
    `request.auth.claims.contains('group', 'admin')`, 
    cel.BoolType)

  // Construct the program plan.
  // Output: false
  program, err := env.Program(ast)
  if err != nil {
    glog.Exit(err)
  }

  eval(program, request(auth("user:me@acme.co", emptyClaims), time.Now()))
  claims := map[string]string{"group": "admin"}
  eval(program, request(auth("user:me@acme.co", claims), time.Now()))
  fmt.Println()
}

Programın başarıyla çalışması gerekir:

=== Exercise 4: Custom Functions ===

request.auth.claims.contains('group', 'admin')

------ input ------
request = time: <
  seconds: 1569302377
>
auth: <
  principal: "user:me@acme.co"
  claims: <
  >
>

------ result ------
value: false (types.Bool)

Hak talebi varsa ne olur?

Fazladan kredi kazanmak için girişteki yönetici hak talebini ayarlayarak aşırı yüklenmesinin, hak talebi mevcut olduğunda da "true" (doğru) değerini döndürdüğünü doğrulayın. Aşağıdaki çıkışı göreceksiniz:

=== Exercise 4: Customization ===

request.auth.claims.contains('group','admin')

------ input ------
request = time: <
  seconds: 1588991010
>
auth: <
  principal: "user:me@acme.co"
  claims: <
  >
>

------ result ------
value: false (types.Bool)

------ input ------
request = time: <
  seconds: 1588991010
>
auth: <
  principal: "user:me@acme.co"
  claims: <
    fields: <
      key: "group"
      value: <
        string_value: "admin"
      >
    >
  >
>

------ result ------
value: true (types.Bool)

Devam etmeden önce mapContainsKeyValue işlevinin kendisini incelemeniz yararlı olacaktır:

// mapContainsKeyValue implements the custom function:
//   map.contains(key, value) -> bool.
func mapContainsKeyValue(args ...ref.Val) ref.Val {
  // The declaration of the function ensures that only arguments which match
  // the mapContainsKey signature will be provided to the function.
  m := args[0].(traits.Mapper)

  // CEL has many interfaces for dealing with different type abstractions.
  // The traits.Mapper interface unifies field presence testing on proto
  // messages and maps.
  key := args[1]
  v, found := m.Find(key)

  // If not found and the value was non-nil, the value is an error per the
  // `Find` contract. Propagate it accordingly. Such an error might occur with
  // a map whose key-type is listed as 'dyn'.
  if !found {
    if v != nil {
      return types.ValOrErr(v, "unsupported key type")
    }
    // Return CEL False if the key was not found.
    return types.False
  }
  // Otherwise whether the value at the key equals the value provided.
  return v.Equal(args[2])
}

En fazla genişletme kolaylığını sağlamak için özel işlevlere ilişkin imza, ref.Val türündeki bağımsız değişkenlerin olmasını bekler. Buradaki karşılığında, genişletme kolaylığının, tüm değer türlerinin düzgün bir şekilde işlenmesini sağlama konusunda uygulayıcıya yük oluşturmasıdır. Giriş bağımsız değişkeni türleri veya sayısı, işlev bildirimiyle eşleşmediğinde no such overload hatası döndürülür.

cel.FunctionBinding(), çalışma zamanı sözleşmesinin ortamda tür kontrolü yapılmış bildirimle eşleştiğinden emin olmak için bir çalışma zamanı türü koruması ekler.

9. JSON oluşturma

CEL, JSON gibi boole olmayan çıkışlar da üretebilir. Fonksiyonunuza aşağıdakini ekleyin:

// exercise5 covers how to build complex objects as CEL literals.
//
// Given the input now, construct a JWT with an expiry of 5 minutes.
func exercise5() {
    fmt.Println("=== Exercise 5: Building JSON ===\n")
    env, _ := cel.NewEnv(
      // Declare the 'now' variable as a Timestamp.
      // cel.Variable("now", cel.TimestampType),
    )
    // Note the quoted keys in the CEL map literal. For proto messages the
    // field names are unquoted as they represent well-defined identifiers.
    ast := compile(env, `
        {'sub': 'serviceAccount:delegate@acme.co',
         'aud': 'my-project',
         'iss': 'auth.acme.com:12350',
         'iat': now,
         'nbf': now,
         'exp': now + duration('300s'),
         'extra_claims': {
             'group': 'admin'
         }}`,
        cel.MapType(cel.StringType, cel.DynType))

    program, _ := env.Program(ast)
    out, _, _ := eval(
        program,
        map[string]interface{}{
            "now": &tpb.Timestamp{Seconds: time.Now().Unix()},
        },
    )
    fmt.Printf("------ type conversion ------\n%v\n", out)
    fmt.Println()
}

Kodu çalıştırma

Kodu yeniden çalıştırdığınızda aşağıdaki hatayı görürsünüz:

ERROR: <input>:5:11: undeclared reference to 'now' (in container '')
 |    'iat': now,
 | ..........^
... and more ...

cel.NewEnv() öğesine cel.TimestampType türündeki now değişkeni için bir bildirim ekleyip tekrar çalıştırın:

// exercise5 covers how to build complex objects as CEL literals.
//
// Given the input now, construct a JWT with an expiry of 5 minutes.
func exercise5() {
    fmt.Println("=== Exercise 5: Building JSON ===\n")
    env, _ := cel.NewEnv(
      cel.Variable("now", cel.TimestampType),
    )
    // Note the quoted keys in the CEL map literal. For proto messages the
    // field names are unquoted as they represent well-defined identifiers.
    ast := compile(env, `
        {'sub': 'serviceAccount:delegate@acme.co',
         'aud': 'my-project',
         'iss': 'auth.acme.com:12350',
         'iat': now,
         'nbf': now,
         'exp': now + duration('300s'),
         'extra_claims': {
             'group': 'admin'
         }}`,
        cel.MapType(cel.StringType, cel.DynType))

     // Hint:
     // Convert `out` to JSON using the valueToJSON() helper method.
     // The valueToJSON() function calls ConvertToNative(&structpb.Value{})
     // to adapt the CEL value to a protobuf JSON representation and then
     // uses the jsonpb.Marshaler utilities on the output to render the JSON
     // string.
    program, _ := env.Program(ast)
    out, _, _ := eval(
        program,
        map[string]interface{}{
            "now": time.Now(),
        },
    )
    fmt.Printf("------ type conversion ------\n%v\n", out)
    fmt.Println()
}

Kodu yeniden çalıştırdığınızda işlemin başarılı olması gerekir:

=== Exercise 5: Building JSON ===

  {'aud': 'my-project',
   'exp': now + duration('300s'),
   'extra_claims': {
    'group': 'admin'
   },
   'iat': now,
   'iss': 'auth.acme.com:12350',
   'nbf': now,
   'sub': 'serviceAccount:delegate@acme.co'
   }

------ input ------
now = seconds: 1569302377

------ result ------
value: &{0xc0002eaf00 map[aud:my-project exp:{0xc0000973c0} extra_claims:0xc000097400 iat:{0xc000097300} iss:auth.acme.com:12350 nbf:{0xc000097300} sub:serviceAccount:delegate@acme.co] {0x8c8f60 0xc00040a6c0 21}} (*types.baseMap)

------ type conversion ------
&{0xc000313510 map[aud:my-project exp:{0xc000442510} extra_claims:0xc0004acdc0 iat:{0xc000442450} iss:auth.acme.com:12350 nbf:{0xc000442450} sub:serviceAccount:delegate@acme.co] {0x9d0ce0 0xc0004424b0 21}}

Program çalışır, ancak out çıkış değerinin açıkça JSON'a dönüştürülmesi gerekir. Bu durumda dahili CEL temsili, yalnızca JSON'un destekleyebileceği veya iyi bilinen bir Proto'dan JSON eşlemeye sahip olan türleri ifade etmesi nedeniyle JSON dönüştürülebilirdir.

// exercise5 covers how to build complex objects as CEL literals.
//
// Given the input now, construct a JWT with an expiry of 5 minutes.
func exercise5() {
    fmt.Println("=== Exercise 5: Building JSON ===\n")
...
    fmt.Printf("------ type conversion ------\n%v\n", valueToJSON(out))
    fmt.Println()
}

Tür, codelab.go dosyasında valueToJSON yardımcı işlevi kullanılarak dönüştürüldükten sonra şu ek çıkışı görürsünüz:

------ type conversion ------
  {
   "aud": "my-project",
   "exp": "2019-10-13T05:54:29Z",
   "extra_claims": {
      "group": "admin"
     },
   "iat": "2019-10-13T05:49:29Z",
   "iss": "auth.acme.com:12350",
   "nbf": "2019-10-13T05:49:29Z",
   "sub": "serviceAccount:delegate@acme.co"
  }

10. Proto'lar Oluşturma

CEL, uygulamada derlenen her mesaj türü için protobuf mesajları oluşturabilir. jwt girişinden google.rpc.context.AttributeContext.Request oluşturmak için işlevi ekleyin

// exercise6 describes how to build proto message types within CEL.
//
// Given an input jwt and time now construct a
// google.rpc.context.AttributeContext.Request with the time and auth
// fields populated according to the go/api-attributes specification.
func exercise6() {
  fmt.Println("=== Exercise 6: Building Protos ===\n")

  // Construct an environment and indicate that the container for all references
  // within the expression is `google.rpc.context.AttributeContext`.
  requestType := &rpcpb.AttributeContext_Request{}
  env, _ := cel.NewEnv(
      // Add cel.Container() option for 'google.rpc.context.AttributeContext'
      cel.Types(requestType),
      // Add cel.Variable() option for 'jwt' as a map(string, Dyn) type
      // and for 'now' as a timestamp.
  )

  // Compile the Request message construction expression and validate that
  // the resulting expression type matches the fully qualified message name.
  //
  // Note: the field names within the proto message types are not quoted as they
  // are well-defined names composed of valid identifier characters. Also, note
  // that when building nested proto objects, the message name needs to prefix 
  // the object construction.
  ast := compile(env, `
    Request{
        auth: Auth{
            principal: jwt.iss + '/' + jwt.sub,
            audiences: [jwt.aud],
            presenter: 'azp' in jwt ? jwt.azp : "",
            claims: jwt
        },
        time: now
    }`,
    cel.ObjectType("google.rpc.context.AttributeContext.Request"),
  )
  program, _ := env.Program(ast)

  // Construct the message. The result is a ref.Val that returns a dynamic
  // proto message.
  out, _, _ := eval(
      program,
      map[string]interface{}{
          "jwt": map[string]interface{}{
              "sub": "serviceAccount:delegate@acme.co",
              "aud": "my-project",
              "iss": "auth.acme.com:12350",
              "extra_claims": map[string]string{
                  "group": "admin",
              },
          },
          "now": time.Now(),
      },
  )

  // Hint: Unwrap the CEL value to a proto. Make sure to use the
  // `ConvertToNative(reflect.TypeOf(requestType))` to convert the dynamic proto
  // message to the concrete proto message type expected.
  fmt.Printf("------ type unwrap ------\n%v\n", out)
  fmt.Println()
}

Kodu çalıştırma

Kodu yeniden çalıştırdığınızda aşağıdaki hatayı görürsünüz:

ERROR: <input>:2:10: undeclared reference to 'Request' (in container '')
 |   Request{
 | .........^

Kapsayıcı, temelde bir ad alanının veya paketin eşdeğeridir, ancak protobuf mesaj adı kadar ayrıntılı da olabilir. CEL kapsayıcıları, belirli bir değişken, işlev veya tür adının nerede tanımlandığını belirlemek için Protobuf ve C++ ile aynı ad alanı çözümleme kurallarını kullanır.

google.rpc.context.AttributeContext kapsayıcısı dikkate alındığında, tür denetleyici ve değerlendirici, tüm değişkenler, türler ve işlevler için aşağıdaki tanımlayıcı adlarını dener:

  • google.rpc.context.AttributeContext.<id>
  • google.rpc.context.<id>
  • google.rpc.<id>
  • google.<id>
  • <id>

Mutlak adlarda değişkenin, türün veya işlev referansının önüne bir nokta koyun. Bu örnekte .<id> ifadesi, önce kapsayıcı içinde kontrol yapmadan yalnızca üst düzey <id> tanımlayıcısını arar.

CEL ortamına cel.Container("google.rpc.context.AttributeContext") seçeneğini belirtip tekrar çalıştırmayı deneyin:

// exercise6 describes how to build proto message types within CEL.
//
// Given an input jwt and time now construct a
// google.rpc.context.AttributeContext.Request with the time and auth
// fields populated according to the go/api-attributes specification.
func exercise6() {
  fmt.Println("=== Exercise 6: Building Protos ===\n")
  // Construct an environment and indicate that the container for all references
  // within the expression is `google.rpc.context.AttributeContext`.
  requestType := &rpcpb.AttributeContext_Request{}
  env, _ := cel.NewEnv(
    // Add cel.Container() option for 'google.rpc.context.AttributeContext'
    cel.Container("google.rpc.context.AttributeContext.Request"),
    cel.Types(requestType),
    // Later, add cel.Variable() options for 'jwt' as a map(string, Dyn) type
    // and for 'now' as a timestamp.
    // cel.Variable("now", cel.TimestampType),
    // cel.Variable("jwt", cel.MapType(cel.StringType, cel.DynType)),
  )

 ...
}

Çıkış şöyle olmalıdır:

ERROR: <input>:4:16: undeclared reference to 'jwt' (in container 'google.rpc.context.AttributeContext')
 |     principal: jwt.iss + '/' + jwt.sub,
 | ...............^

... ve daha pek çok hata...

Ardından, jwt ve now değişkenlerini tanımlayın. Programın beklendiği gibi çalışması gerekir:

=== Exercise 6: Building Protos ===

  Request{
   auth: Auth{
    principal: jwt.iss + '/' + jwt.sub,
    audiences: [jwt.aud],
    presenter: 'azp' in jwt ? jwt.azp : "",
    claims: jwt
   },
   time: now
  }

------ input ------
jwt = {
  "aud": "my-project",
  "extra_claims": {
    "group": "admin"
  },
  "iss": "auth.acme.com:12350",
  "sub": "serviceAccount:delegate@acme.co"
}
now = seconds: 1588993027

------ result ------
value: &{0xc000485270 0xc000274180 {0xa6ee80 0xc000274180 22} 0xc0004be140 0xc0002bf700 false} (*types.protoObj)

----- type unwrap ----
&{0xc000485270 0xc000274180 {0xa6ee80 0xc000274180 22} 0xc0004be140 0xc0002bf700 false}

Ekstra kredi için, sonucun nasıl değiştiğini görmek üzere iletide out.Value() öğesini çağırarak türün sarmalamasını açmanız da gerekir.

11. Makrolar

Makrolar, ayrıştırma zamanında CEL programını değiştirmek için kullanılabilir. Makrolar bir çağrı imzasını eşleştirir ve yeni bir alt ifade AST oluşturmak için giriş çağrısını ve bağımsız değişkenlerini işler.

Makrolar, AST'de doğrudan CEL'de yazılamayan karmaşık mantığı uygulamak için kullanılabilir. Örneğin, has makrosu alan varlığı testini etkinleştirir. "Var" ve "Tümü" gibi anlama makroları, bir giriş listesi veya harita üzerinde bir işlev çağrısını sınırlı yinelemeyle değiştirir. Her iki kavram da söz dizimsel düzeyde mümkün değildir ancak makro genişletmeler yoluyla mümkündür.

Bir sonraki egzersizi ekleyin ve çalıştırın:

// exercise7 introduces macros for dealing with repeated fields and maps.
//
// Determine whether the jwt.extra_claims has at least one key that starts
// with the group prefix, and ensure that all group-like keys have list
// values containing only strings that end with '@acme.co`.
func exercise7() {
    fmt.Println("=== Exercise 7: Macros ===\n")
    env, _ := cel.NewEnv(
      cel.ClearMacros(),
      cel.Variable("jwt", cel.MapType(cel.StringType, cel.DynType)),
    )
    ast := compile(env,
        `jwt.extra_claims.exists(c, c.startsWith('group'))
                && jwt.extra_claims
                      .filter(c, c.startsWith('group'))
                      .all(c, jwt.extra_claims[c]
                                 .all(g, g.endsWith('@acme.co')))`,
        cel.BoolType)
    program, _ := env.Program(ast)

    // Evaluate a complex-ish JWT with two groups that satisfy the criteria.
    // Output: true.
    eval(program,
        map[string]interface{}{
            "jwt": map[string]interface{}{
                "sub": "serviceAccount:delegate@acme.co",
                "aud": "my-project",
                "iss": "auth.acme.com:12350",
                "extra_claims": map[string][]string{
                    "group1": {"admin@acme.co", "analyst@acme.co"},
                    "labels": {"metadata", "prod", "pii"},
                    "groupN": {"forever@acme.co"},
                },
            },
        })

    fmt.Println()
}

Aşağıdaki hataları görmeniz gerekir:

ERROR: <input>:1:25: undeclared reference to 'c' (in container '')
 | jwt.extra_claims.exists(c, c.startsWith('group'))
 | ........................^

... ve çok daha fazlası ...

Bu hatalar, makrolar henüz etkinleştirilmediği için oluşur. Makroları etkinleştirmek için cel.ClearMacros() değişkenini kaldırıp tekrar çalıştırın:

=== Exercise 7: Macros ===

jwt.extra_claims.exists(c, c.startsWith('group'))
    && jwt.extra_claims
       .filter(c, c.startsWith('group'))
       .all(c, jwt.extra_claims[c]
              .all(g, g.endsWith('@acme.co')))

------ input ------
jwt = {
  "aud": "my-project",
  "extra_claims": {
    "group1": [
      "admin@acme.co",
      "analyst@acme.co"
    ],
    "groupN": [
      "forever@acme.co"
    ],
    "labels": [
      "metadata",
      "prod",
      "pii"
    ]
  },
  "iss": "auth.acme.com:12350",
  "sub": "serviceAccount:delegate@acme.co"
}

------ result ------
value: true (types.Bool)

Şu anda desteklenen makrolar şunlardır:

Makro

İmza

Açıklama

tümü

r.all(var; koşul)

Koşulun r aralığındaki tüm varyantlar için doğru olarak değerlendirilip değerlendirilmediğini test edin.

mevcut

r.exists(var; cond)

Koşulların, r aralığındaki herhangi bir değişken için doğru olarak değerlendirilip değerlendirilmediğini test edin.

exists_one

r.exists_one(var, koşul)

Koşulun r aralığında yalnızca bir değişken için doğru olarak değerlendirilip değerlendirilmediğini test edin.

filtrele

r.filter(var; koşul)

Listeler için, aralıktaki her bir öğenin koşul koşullarını karşıladığı yeni bir liste oluşturun. Haritalarda, r aralığındaki her bir anahtarın koşul durumunu karşıladığı yeni bir liste oluşturun.

harita

r.map(var; expr)

r aralığındaki her var değerinin expr ile dönüştürüldüğü yeni bir liste oluşturun.

r.map(var; cond; expr)

İki bağımsız değişkenli harita ile aynı olsa da değer dönüştürülmeden önce koşullu koşul filtresi içerir.

sahip

has(a.b)

A değerinde b için varlık testi : Haritalar için json testleri tanımıdır. Protolar için varsayılan olmayan temel değeri veya bir ya da ayarlanmış mesaj alanını test eder.

r aralığı map türünde olduğunda var eşleme anahtarı, list türü değerler için var liste öğesi değeri olur. all, exists, exists_one, filter ve map makroları, girişin boyutuyla sınırlanan her iterasyon için bir AST yeniden yazma işlemi gerçekleştirir.

Sınırlı kavramlar, CEL programlarının Turing'de tam olarak ilerlememesini sağlar. Ancak bunlar girdiye göre süper doğrusal zamanlara göre değerlendirilir. Bu makroları ölçülü bir şekilde kullanın veya hiç kullanmayın. Anlayışın yoğun bir şekilde kullanılması, genellikle özel bir işlevin daha iyi kullanıcı deneyimi ve daha iyi performans sağlayacağına dair iyi bir göstergedir.

12. İnce ayar

Şu anda CEL-Go'ya özel olan, ancak diğer CEL uygulamalarına yönelik gelecekteki planların göstergesi olan birkaç özellik mevcuttur. Aşağıdaki alıştırmada, aynı AST için farklı program planları açıklanmaktadır:

// exercise8 covers features of CEL-Go which can be used to improve
// performance and debug evaluation behavior.
//
// Turn on the optimization, exhaustive eval, and state tracking
// ProgramOption flags to see the impact on evaluation behavior.
func exercise8() {
    fmt.Println("=== Exercise 8: Tuning ===\n")
    // Declare the x and 'y' variables as input into the expression.
    env, _ := cel.NewEnv(
        cel.Variable("x", cel.IntType),
        cel.Variable("y", cel.UintType),
    )
    ast := compile(env,
        `x in [1, 2, 3, 4, 5] && type(y) == uint`,
        cel.BoolType)

    // Try the different cel.EvalOptions flags when evaluating this AST for
    // the following use cases:
    // - cel.OptOptimize: optimize the expression performance.
    // - cel.OptExhaustiveEval: turn off short-circuiting.
    // - cel.OptTrackState: track state and compute a residual using the
    //   interpreter.PruneAst function.
    program, _ := env.Program(ast)
    eval(program, cel.NoVars())

    fmt.Println()
}

Optimize

Optimizasyon işareti etkinleştirildiğinde CEL, değişmez değerleri önceden oluşturmak ve eşlemek için ek zaman harcar ve gerçek bir grup üyelik testi olması amacıyla operatör gibi belirli çağrıları optimize eder:

    // Turn on optimization.
    trueVars := map[string]interface{}{"x": int64(4), "y": uint64(2)}
    program, _ := env.Program(ast, cel.EvalOptions(cel.OptOptimize))
    // Try benchmarking with the optimization flag on and off.
    eval(program, trueVars)

Aynı program farklı girdilere göre birçok kez değerlendiriliyorsa optimizasyon iyi bir seçimdir. Ancak program yalnızca bir kez değerlendirilecekse optimizasyon yalnızca ek yük getirir.

Yorucu Değerlendirme

Kapsamlı Değerlendirme, ifade değerlendirmesindeki her adımda gözlemlenen değerle ilgili bilgi sağladığı için ifade değerlendirme davranışında hata ayıklama konusunda yararlı olabilir.

    // Turn on exhaustive eval to see what the evaluation state looks like.
    // The input is structure to show a false on the first branch, and true
    // on the second.
    falseVars := map[string]interface{}{"x": int64(6), "y": uint64(2)}
    program, _ = env.Program(ast, cel.EvalOptions(cel.OptExhaustiveEval))
    eval(program, falseVars)

Her ifade kimliği için ifade değerlendirme durumunun yer aldığı bir liste gösterilir:

------ eval states ------
1: 6 (types.Int)
2: false (types.Bool)
3: &{0xc0000336b0 [1 2 3 4 5] {0x89f020 0xc000434760 151}} (*types.baseList)
4: 1 (types.Int)
5: 2 (types.Int)
6: 3 (types.Int)
7: 4 (types.Int)
8: 5 (types.Int)
9: uint (*types.Type)
10: 2 (types.Uint)
11: true (types.Bool)
12: uint (*types.Type)
13: false (types.Bool)

İfade kimliği 2, birincideki in operatörünün sonucuna karşılık gelir. dal ve ifade kimliği 11, ikincideki == operatörüne karşılık gelir. Normal değerlendirmede, 2 hesaplandıktan sonra ifade kısa devreye girerdi. y uint olmasaydı durum, ifadenin neden başarısız olacağını sadece bir değil, iki neden gösterirdi.

13. Neler ele alındı?

İfade motoruna ihtiyacınız varsa CEL'yi kullanabilirsiniz. CEL, performansın kritik olduğu kullanıcı yapılandırması yürütmesi gereken projeler için idealdir.

Önceki alıştırmalarda verilerinizi CEL'e aktarma ve çıktıyı ya da kararı tekrar alma konusunda rahat hissettiğinizi umuyoruz.

Boole kararından JSON ve Protobuffer mesajları oluşturmaya kadar yapabileceğiniz işlemler hakkında bilgi sahibi olduğunuzu umuyoruz.

İfadelerle nasıl çalışacağınızı ve ne işe yaradığını anladığınızı umarız. Bu süreyi uzatmanın yaygın yollarını da biliyoruz.