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	cel "github.com/google/cel-go/cel"
26	"github.com/google/cel-go/checker/decls"
27	types "github.com/google/cel-go/common/types"
28	interpreter "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		),
328	)
329	if err != nil {
330		return &AuthorizationEngine{}, fmt.Errorf("failed to create CEL Env: %v", err)
331	}
332	// create policy engines
333	allowEngine, err := newPolicyEngine(allow, env)
334	if err != nil {
335		return &AuthorizationEngine{}, err
336	}
337	denyEngine, err := newPolicyEngine(deny, env)
338	if err != nil {
339		return &AuthorizationEngine{}, err
340	}
341	return &AuthorizationEngine{allow: allowEngine, deny: denyEngine}, nil
342}
343
344// Evaluate is the core function that evaluates whether an RPC is authorized.
345//
346// ALLOW policy. If one of the RBAC conditions is evaluated as true, then the
347// CEL-based authorization engine evaluation returns allow. If all of the RBAC
348// conditions are evaluated as false, then it returns deny. Otherwise, some
349// conditions are false and some are unknown, it returns undecided.
350//
351// DENY policy. If one of the RBAC conditions is evaluated as true, then the
352// CEL-based authorization engine evaluation returns deny. If all of the RBAC
353// conditions are evaluated as false, then it returns allow. Otherwise, some
354// conditions are false and some are unknown, it returns undecided.
355//
356// DENY policy + ALLOW policy. Evaluation is in the following order: If one
357// of the expressions in the DENY policy is true, the authorization engine
358// returns deny. If one of the expressions in the DENY policy is unknown, it
359// returns undecided. Now all the expressions in the DENY policy are false,
360// it returns the evaluation of the ALLOW policy.
361func (authorizationEngine *AuthorizationEngine) Evaluate(args *AuthorizationArgs) (AuthorizationDecision, error) {
362	activation := newActivation(args)
363	decision := DecisionAllow
364	var policyNames []string
365	// Evaluate the deny engine, if it exists.
366	if authorizationEngine.deny != nil {
367		decision, policyNames = authorizationEngine.deny.evaluate(activation)
368	}
369	// Evaluate the allow engine, if it exists and if the deny engine doesn't exist or is unmatched.
370	if authorizationEngine.allow != nil && decision == DecisionAllow {
371		decision, policyNames = authorizationEngine.allow.evaluate(activation)
372	}
373	return AuthorizationDecision{decision, policyNames}, nil
374}
375