Skip to content

chore: Update rego to be partial execution friendly #3449

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

Merged
merged 30 commits into from
Aug 11, 2022

Conversation

Emyrk
Copy link
Member

@Emyrk Emyrk commented Aug 10, 2022

What this does

Updates to a new rego policy that passes all the same tests. The new rego policy can be partially evaluated into simple queries. A unit was added to ensure the new policy always can be converted into these simple queries.

Performance

This does not affect performance when authorizing a single object. It only affects when using Filter.

Below is the Filter as is, vs the new filtering using Partial evaluation to optimize subsequent Auth() calls. Note this benchmark is using the new rego policy. The PR #3426 is using a different rego policy. So the benchmarks are expected to be different.

cpu: Intel(R) Core(TM) i7-6770HQ CPU @ 2.60GHz
BenchmarkRBACFilter/NoRoles-8              11178      104742 ns/op       44944 B/op      804 allocs/op
BenchmarkRBACFilter/Admin-8                 3532      314068 ns/op      101511 B/op      101 allocs/op
BenchmarkRBACFilter/OrgAdmin-8              3636      305136 ns/op       99223 B/op      997 allocs/op
BenchmarkRBACFilter/OrgMember-8             3472      318597 ns/op      101057 B/op      151 allocs/op
BenchmarkRBACFilter/ManyRoles-8             2389      527514 ns/op      161426 B/op      404 allocs/op

BenchmarkRBACFilter/NoRolesQueries-8              901699              1332 ns/op             137 B/op          3 allocs/op
BenchmarkRBACFilter/AdminQueries-8               2222412               636.1 ns/op           276 B/op          0 allocs/op
BenchmarkRBACFilter/OrgAdminQueries-8              29290             39604 ns/op           16264 B/op        305 allocs/op
BenchmarkRBACFilter/OrgMemberQueries-8             32221             36196 ns/op           14039 B/op        264 allocs/op
BenchmarkRBACFilter/ManyRolesQueries-8             43754             27587 ns/op           12949 B/op        243 allocs/op

Correctness

RBAC has a pretty good test suite, so I can pretty confidently say the behavior matches what we had before. Meaning false positives/negatives should not occur.

Notes

There is a difference between rego's Partial and PartialResult. PartialResult does not optimize in the same way Partial does. The reason Partial is much faster, is that the policy converts down to a set of queries based on the unknown fields (our new rego policy is designed to compress to these queries!)

opa eval --partial --format=pretty 'data.authz.allow = true' -d policy.rego --unknowns input.object.owner --unknowns input.object.org_owner -i input.json

+---------+--------------------------------------------------------------------+
| Query 1 | input.object.org_owner != ""                                       |
|         | input.object.org_owner in {"feda2e52-8bf1-42ce-ad75-6c5595cb297a"} |
|         | "me" = input.object.owner                                          |
+---------+--------------------------------------------------------------------+
| Query 2 | input.object.org_owner = ""                                        |
|         | "me" = input.object.owner                                          |
+---------+--------------------------------------------------------------------+

The number of queries and the content of the queries depends on the user's roles. The intention is to convert these queries into SQL WHERE clauses combined by an OR. Until we do that, we need to run through the queries ourselves, which as seen in the last benchmark, is quite fast.

@Emyrk Emyrk marked this pull request as ready for review August 10, 2022 20:22
@Emyrk Emyrk requested review from kylecarbs and spikecurtis August 10, 2022 20:30
@@ -11,24 +11,35 @@ import (

type Authorizer interface {
ByRoleName(ctx context.Context, subjectID string, roleNames []string, action Action, object Object) error
PrepareByRoleName(ctx context.Context, subjectID string, roles []string, action Action, objectType string) (PreparedAuthorized, error)
Copy link
Member

Choose a reason for hiding this comment

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

This function is only used internally, so seems like it might not need to be exported like this.

Copy link
Member Author

@Emyrk Emyrk Aug 10, 2022

Choose a reason for hiding this comment

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

I need it for my Fake authorizer to work for some unit tests. I can't fake it without an interface ☹️

recordingAuthorizer always returns false and save the auth calls so I can assert them in my tests.

return objects, nil
}
objecType := objects[0].RBACObject().Type
objects, err := rbac.Filter(r.Context(), api.Authorizer, roles.ID.String(), roles.Roles, action, objecType, objects)
Copy link
Member

Choose a reason for hiding this comment

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

Seems like all of this logic could be in the rbac package itself, then we wouldn't need the recordingAuthorizer code. Then functions could just call rbac.AuthorizeFilter directly.

Copy link
Member Author

Choose a reason for hiding this comment

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

You are right about the object type stuff 👍

As for recordingAuthorizer. That is needed for my unit test that checks all the routes. recordingAuthorizer is never used outside unit tests.

@@ -11,24 +11,35 @@ import (

type Authorizer interface {
ByRoleName(ctx context.Context, subjectID string, roleNames []string, action Action, object Object) error
PrepareByRoleName(ctx context.Context, subjectID string, roles []string, action Action, objectType string) (PreparedAuthorized, error)
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't see any use case for exposing the PreparedAuthorized to consumers of authorization --- why not just expose the Filter() method here?

Copy link
Member Author

Choose a reason for hiding this comment

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

I wish I could, but you can't make generic methods on structs. The nice thing about the generic Filter() method is you can pass in and return the same typed slice.

If I made it a non-generic function, you'd have to take your slice of []Workspace, convert it to []rbac.Object, and then convert it back to []Workspace. I could do this all in some rbac.Authorize, but it takes a lot of slice copying I prefer to avoid.

Copy link
Contributor

Choose a reason for hiding this comment

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

Well, that sucks. Go generics continue to disappoint.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yea, it's really dumb because I don't see why it's not possible. Like I would like to know the objection to it.

@@ -1209,7 +1209,7 @@ func TestPaginatedUsers(t *testing.T) {
t.Parallel()

// This test takes longer than a long time.
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong*2)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong*3)
Copy link
Contributor

Choose a reason for hiding this comment

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

why did we increase timeout if we decreased RBAC time?

Copy link
Member Author

Choose a reason for hiding this comment

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

I am unsure why this test was timing out. I can spend more time and look into it

Copy link
Member Author

Choose a reason for hiding this comment

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

This test takes <3s on my local machine. I have no clue why it was timing out... going to drop back to *2 and see what I get.

@Emyrk Emyrk enabled auto-merge (squash) August 11, 2022 22:00
@Emyrk Emyrk merged commit 3ae42f4 into main Aug 11, 2022
@Emyrk Emyrk deleted the stevenmasley/rbac_update_rego branch August 11, 2022 22:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants