-
Notifications
You must be signed in to change notification settings - Fork 4.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add extra layer on top of RBAC Engine #4576
Changes from 4 commits
6201082
3345452
c6f6c7c
d2fdd85
31301be
73d9c40
11acf73
153fbb6
16002a5
72311a2
d60b45b
cbd8112
9ff3255
4fe746b
9ea7103
ae0e62c
8b9ebee
16273d7
0cb3455
2aad5fb
8b6ee2f
e5c1f30
570c834
6ff2216
6e25747
21d0121
3279ee5
f86087e
abc2d77
3b55e0f
0f87719
7922256
a35e19b
00b24c2
79fe896
e2e15cf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,6 +24,7 @@ import ( | |
"context" | ||
"crypto/x509" | ||
"errors" | ||
"fmt" | ||
"net" | ||
"strconv" | ||
|
||
|
@@ -69,13 +70,13 @@ func (cre *ChainEngine) IsAuthorized(ctx context.Context) error { | |
} | ||
for _, engine := range cre.chainedEngines { | ||
// TODO: What do I do now with this matchingPolicyName? | ||
_, err := engine.findMatchingPolicy(rpcData) | ||
matchingPolicyName, matchingPolicy := engine.findMatchingPolicy(rpcData) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These names are too similar. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I chose ok. I've seen many instances of _, ok := func() throughout codebase. |
||
|
||
switch { | ||
case engine.action == allow && err == errPolicyNotFound: | ||
return status.Errorf(codes.PermissionDenied, "incoming RPC %v did not match an allow policy", rpcData) | ||
case engine.action == deny && err != errPolicyNotFound: | ||
return status.Errorf(codes.PermissionDenied, "incoming RPC %+v matched a deny policy", rpcData) | ||
case engine.action == allow && !matchingPolicy: | ||
return status.Errorf(codes.PermissionDenied, "incoming RPC did not match an allow policy") | ||
case engine.action == deny && matchingPolicy: | ||
return status.Errorf(codes.PermissionDenied, "incoming RPC matched a deny policy %v", matchingPolicyName) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed. |
||
} | ||
dfawley marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
// If the incoming RPC gets through all of the engines successfully (i.e. | ||
|
@@ -100,14 +101,14 @@ type engine struct { | |
// newEngine creates an RBAC Engine based on the contents of policy. Returns a | ||
// non-nil error if the policy is invalid. | ||
func newEngine(policy *v3rbacpb.RBAC) (*engine, error) { | ||
var action action | ||
var a action | ||
switch *policy.Action.Enum() { | ||
case v3rbacpb.RBAC_ALLOW: | ||
action = allow | ||
a = allow | ||
case v3rbacpb.RBAC_DENY: | ||
action = deny | ||
case v3rbacpb.RBAC_LOG: | ||
return nil, errors.New("unsupported action") | ||
a = deny | ||
default: | ||
return nil, fmt.Errorf("unsupported action %s", policy.Action) | ||
} | ||
|
||
policies := make(map[string]*policyMatcher, len(policy.Policies)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wait.. there are multiple policies in a policy? Is this right? Should we not call this parameter name There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, there are. The high level idea of the matching engine is to check if RPC's match any policy, which will be then be tied to an action. See https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/rbac/v3/rbac.proto#config-rbac-v3-rbac for an example. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's fine, but let's not call a "thing that contains many policies" a "policy". This is confusing. If no better name can be thought of, maybe just There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The envoy docs refer to it as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You both are right. Policy is a weird name. Switched to "config" :D. |
||
|
@@ -120,26 +121,24 @@ func newEngine(policy *v3rbacpb.RBAC) (*engine, error) { | |
} | ||
return &engine{ | ||
policies: policies, | ||
action: action, | ||
action: a, | ||
}, nil | ||
} | ||
|
||
var errPolicyNotFound = errors.New("a matching policy was not found") | ||
|
||
// findMatchingPolicy determines if an incoming RPC matches a policy. On a | ||
// successful match, it returns the name of the matching policy and a nil error | ||
// to specify that there was a matching policy found. It returns an error in | ||
// successful match, it returns the name of the matching policy and a true bool | ||
// to specify that there was a matching policy found. It returns false in | ||
// the case of not finding a matching policy. | ||
func (r *engine) findMatchingPolicy(rpcData *rpcData) (string, error) { | ||
func (r *engine) findMatchingPolicy(rpcData *rpcData) (string, bool) { | ||
for policy, matcher := range r.policies { | ||
if matcher.match(rpcData) { | ||
return policy, nil | ||
return policy, true | ||
} | ||
} | ||
return "", errPolicyNotFound | ||
return "", false | ||
} | ||
|
||
// newRPCData takes a incoming context (should be a context representing state | ||
// newRPCData takes an incoming context (should be a context representing state | ||
// needed for server RPC Call with metadata, peer info (used for source ip/port | ||
// and TLS information) and connection (used for destination ip/port) piped into | ||
// it) and the method name of the Service being called server side and populates | ||
|
@@ -168,17 +167,17 @@ func newRPCData(ctx context.Context) (*rpcData, error) { | |
|
||
// The connection is needed in order to find the destination address and | ||
// port of the incoming RPC Call. | ||
conn, ok := getConnection(ctx) | ||
if !ok { | ||
conn := getConnection(ctx) | ||
if conn == nil { | ||
return nil, errors.New("missing connection in incoming context") | ||
} | ||
_, dPort, err := net.SplitHostPort(conn.LocalAddr().String()) | ||
if err != nil { | ||
return nil, err | ||
return nil, fmt.Errorf("error parsing local address: %v", err) | ||
} | ||
dp, err := strconv.ParseUint(dPort, 10, 32) | ||
if err != nil { | ||
return nil, err | ||
return nil, fmt.Errorf("error parsing local address: %v", err) | ||
} | ||
|
||
var peerCertificates []*x509.Certificate | ||
|
@@ -220,9 +219,9 @@ type rpcData struct { | |
|
||
type connectionKey struct{} | ||
|
||
func getConnection(ctx context.Context) (net.Conn, bool) { | ||
conn, ok := ctx.Value(connectionKey{}).(net.Conn) | ||
return conn, ok | ||
func getConnection(ctx context.Context) net.Conn { | ||
conn, _ := ctx.Value(connectionKey{}).(net.Conn) | ||
return conn | ||
} | ||
|
||
// SetConnection adds the connection to the context to be able to get | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a question for one of the reviewers?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, and no. The way the API is structured in the RBAC Design document, the matchingPolicyName was supposed to be used solely for debugging purposes. Thus, I feel like I should log this or something? Although it's not of super present concern.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to return the matching policy name from here, so that the decision to log (or keep stats/metrics) may be taken at the interceptor implementation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So the thing is, the matchingPolicyName is per engine. If you were to return matchingPolicyNames, it would be a slice of matchingPolicyNames from here. I find that not very useful to the interceptor. Thus, I don't know what to do with it here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you should actually capture the matching policy, and return it on line 78 (if it is a matching DENY policy). In case it's a matched "allow" policy, I'd just ignore that for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great idea :)! Added return to error message about matching DENY policy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Delete TODO?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Deleted.