1/* 2 * Copyright 2020 gRPC authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package engine 18 19import ( 20 "fmt" 21 "net" 22 "strconv" 23 24 pb "github.com/envoyproxy/go-control-plane/envoy/config/rbac/v2" 25 "github.com/google/cel-go/cel" 26 "github.com/google/cel-go/checker/decls" 27 "github.com/google/cel-go/common/types" 28 "github.com/google/cel-go/interpreter" 29 expr "google.golang.org/genproto/googleapis/api/expr/v1alpha1" 30 "google.golang.org/grpc/grpclog" 31 "google.golang.org/grpc/metadata" 32 "google.golang.org/grpc/peer" 33 "google.golang.org/protobuf/proto" 34) 35 36var logger = grpclog.Component("authorization") 37 38var stringAttributeMap = map[string]func(*AuthorizationArgs) (string, error){ 39 "request.url_path": (*AuthorizationArgs).getRequestURLPath, 40 "request.host": (*AuthorizationArgs).getRequestHost, 41 "request.method": (*AuthorizationArgs).getRequestMethod, 42 "source.address": (*AuthorizationArgs).getSourceAddress, 43 "destination.address": (*AuthorizationArgs).getDestinationAddress, 44 "connection.uri_san_peer_certificate": (*AuthorizationArgs).getURISanPeerCertificate, 45 "source.principal": (*AuthorizationArgs).getSourcePrincipal, 46} 47 48var intAttributeMap = map[string]func(*AuthorizationArgs) (int, error){ 49 "source.port": (*AuthorizationArgs).getSourcePort, 50 "destination.port": (*AuthorizationArgs).getDestinationPort, 51} 52 53// activationImpl is an implementation of interpreter.Activation. 54// An Activation is the primary mechanism by which a caller supplies input into a CEL program. 55type activationImpl struct { 56 dict map[string]interface{} 57} 58 59// ResolveName returns a value from the activation by qualified name, or false if the name 60// could not be found. 61func (activation activationImpl) ResolveName(name string) (interface{}, bool) { 62 result, ok := activation.dict[name] 63 return result, ok 64} 65 66// Parent returns the parent of the current activation, may be nil. 67// If non-nil, the parent will be searched during resolve calls. 68func (activation activationImpl) Parent() interpreter.Activation { 69 return activationImpl{} 70} 71 72// AuthorizationArgs is the input of the CEL-based authorization engine. 73type AuthorizationArgs struct { 74 md metadata.MD 75 peerInfo *peer.Peer 76 fullMethod string 77} 78 79// newActivation converts AuthorizationArgs into the activation for CEL. 80func newActivation(args *AuthorizationArgs) interpreter.Activation { 81 // Fill out evaluation map, only adding the attributes that can be extracted. 82 evalMap := make(map[string]interface{}) 83 for key, function := range stringAttributeMap { 84 val, err := function(args) 85 if err == nil { 86 evalMap[key] = val 87 } 88 } 89 for key, function := range intAttributeMap { 90 val, err := function(args) 91 if err == nil { 92 evalMap[key] = val 93 } 94 } 95 val, err := args.getRequestHeaders() 96 if err == nil { 97 evalMap["request.headers"] = val 98 } 99 // Convert evaluation map to activation. 100 return activationImpl{dict: evalMap} 101} 102 103func (args *AuthorizationArgs) getRequestURLPath() (string, error) { 104 if args.fullMethod == "" { 105 return "", fmt.Errorf("authorization args doesn't have a valid request url path") 106 } 107 return args.fullMethod, nil 108} 109 110func (args *AuthorizationArgs) getRequestHost() (string, error) { 111 // TODO(@zhenlian): fill out attribute extraction for request.host 112 return "", fmt.Errorf("authorization args doesn't have a valid request host") 113} 114 115func (args *AuthorizationArgs) getRequestMethod() (string, error) { 116 // TODO(@zhenlian): fill out attribute extraction for request.method 117 return "", fmt.Errorf("authorization args doesn't have a valid request method") 118} 119 120func (args *AuthorizationArgs) getRequestHeaders() (map[string]string, error) { 121 // TODO(@zhenlian): fill out attribute extraction for request.headers 122 return nil, fmt.Errorf("authorization args doesn't have valid request headers") 123} 124 125func (args *AuthorizationArgs) getSourceAddress() (string, error) { 126 if args.peerInfo == nil { 127 return "", fmt.Errorf("authorization args doesn't have a valid source address") 128 } 129 addr := args.peerInfo.Addr.String() 130 host, _, err := net.SplitHostPort(addr) 131 if err != nil { 132 return "", err 133 } 134 return host, nil 135} 136 137func (args *AuthorizationArgs) getSourcePort() (int, error) { 138 if args.peerInfo == nil { 139 return 0, fmt.Errorf("authorization args doesn't have a valid source port") 140 } 141 addr := args.peerInfo.Addr.String() 142 _, port, err := net.SplitHostPort(addr) 143 if err != nil { 144 return 0, err 145 } 146 return strconv.Atoi(port) 147} 148 149func (args *AuthorizationArgs) getDestinationAddress() (string, error) { 150 // TODO(@zhenlian): fill out attribute extraction for destination.address 151 return "", fmt.Errorf("authorization args doesn't have a valid destination address") 152} 153 154func (args *AuthorizationArgs) getDestinationPort() (int, error) { 155 // TODO(@zhenlian): fill out attribute extraction for destination.port 156 return 0, fmt.Errorf("authorization args doesn't have a valid destination port") 157} 158 159func (args *AuthorizationArgs) getURISanPeerCertificate() (string, error) { 160 // TODO(@zhenlian): fill out attribute extraction for connection.uri_san_peer_certificate 161 return "", fmt.Errorf("authorization args doesn't have a valid URI in SAN field of the peer certificate") 162} 163 164func (args *AuthorizationArgs) getSourcePrincipal() (string, error) { 165 // TODO(@zhenlian): fill out attribute extraction for source.principal 166 return "", fmt.Errorf("authorization args doesn't have a valid source principal") 167} 168 169// Decision represents different authorization decisions a CEL-based 170// authorization engine can return. 171type Decision int32 172 173const ( 174 // DecisionAllow indicates allowing the RPC to go through. 175 DecisionAllow Decision = iota 176 // DecisionDeny indicates denying the RPC from going through. 177 DecisionDeny 178 // DecisionUnknown indicates that there is insufficient information to 179 // determine whether or not an RPC call is authorized. 180 DecisionUnknown 181) 182 183// String returns the string representation of a Decision object. 184func (d Decision) String() string { 185 return [...]string{"DecisionAllow", "DecisionDeny", "DecisionUnknown"}[d] 186} 187 188// AuthorizationDecision is the output of CEL-based authorization engines. 189// If decision is allow or deny, policyNames will either contain the names of 190// all the policies matched in the engine that permitted the action, or be 191// empty as the decision was made after all conditions evaluated to false. 192// If decision is unknown, policyNames will contain the list of policies that 193// evaluated to unknown. 194type AuthorizationDecision struct { 195 decision Decision 196 policyNames []string 197} 198 199// Converts an expression to a parsed expression, with SourceInfo nil. 200func exprToParsedExpr(condition *expr.Expr) *expr.ParsedExpr { 201 return &expr.ParsedExpr{Expr: condition} 202} 203 204// Converts an expression to a CEL program. 205func exprToProgram(condition *expr.Expr, env *cel.Env) (cel.Program, error) { 206 // Converts condition to ParsedExpr by setting SourceInfo empty. 207 pexpr := exprToParsedExpr(condition) 208 // pretend cel.ExprToAst exists 209 ast, iss := env.Check(cel.ParsedExprToAst(pexpr)) 210 if iss.Err() != nil { 211 return nil, iss.Err() 212 } 213 // Check that the expression will evaluate to a boolean. 214 if !proto.Equal(ast.ResultType(), decls.Bool) { 215 return nil, fmt.Errorf("expected boolean condition") 216 } 217 // Build the program plan. 218 return env.Program(ast, 219 cel.EvalOptions(cel.OptOptimize), 220 ) 221} 222 223// policyEngine is the struct for an engine created from one RBAC proto. 224type policyEngine struct { 225 action pb.RBAC_Action 226 programs map[string]cel.Program 227} 228 229// Creates a new policyEngine from an RBAC policy proto. 230func newPolicyEngine(rbac *pb.RBAC, env *cel.Env) (*policyEngine, error) { 231 if rbac == nil { 232 return nil, nil 233 } 234 action := rbac.Action 235 programs := make(map[string]cel.Program) 236 for policyName, policy := range rbac.Policies { 237 prg, err := exprToProgram(policy.Condition, env) 238 if err != nil { 239 return &policyEngine{}, fmt.Errorf("failed to create CEL program from condition: %v", err) 240 } 241 programs[policyName] = prg 242 } 243 return &policyEngine{action, programs}, nil 244} 245 246// Returns the decision of an engine based on whether or not AuthorizationArgs is a match, 247// i.e. if engine's action is ALLOW and match is true, we will return DecisionAllow; 248// if engine's action is ALLOW and match is false, we will return DecisionDeny. 249func getDecision(engine *policyEngine, match bool) Decision { 250 if engine.action == pb.RBAC_ALLOW && match || engine.action == pb.RBAC_DENY && !match { 251 return DecisionAllow 252 } 253 return DecisionDeny 254} 255 256// Returns the authorization decision of a single policy engine based on activation. 257// If any policy matches, the decision matches the engine's action, and the first 258// matching policy name will be returned. 259// Else if any policy is missing attributes, the decision is unknown, and the list of 260// policy names that can't be evaluated due to missing attributes will be returned. 261// Else, the decision is the opposite of the engine's action, i.e. an ALLOW engine 262// will return DecisionDeny, and vice versa. 263func (engine *policyEngine) evaluate(activation interpreter.Activation) (Decision, []string) { 264 unknownPolicyNames := []string{} 265 for policyName, program := range engine.programs { 266 // Evaluate program against activation. 267 var match bool 268 out, _, err := program.Eval(activation) 269 if err != nil { 270 if out == nil { 271 // Unsuccessful evaluation, typically the result of a series of incompatible 272 // `EnvOption` or `ProgramOption` values used in the creation of the evaluation 273 // environment or executable program. 274 logger.Warning("Unsuccessful evaluation encountered during AuthorizationEngine.Evaluate: %s", err.Error()) 275 } 276 // Unsuccessful evaluation or successful evaluation to an error result, i.e. missing attributes. 277 match = false 278 } else { 279 // Successful evaluation to a non-error result. 280 if !types.IsBool(out) { 281 logger.Warning("'Successful evaluation', but output isn't a boolean: %v", out) 282 match = false 283 } else { 284 match = out.Value().(bool) 285 } 286 } 287 288 // Process evaluation results. 289 if err != nil { 290 unknownPolicyNames = append(unknownPolicyNames, policyName) 291 } else if match { 292 return getDecision(engine, true), []string{policyName} 293 } 294 } 295 if len(unknownPolicyNames) > 0 { 296 return DecisionUnknown, unknownPolicyNames 297 } 298 return getDecision(engine, false), []string{} 299} 300 301// AuthorizationEngine is the struct for the CEL-based authorization engine. 302type AuthorizationEngine struct { 303 allow *policyEngine 304 deny *policyEngine 305} 306 307// NewAuthorizationEngine builds a CEL evaluation engine from at most one allow and one deny Envoy RBAC. 308func NewAuthorizationEngine(allow, deny *pb.RBAC) (*AuthorizationEngine, error) { 309 if allow == nil && deny == nil { 310 return &AuthorizationEngine{}, fmt.Errorf("at least one of allow, deny must be non-nil") 311 } 312 if allow != nil && allow.Action != pb.RBAC_ALLOW || deny != nil && deny.Action != pb.RBAC_DENY { 313 return nil, fmt.Errorf("allow must have action ALLOW, deny must have action DENY") 314 } 315 // Note: env can be shared across multiple Checks / Program constructions. 316 env, err := cel.NewEnv( 317 cel.Declarations( 318 decls.NewVar("request.url_path", decls.String), 319 decls.NewVar("request.host", decls.String), 320 decls.NewVar("request.method", decls.String), 321 decls.NewVar("request.headers", decls.NewMapType(decls.String, decls.String)), 322 decls.NewVar("source.address", decls.String), 323 decls.NewVar("source.port", decls.Int), 324 decls.NewVar("destination.address", decls.String), 325 decls.NewVar("destination.port", decls.Int), 326 decls.NewVar("connection.uri_san_peer_certificate", decls.String), 327 decls.NewVar("source.principal", decls.String), 328 ), 329 ) 330 if err != nil { 331 return &AuthorizationEngine{}, fmt.Errorf("failed to create CEL Env: %v", err) 332 } 333 // create policy engines 334 allowEngine, err := newPolicyEngine(allow, env) 335 if err != nil { 336 return &AuthorizationEngine{}, err 337 } 338 denyEngine, err := newPolicyEngine(deny, env) 339 if err != nil { 340 return &AuthorizationEngine{}, err 341 } 342 return &AuthorizationEngine{allow: allowEngine, deny: denyEngine}, nil 343} 344 345// Evaluate is the core function that evaluates whether an RPC is authorized. 346// 347// ALLOW policy. If one of the RBAC conditions is evaluated as true, then the 348// CEL-based authorization engine evaluation returns allow. If all of the RBAC 349// conditions are evaluated as false, then it returns deny. Otherwise, some 350// conditions are false and some are unknown, it returns undecided. 351// 352// DENY policy. If one of the RBAC conditions is evaluated as true, then the 353// CEL-based authorization engine evaluation returns deny. If all of the RBAC 354// conditions are evaluated as false, then it returns allow. Otherwise, some 355// conditions are false and some are unknown, it returns undecided. 356// 357// DENY policy + ALLOW policy. Evaluation is in the following order: If one 358// of the expressions in the DENY policy is true, the authorization engine 359// returns deny. If one of the expressions in the DENY policy is unknown, it 360// returns undecided. Now all the expressions in the DENY policy are false, 361// it returns the evaluation of the ALLOW policy. 362func (authorizationEngine *AuthorizationEngine) Evaluate(args *AuthorizationArgs) (AuthorizationDecision, error) { 363 activation := newActivation(args) 364 decision := DecisionAllow 365 var policyNames []string 366 // Evaluate the deny engine, if it exists. 367 if authorizationEngine.deny != nil { 368 decision, policyNames = authorizationEngine.deny.evaluate(activation) 369 } 370 // Evaluate the allow engine, if it exists and if the deny engine doesn't exist or is unmatched. 371 if authorizationEngine.allow != nil && decision == DecisionAllow { 372 decision, policyNames = authorizationEngine.allow.evaluate(activation) 373 } 374 return AuthorizationDecision{decision, policyNames}, nil 375} 376