Skip to content

Commit

Permalink
security/authorization: util function for converting CEL expression s…
Browse files Browse the repository at this point in the history
…tring (#3822)

* security/authorization: add the util function to compile string cel expr
  • Loading branch information
ZhenLian authored Aug 19, 2020
1 parent 0f73133 commit f640ae6
Show file tree
Hide file tree
Showing 3 changed files with 185 additions and 1 deletion.
2 changes: 1 addition & 1 deletion security/authorization/engine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import (
"google.golang.org/protobuf/proto"
)

var logger = grpclog.Component("channelz")
var logger = grpclog.Component("authorization")

var stringAttributeMap = map[string]func(*AuthorizationArgs) (string, error){
"request.url_path": (*AuthorizationArgs).getRequestURLPath,
Expand Down
63 changes: 63 additions & 0 deletions security/authorization/util/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
*
* Copyright 2020 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package engine

import (
"errors"

expr "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
"google.golang.org/protobuf/proto"

"github.com/google/cel-go/cel"
"github.com/google/cel-go/checker/decls"
)

func compileCel(env *cel.Env, expr string) (*cel.Ast, error) {
ast, iss := env.Parse(expr)
// Report syntactic errors, if present.
if iss.Err() != nil {
return nil, iss.Err()
}
// Type-check the expression for correctness.
checked, iss := env.Check(ast)
if iss.Err() != nil {
return nil, iss.Err()
}
// Check the result type is a Boolean.
if !proto.Equal(checked.ResultType(), decls.Bool) {
return nil, errors.New("failed to compile CEL string: get non-bool value")
}
return checked, nil
}

func compileStringToCheckedExpr(expr string, declarations []*expr.Decl) (*expr.CheckedExpr, error) {
env, err := cel.NewEnv(cel.Declarations(declarations...))
if err != nil {
return nil, err
}
checked, err := compileCel(env, expr)
if err != nil {
return nil, err
}
checkedExpr, err := cel.AstToCheckedExpr(checked)
if err != nil {
return nil, err
}
return checkedExpr, nil
}
121 changes: 121 additions & 0 deletions security/authorization/util/util_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
*
* Copyright 2020 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package engine

import (
"testing"

expr "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
"google.golang.org/grpc/internal/grpctest"

"github.com/google/cel-go/cel"
"github.com/google/cel-go/checker/decls"
)

type s struct {
grpctest.Tester
}

func Test(t *testing.T) {
grpctest.RunSubTests(t, s{})
}

func (s) TestStringConvert(t *testing.T) {
declarations := []*expr.Decl{
decls.NewIdent("request.url_path", decls.String, nil),
decls.NewIdent("request.host", decls.String, nil),
decls.NewIdent("connection.uri_san_peer_certificate", decls.String, nil),
}
env, err := cel.NewEnv()
if err != nil {
t.Fatalf("Failed to create the CEL environment")
}
for _, test := range []struct {
desc string
wantEvalOutcome bool
wantParsingError bool
wantEvalError bool
expr string
authzArgs map[string]interface{}
}{
{
desc: "single primitive match",
wantEvalOutcome: true,
expr: "request.url_path.startsWith('/pkg.service/test')",
authzArgs: map[string]interface{}{"request.url_path": "/pkg.service/test"},
},
{
desc: "single compare match",
wantEvalOutcome: true,
expr: "connection.uri_san_peer_certificate == 'cluster/ns/default/sa/admin'",
authzArgs: map[string]interface{}{"connection.uri_san_peer_certificate": "cluster/ns/default/sa/admin"},
},
{
desc: "single primitive no match",
wantEvalOutcome: false,
expr: "request.url_path.startsWith('/pkg.service/test')",
authzArgs: map[string]interface{}{"request.url_path": "/source/pkg.service/test"},
},
{
desc: "primitive and compare match",
wantEvalOutcome: true,
expr: "request.url_path == '/pkg.service/test' && connection.uri_san_peer_certificate == 'cluster/ns/default/sa/admin'",
authzArgs: map[string]interface{}{"request.url_path": "/pkg.service/test",
"connection.uri_san_peer_certificate": "cluster/ns/default/sa/admin"},
},
{
desc: "parse error field not present in environment",
wantParsingError: true,
expr: "request.source_path.startsWith('/pkg.service/test')",
authzArgs: map[string]interface{}{"request.url_path": "/pkg.service/test"},
},
{
desc: "eval error argument not included in environment",
wantEvalError: true,
expr: "request.url_path.startsWith('/pkg.service/test')",
authzArgs: map[string]interface{}{"request.source_path": "/pkg.service/test"},
},
} {
test := test
t.Run(test.desc, func(t *testing.T) {
checked, err := compileStringToCheckedExpr(test.expr, declarations)
if (err != nil) != test.wantParsingError {
t.Fatalf("Error mismatch in conversion, wantParsingError =%v, got %v", test.wantParsingError, err != nil)
}
if test.wantParsingError {
return
}
ast := cel.CheckedExprToAst(checked)
program, err := env.Program(ast)
if err != nil {
t.Fatalf("Failed to create CEL Program: %v", err)
}
eval, _, err := program.Eval(test.authzArgs)
if (err != nil) != test.wantEvalError {
t.Fatalf("Error mismatch in evaluation, wantEvalError =%v, got %v", test.wantEvalError, err != nil)
}
if test.wantEvalError {
return
}
if eval.Value() != test.wantEvalOutcome {
t.Fatalf("Error in evaluating converted CheckedExpr: want %v, got %v", test.wantEvalOutcome, eval.Value())
}
})
}
}

0 comments on commit f640ae6

Please sign in to comment.