Skip to content
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

Merged
merged 36 commits into from
Jul 22, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
6201082
Finished helper function
zasweq Jun 30, 2021
3345452
Vet
zasweq Jun 30, 2021
c6f6c7c
Vet
zasweq Jun 30, 2021
d2fdd85
Vet
zasweq Jun 30, 2021
31301be
Save progress
zasweq Jul 1, 2021
73d9c40
Save progress
zasweq Jul 1, 2021
11acf73
Save progress before cleanup
zasweq Jul 1, 2021
153fbb6
Cleanup
zasweq Jul 2, 2021
16002a5
More cleanup
zasweq Jul 2, 2021
72311a2
Save progress
zasweq Jul 2, 2021
d60b45b
Working instantiation test
zasweq Jul 2, 2021
cbd8112
Save progress
zasweq Jul 6, 2021
9ff3255
Save progress
zasweq Jul 6, 2021
4fe746b
Save progress, almost done
zasweq Jul 7, 2021
9ea7103
Clean up
zasweq Jul 7, 2021
ae0e62c
vet
zasweq Jul 7, 2021
8b9ebee
Vet
zasweq Jul 7, 2021
16273d7
Vet
zasweq Jul 7, 2021
0cb3455
Added Auth Info
zasweq Jul 7, 2021
2aad5fb
Responded to Easwar's comments
zasweq Jul 8, 2021
8b6ee2f
Missed one
zasweq Jul 8, 2021
e5c1f30
Responded to Easwar's comments
zasweq Jul 9, 2021
570c834
Vet
zasweq Jul 9, 2021
6ff2216
Vet
zasweq Jul 9, 2021
6e25747
Changed API
zasweq Jul 9, 2021
21d0121
Vet
zasweq Jul 9, 2021
3279ee5
Responded to Doug's comments
zasweq Jul 16, 2021
f86087e
Responded to Ashitha's comment
zasweq Jul 16, 2021
abc2d77
Vet
zasweq Jul 16, 2021
3b55e0f
Vet
zasweq Jul 16, 2021
0f87719
Responded to all comments except lis port
zasweq Jul 20, 2021
7922256
Listened on wildcard port
zasweq Jul 20, 2021
a35e19b
Vet
zasweq Jul 20, 2021
00b24c2
Vet
zasweq Jul 20, 2021
79fe896
Responded to Doug's comments
zasweq Jul 22, 2021
e2e15cf
Vet
zasweq Jul 22, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 11 additions & 16 deletions internal/xds/rbac/matchers.go
Original file line number Diff line number Diff line change
Expand Up @@ -395,27 +395,22 @@ func (am *authenticatedMatcher) match(data *rpcData) bool {
if am.stringMatcher == nil {
return len(data.certs) != 0
}

// No certificate present, so will never successfully match.
if len(data.certs) == 0 {
return false
}
cert := data.certs[0]
// The order of matching as per the RBAC documentation (see package-level comments)
// is as follows: URI SANs, DNS SANs, and then subject name.
for _, cert := range data.certs {
for _, uriSAN := range cert.URIs {
if am.stringMatcher.Match(uriSAN.String()) {
return true
}
}
}
for _, cert := range data.certs {
for _, dnsSAN := range cert.DNSNames {
if am.stringMatcher.Match(dnsSAN) {
return true
}
for _, uriSAN := range cert.URIs {
if am.stringMatcher.Match(uriSAN.String()) {
return true
}
}
for _, cert := range data.certs {
if am.stringMatcher.Match(cert.Subject.String()) {
for _, dnsSAN := range cert.DNSNames {
if am.stringMatcher.Match(dnsSAN) {
return true
}
}
return false
return am.stringMatcher.Match(cert.Subject.String())
}
51 changes: 25 additions & 26 deletions internal/xds/rbac/rbac_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"context"
"crypto/x509"
"errors"
"fmt"
"net"
"strconv"

Expand Down Expand Up @@ -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?
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

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?

Copy link
Contributor Author

@zasweq zasweq Jul 8, 2021

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.

Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delete TODO?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deleted.

_, err := engine.findMatchingPolicy(rpcData)
matchingPolicyName, matchingPolicy := engine.findMatchingPolicy(rpcData)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These names are too similar. matched for the bool? or ok is fine too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

%q might be nicer to better offset the name of the matched policy

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.
Expand All @@ -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))
Copy link
Member

Choose a reason for hiding this comment

The 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 policy?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The 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 rbac since that's the proto message type.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The envoy docs refer to it as RBAC configuration. I guess config or rbacConfig would be a good name. I would prefer the former since we are in the rbac package, so the latter would be a little redundant.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
7 changes: 4 additions & 3 deletions internal/xds/rbac/rbac_engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -908,9 +908,10 @@ func (s) TestChainEngine(t *testing.T) {

ctx = grpc.NewContextWithServerTransportStream(ctx, stream)
err = cre.IsAuthorized(ctx)
status, _ := status.FromError(err)
if data.wantStatusCode != status.Code() {
t.Fatalf("IsAuthorized(%+v, %+v) returned (%+v), want(%+v)", ctx, data.rpcData.fullMethod, status.Code(), data.wantStatusCode)
if gotCode := status.Code(err); gotCode != data.wantStatusCode {
t.Fatalf("IsAuthorized(%+v, %+v) returned (%+v), want(%+v)", ctx, data.rpcData.fullMethod, gotCode, data.wantStatusCode)
conn.Close()
lis.Close()
}

conn.Close()
Expand Down