security/authorization: Added CEL-based authorization engine (#3707)

* Add CEL-based authorization engine in a new module under security/authorization
This commit is contained in:
Elizabeth Zou 2020-08-07 11:49:45 -05:00 committed by GitHub
parent 7419b444ee
commit d3e3e7a46f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 762 additions and 0 deletions

View File

@ -21,6 +21,7 @@ test: testdeps
testsubmodule: testdeps
cd security/advancedtls && go test -cpu 1,4 -timeout 7m google.golang.org/grpc/security/advancedtls/...
cd security/authorization && go test -cpu 1,4 -timeout 7m google.golang.org/grpc/security/authorization/...
testdeps:
go get -d -v -t google.golang.org/grpc/...

View File

@ -0,0 +1,374 @@
/*
* 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 (
"fmt"
"net"
"strconv"
pb "github.com/envoyproxy/go-control-plane/envoy/config/rbac/v2"
cel "github.com/google/cel-go/cel"
"github.com/google/cel-go/checker/decls"
types "github.com/google/cel-go/common/types"
interpreter "github.com/google/cel-go/interpreter"
expr "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/peer"
"google.golang.org/protobuf/proto"
)
var logger = grpclog.Component("channelz")
var stringAttributeMap = map[string]func(*AuthorizationArgs) (string, error){
"request.url_path": (*AuthorizationArgs).getRequestURLPath,
"request.host": (*AuthorizationArgs).getRequestHost,
"request.method": (*AuthorizationArgs).getRequestMethod,
"source.address": (*AuthorizationArgs).getSourceAddress,
"destination.address": (*AuthorizationArgs).getDestinationAddress,
"connection.uri_san_peer_certificate": (*AuthorizationArgs).getURISanPeerCertificate,
"source.principal": (*AuthorizationArgs).getSourcePrincipal,
}
var intAttributeMap = map[string]func(*AuthorizationArgs) (int, error){
"source.port": (*AuthorizationArgs).getSourcePort,
"destination.port": (*AuthorizationArgs).getDestinationPort,
}
// activationImpl is an implementation of interpreter.Activation.
// An Activation is the primary mechanism by which a caller supplies input into a CEL program.
type activationImpl struct {
dict map[string]interface{}
}
// ResolveName returns a value from the activation by qualified name, or false if the name
// could not be found.
func (activation activationImpl) ResolveName(name string) (interface{}, bool) {
result, ok := activation.dict[name]
return result, ok
}
// Parent returns the parent of the current activation, may be nil.
// If non-nil, the parent will be searched during resolve calls.
func (activation activationImpl) Parent() interpreter.Activation {
return activationImpl{}
}
// AuthorizationArgs is the input of the CEL-based authorization engine.
type AuthorizationArgs struct {
md metadata.MD
peerInfo *peer.Peer
fullMethod string
}
// newActivation converts AuthorizationArgs into the activation for CEL.
func newActivation(args *AuthorizationArgs) interpreter.Activation {
// Fill out evaluation map, only adding the attributes that can be extracted.
evalMap := make(map[string]interface{})
for key, function := range stringAttributeMap {
val, err := function(args)
if err == nil {
evalMap[key] = val
}
}
for key, function := range intAttributeMap {
val, err := function(args)
if err == nil {
evalMap[key] = val
}
}
val, err := args.getRequestHeaders()
if err == nil {
evalMap["request.headers"] = val
}
// Convert evaluation map to activation.
return activationImpl{dict: evalMap}
}
func (args *AuthorizationArgs) getRequestURLPath() (string, error) {
if args.fullMethod == "" {
return "", fmt.Errorf("authorization args doesn't have a valid request url path")
}
return args.fullMethod, nil
}
func (args *AuthorizationArgs) getRequestHost() (string, error) {
// TODO(@zhenlian): fill out attribute extraction for request.host
return "", fmt.Errorf("authorization args doesn't have a valid request host")
}
func (args *AuthorizationArgs) getRequestMethod() (string, error) {
// TODO(@zhenlian): fill out attribute extraction for request.method
return "", fmt.Errorf("authorization args doesn't have a valid request method")
}
func (args *AuthorizationArgs) getRequestHeaders() (map[string]string, error) {
// TODO(@zhenlian): fill out attribute extraction for request.headers
return nil, fmt.Errorf("authorization args doesn't have valid request headers")
}
func (args *AuthorizationArgs) getSourceAddress() (string, error) {
if args.peerInfo == nil {
return "", fmt.Errorf("authorization args doesn't have a valid source address")
}
addr := args.peerInfo.Addr.String()
host, _, err := net.SplitHostPort(addr)
if err != nil {
return "", err
}
return host, nil
}
func (args *AuthorizationArgs) getSourcePort() (int, error) {
if args.peerInfo == nil {
return 0, fmt.Errorf("authorization args doesn't have a valid source port")
}
addr := args.peerInfo.Addr.String()
_, port, err := net.SplitHostPort(addr)
if err != nil {
return 0, err
}
return strconv.Atoi(port)
}
func (args *AuthorizationArgs) getDestinationAddress() (string, error) {
// TODO(@zhenlian): fill out attribute extraction for destination.address
return "", fmt.Errorf("authorization args doesn't have a valid destination address")
}
func (args *AuthorizationArgs) getDestinationPort() (int, error) {
// TODO(@zhenlian): fill out attribute extraction for destination.port
return 0, fmt.Errorf("authorization args doesn't have a valid destination port")
}
func (args *AuthorizationArgs) getURISanPeerCertificate() (string, error) {
// TODO(@zhenlian): fill out attribute extraction for connection.uri_san_peer_certificate
return "", fmt.Errorf("authorization args doesn't have a valid URI in SAN field of the peer certificate")
}
func (args *AuthorizationArgs) getSourcePrincipal() (string, error) {
// TODO(@zhenlian): fill out attribute extraction for source.principal
return "", fmt.Errorf("authorization args doesn't have a valid source principal")
}
// Decision represents different authorization decisions a CEL-based
// authorization engine can return.
type Decision int32
const (
// DecisionAllow indicates allowing the RPC to go through.
DecisionAllow Decision = iota
// DecisionDeny indicates denying the RPC from going through.
DecisionDeny
// DecisionUnknown indicates that there is insufficient information to
// determine whether or not an RPC call is authorized.
DecisionUnknown
)
// String returns the string representation of a Decision object.
func (d Decision) String() string {
return [...]string{"DecisionAllow", "DecisionDeny", "DecisionUnknown"}[d]
}
// AuthorizationDecision is the output of CEL-based authorization engines.
// If decision is allow or deny, policyNames will either contain the names of
// all the policies matched in the engine that permitted the action, or be
// empty as the decision was made after all conditions evaluated to false.
// If decision is unknown, policyNames will contain the list of policies that
// evaluated to unknown.
type AuthorizationDecision struct {
decision Decision
policyNames []string
}
// Converts an expression to a parsed expression, with SourceInfo nil.
func exprToParsedExpr(condition *expr.Expr) *expr.ParsedExpr {
return &expr.ParsedExpr{Expr: condition}
}
// Converts an expression to a CEL program.
func exprToProgram(condition *expr.Expr, env *cel.Env) (cel.Program, error) {
// Converts condition to ParsedExpr by setting SourceInfo empty.
pexpr := exprToParsedExpr(condition)
// pretend cel.ExprToAst exists
ast, iss := env.Check(cel.ParsedExprToAst(pexpr))
if iss.Err() != nil {
return nil, iss.Err()
}
// Check that the expression will evaluate to a boolean.
if !proto.Equal(ast.ResultType(), decls.Bool) {
return nil, fmt.Errorf("expected boolean condition")
}
// Build the program plan.
return env.Program(ast,
cel.EvalOptions(cel.OptOptimize),
)
}
// policyEngine is the struct for an engine created from one RBAC proto.
type policyEngine struct {
action pb.RBAC_Action
programs map[string]cel.Program
}
// Creates a new policyEngine from an RBAC policy proto.
func newPolicyEngine(rbac *pb.RBAC, env *cel.Env) (*policyEngine, error) {
if rbac == nil {
return nil, nil
}
action := rbac.Action
programs := make(map[string]cel.Program)
for policyName, policy := range rbac.Policies {
prg, err := exprToProgram(policy.Condition, env)
if err != nil {
return &policyEngine{}, fmt.Errorf("failed to create CEL program from condition: %v", err)
}
programs[policyName] = prg
}
return &policyEngine{action, programs}, nil
}
// Returns the decision of an engine based on whether or not AuthorizationArgs is a match,
// i.e. if engine's action is ALLOW and match is true, we will return DecisionAllow;
// if engine's action is ALLOW and match is false, we will return DecisionDeny.
func getDecision(engine *policyEngine, match bool) Decision {
if engine.action == pb.RBAC_ALLOW && match || engine.action == pb.RBAC_DENY && !match {
return DecisionAllow
}
return DecisionDeny
}
// Returns the authorization decision of a single policy engine based on activation.
// If any policy matches, the decision matches the engine's action, and the first
// matching policy name will be returned.
// Else if any policy is missing attributes, the decision is unknown, and the list of
// policy names that can't be evaluated due to missing attributes will be returned.
// Else, the decision is the opposite of the engine's action, i.e. an ALLOW engine
// will return DecisionDeny, and vice versa.
func (engine *policyEngine) evaluate(activation interpreter.Activation) (Decision, []string) {
unknownPolicyNames := []string{}
for policyName, program := range engine.programs {
// Evaluate program against activation.
var match bool
out, _, err := program.Eval(activation)
if err != nil {
if out == nil {
// Unsuccessful evaluation, typically the result of a series of incompatible
// `EnvOption` or `ProgramOption` values used in the creation of the evaluation
// environment or executable program.
logger.Warning("Unsuccessful evaluation encountered during AuthorizationEngine.Evaluate: %s", err.Error())
}
// Unsuccessful evaluation or successful evaluation to an error result, i.e. missing attributes.
match = false
} else {
// Successful evaluation to a non-error result.
if !types.IsBool(out) {
logger.Warning("'Successful evaluation', but output isn't a boolean: %v", out)
match = false
} else {
match = out.Value().(bool)
}
}
// Process evaluation results.
if err != nil {
unknownPolicyNames = append(unknownPolicyNames, policyName)
} else if match {
return getDecision(engine, true), []string{policyName}
}
}
if len(unknownPolicyNames) > 0 {
return DecisionUnknown, unknownPolicyNames
}
return getDecision(engine, false), []string{}
}
// AuthorizationEngine is the struct for the CEL-based authorization engine.
type AuthorizationEngine struct {
allow *policyEngine
deny *policyEngine
}
// NewAuthorizationEngine builds a CEL evaluation engine from at most one allow and one deny Envoy RBAC.
func NewAuthorizationEngine(allow, deny *pb.RBAC) (*AuthorizationEngine, error) {
if allow == nil && deny == nil {
return &AuthorizationEngine{}, fmt.Errorf("at least one of allow, deny must be non-nil")
}
if allow != nil && allow.Action != pb.RBAC_ALLOW || deny != nil && deny.Action != pb.RBAC_DENY {
return nil, fmt.Errorf("allow must have action ALLOW, deny must have action DENY")
}
// Note: env can be shared across multiple Checks / Program constructions.
env, err := cel.NewEnv(
cel.Declarations(
decls.NewVar("request.url_path", decls.String),
decls.NewVar("request.host", decls.String),
decls.NewVar("request.method", decls.String),
decls.NewVar("request.headers", decls.NewMapType(decls.String, decls.String)),
decls.NewVar("source.address", decls.String),
decls.NewVar("source.port", decls.Int),
decls.NewVar("destination.address", decls.String),
decls.NewVar("destination.port", decls.Int),
decls.NewVar("connection.uri_san_peer_certificate", decls.String),
),
)
if err != nil {
return &AuthorizationEngine{}, fmt.Errorf("failed to create CEL Env: %v", err)
}
// create policy engines
allowEngine, err := newPolicyEngine(allow, env)
if err != nil {
return &AuthorizationEngine{}, err
}
denyEngine, err := newPolicyEngine(deny, env)
if err != nil {
return &AuthorizationEngine{}, err
}
return &AuthorizationEngine{allow: allowEngine, deny: denyEngine}, nil
}
// Evaluate is the core function that evaluates whether an RPC is authorized.
//
// ALLOW policy. If one of the RBAC conditions is evaluated as true, then the
// CEL-based authorization engine evaluation returns allow. If all of the RBAC
// conditions are evaluated as false, then it returns deny. Otherwise, some
// conditions are false and some are unknown, it returns undecided.
//
// DENY policy. If one of the RBAC conditions is evaluated as true, then the
// CEL-based authorization engine evaluation returns deny. If all of the RBAC
// conditions are evaluated as false, then it returns allow. Otherwise, some
// conditions are false and some are unknown, it returns undecided.
//
// DENY policy + ALLOW policy. Evaluation is in the following order: If one
// of the expressions in the DENY policy is true, the authorization engine
// returns deny. If one of the expressions in the DENY policy is unknown, it
// returns undecided. Now all the expressions in the DENY policy are false,
// it returns the evaluation of the ALLOW policy.
func (authorizationEngine *AuthorizationEngine) Evaluate(args *AuthorizationArgs) (AuthorizationDecision, error) {
activation := newActivation(args)
decision := DecisionAllow
var policyNames []string
// Evaluate the deny engine, if it exists.
if authorizationEngine.deny != nil {
decision, policyNames = authorizationEngine.deny.evaluate(activation)
}
// Evaluate the allow engine, if it exists and if the deny engine doesn't exist or is unmatched.
if authorizationEngine.allow != nil && decision == DecisionAllow {
decision, policyNames = authorizationEngine.allow.evaluate(activation)
}
return AuthorizationDecision{decision, policyNames}, nil
}

View File

@ -0,0 +1,272 @@
/*
* 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 (
"reflect"
"sort"
"testing"
pb "github.com/envoyproxy/go-control-plane/envoy/config/rbac/v2"
cel "github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
interpreter "github.com/google/cel-go/interpreter"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type programMock struct {
out ref.Val
err error
}
func (mock programMock) Eval(vars interface{}) (ref.Val, *cel.EvalDetails, error) {
return mock.out, nil, mock.err
}
type valMock struct {
val interface{}
}
func (mock valMock) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
return nil, nil
}
func (mock valMock) ConvertToType(typeValue ref.Type) ref.Val {
return nil
}
func (mock valMock) Equal(other ref.Val) ref.Val {
return nil
}
func (mock valMock) Type() ref.Type {
if mock.val == true || mock.val == false {
return types.BoolType
}
return nil
}
func (mock valMock) Value() interface{} {
return mock.val
}
var (
emptyActivation = interpreter.EmptyActivation()
unsuccessfulProgram = programMock{out: nil, err: status.Errorf(codes.InvalidArgument, "Unsuccessful program evaluation")}
errProgram = programMock{out: valMock{"missing attributes"}, err: status.Errorf(codes.InvalidArgument, "Successful program evaluation to an error result -- missing attributes")}
trueProgram = programMock{out: valMock{true}, err: nil}
falseProgram = programMock{out: valMock{false}, err: nil}
allowMatchEngine = &policyEngine{action: pb.RBAC_ALLOW, programs: map[string]cel.Program{
"allow match policy1": unsuccessfulProgram,
"allow match policy2": trueProgram,
"allow match policy3": falseProgram,
"allow match policy4": errProgram,
}}
denyFailEngine = &policyEngine{action: pb.RBAC_DENY, programs: map[string]cel.Program{
"deny fail policy1": falseProgram,
"deny fail policy2": falseProgram,
"deny fail policy3": falseProgram,
}}
denyUnknownEngine = &policyEngine{action: pb.RBAC_DENY, programs: map[string]cel.Program{
"deny unknown policy1": falseProgram,
"deny unknown policy2": unsuccessfulProgram,
"deny unknown policy3": errProgram,
"deny unknown policy4": falseProgram,
}}
)
func TestNewAuthorizationEngine(t *testing.T) {
tests := map[string]struct {
allow *pb.RBAC
deny *pb.RBAC
wantErr string
errStr string
}{
"too few rbacs": {
allow: nil,
deny: nil,
wantErr: "at least one of allow, deny must be non-nil",
errStr: "Expected error: at least one of allow, deny must be non-nil",
},
"one rbac allow": {
allow: &pb.RBAC{Action: pb.RBAC_ALLOW},
deny: nil,
wantErr: "",
errStr: "Expected 1 ALLOW RBAC to be successful",
},
"one rbac deny": {
allow: nil,
deny: &pb.RBAC{Action: pb.RBAC_DENY},
wantErr: "",
errStr: "Expected 1 DENY RBAC to be successful",
},
"two rbacs": {
allow: &pb.RBAC{Action: pb.RBAC_ALLOW},
deny: &pb.RBAC{Action: pb.RBAC_DENY},
wantErr: "",
errStr: "Expected 2 RBACs (DENY + ALLOW) to be successful",
},
"wrong rbac actions": {
allow: &pb.RBAC{Action: pb.RBAC_DENY},
deny: nil,
wantErr: "allow must have action ALLOW, deny must have action DENY",
errStr: "Expected error: allow must have action ALLOW, deny must have action DENY",
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
_, gotErr := NewAuthorizationEngine(tc.allow, tc.deny)
if tc.wantErr == "" && gotErr == nil {
return
}
if gotErr == nil || gotErr.Error() != tc.wantErr {
t.Errorf(tc.errStr)
}
})
}
}
func TestGetDecision(t *testing.T) {
tests := map[string]struct {
engine *policyEngine
match bool
want Decision
}{
"ALLOW engine match": {
engine: &policyEngine{action: pb.RBAC_ALLOW, programs: map[string]cel.Program{}},
match: true,
want: DecisionAllow,
},
"ALLOW engine fail": {
engine: &policyEngine{action: pb.RBAC_ALLOW, programs: map[string]cel.Program{}},
match: false,
want: DecisionDeny,
},
"DENY engine match": {
engine: &policyEngine{action: pb.RBAC_DENY, programs: map[string]cel.Program{}},
match: true,
want: DecisionDeny,
},
"DENY engine fail": {
engine: &policyEngine{action: pb.RBAC_DENY, programs: map[string]cel.Program{}},
match: false,
want: DecisionAllow,
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
if got := getDecision(tc.engine, tc.match); got != tc.want {
t.Errorf("Expected %v, instead got %v", tc.want, got)
}
})
}
}
func TestPolicyEngineEvaluate(t *testing.T) {
tests := map[string]struct {
engine *policyEngine
activation interpreter.Activation
wantDecision Decision
wantPolicyNames []string
}{
"no policies": {
engine: &policyEngine{},
activation: emptyActivation,
wantDecision: DecisionDeny,
wantPolicyNames: []string{},
},
"match succeed": {
engine: allowMatchEngine,
activation: emptyActivation,
wantDecision: DecisionAllow,
wantPolicyNames: []string{"allow match policy2"},
},
"match fail": {
engine: denyFailEngine,
activation: emptyActivation,
wantDecision: DecisionAllow,
wantPolicyNames: []string{},
},
"unknown": {
engine: denyUnknownEngine,
activation: emptyActivation,
wantDecision: DecisionUnknown,
wantPolicyNames: []string{"deny unknown policy2", "deny unknown policy3"},
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
gotDecision, gotPolicyNames := tc.engine.evaluate(tc.activation)
sort.Strings(gotPolicyNames)
if gotDecision != tc.wantDecision || !reflect.DeepEqual(gotPolicyNames, tc.wantPolicyNames) {
t.Errorf("Expected (%v, %v), instead got (%v, %v)", tc.wantDecision, tc.wantPolicyNames, gotDecision, gotPolicyNames)
}
})
}
}
func TestAuthorizationEngineEvaluate(t *testing.T) {
tests := map[string]struct {
engine *AuthorizationEngine
authArgs *AuthorizationArgs
wantAuthDecision *AuthorizationDecision
wantErr error
}{
"allow match": {
engine: &AuthorizationEngine{allow: allowMatchEngine},
authArgs: &AuthorizationArgs{},
wantAuthDecision: &AuthorizationDecision{decision: DecisionAllow, policyNames: []string{"allow match policy2"}},
wantErr: nil,
},
"deny fail": {
engine: &AuthorizationEngine{deny: denyFailEngine},
authArgs: &AuthorizationArgs{},
wantAuthDecision: &AuthorizationDecision{decision: DecisionAllow, policyNames: []string{}},
wantErr: nil,
},
"first engine unknown": {
engine: &AuthorizationEngine{allow: allowMatchEngine, deny: denyUnknownEngine},
authArgs: &AuthorizationArgs{},
wantAuthDecision: &AuthorizationDecision{decision: DecisionUnknown, policyNames: []string{"deny unknown policy2", "deny unknown policy3"}},
wantErr: nil,
},
"second engine match": {
engine: &AuthorizationEngine{allow: allowMatchEngine, deny: denyFailEngine},
authArgs: &AuthorizationArgs{},
wantAuthDecision: &AuthorizationDecision{decision: DecisionAllow, policyNames: []string{"allow match policy2"}},
wantErr: nil,
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
gotAuthDecision, gotErr := tc.engine.Evaluate(tc.authArgs)
sort.Strings(gotAuthDecision.policyNames)
if tc.wantErr != nil && (gotErr == nil || gotErr.Error() != tc.wantErr.Error()) {
t.Errorf("Expected error to be %v, instead got %v", tc.wantErr, gotErr)
} else if tc.wantErr == nil && (gotErr != nil || gotAuthDecision.decision != tc.wantAuthDecision.decision || !reflect.DeepEqual(gotAuthDecision.policyNames, tc.wantAuthDecision.policyNames)) {
t.Errorf("Expected authorization decision to be (%v, %v), instead got (%v, %v)", tc.wantAuthDecision.decision, tc.wantAuthDecision.policyNames, gotAuthDecision.decision, gotAuthDecision.policyNames)
}
})
}
}

View File

@ -0,0 +1,11 @@
module google.golang.org/grpc/security/authorization
go 1.12
require (
github.com/envoyproxy/go-control-plane v0.9.5
github.com/google/cel-go v0.5.1
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013
google.golang.org/grpc v1.31.0
google.golang.org/protobuf v1.25.0
)

View File

@ -0,0 +1,104 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/antlr/antlr4 v0.0.0-20200503195918-621b933c7a7f h1:0cEys61Sr2hUBEXfNV8eyQP01oZuBgoMeHunebPirK8=
github.com/antlr/antlr4 v0.0.0-20200503195918-621b933c7a7f/go.mod h1:T7PbCXFs94rrTttyxjbyT5+/1V8T2TYDejxUfHJjw1Y=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200313221541-5f7e5dd04533 h1:8wZizuKuZVu5COB7EsBYxBQz8nRcXXn5d4Gt91eJLvU=
github.com/cncf/udpa/go v0.0.0-20200313221541-5f7e5dd04533/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.5 h1:lRJIqDD8yjV1YyPRqecMdytjDLs2fTXq363aCib5xPU=
github.com/envoyproxy/go-control-plane v0.9.5/go.mod h1:OXl5to++W0ctG+EHWTFUjiypVxC/Y4VLc/KFU+al13s=
github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1 h1:ZFgWrT+bLgsYPirOnRfKLYJLvssAegOj/hgyMFdJZe0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/google/cel-go v0.5.1 h1:oDsbtAwlwFPEcC8dMoRWNuVzWJUDeDZeHjoet9rXjTs=
github.com/google/cel-go v0.5.1/go.mod h1:9SvtVVTtZV4DTB1/RuAD1D2HhuqEIdmZEE/r/lrFyKE=
github.com/google/cel-spec v0.4.0/go.mod h1:2pBM5cU4UKjbPDXBgwWkiwBsVgnxknuEJ7C5TDWwORQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200305110556-506484158171 h1:xes2Q2k+d/+YNXVw0FpZkIDJiaux4OVrRKXRAzH6A0U=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1 h1:wdKvqQk7IttEw92GoRyKG2IDrUIpgpj6H6m81yfeMW0=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.31.0 h1:T7P4R73V3SSDPhH7WW7ATbfViLtmamH0DKrP3f9AuDI=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=