Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
Emyrk merged 30 commits intomainfromstevenmasley/rbac_update_rego
Aug 11, 2022
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
30 commits
Select commitHold shift + click to select a range
b3536bc
chore: Update rego to be partial execution friendly
EmyrkAug 10, 2022
75e3a12
test: Add unit test to verify partial query support
EmyrkAug 10, 2022
6cdf575
Add partial filtering and evaluation
EmyrkAug 10, 2022
f5eacd0
Benmark Partials
EmyrkAug 10, 2022
4a7c68e
Linting
EmyrkAug 10, 2022
df75be5
Spelling
EmyrkAug 10, 2022
e90ac2d
Fix recording fake authorizer
EmyrkAug 10, 2022
1e774e0
Fix partial query tests
EmyrkAug 10, 2022
38917dc
Working partial execution to queries
EmyrkAug 10, 2022
e139a1f
Cleanup
EmyrkAug 10, 2022
c44d4d1
Cleanup
EmyrkAug 10, 2022
19f3557
Add comments
EmyrkAug 10, 2022
f9dd9aa
Spelling
EmyrkAug 10, 2022
bed9f4f
Remove old Filter
EmyrkAug 10, 2022
74d90f4
Improve error message
EmyrkAug 10, 2022
fe0d05a
Comments
EmyrkAug 10, 2022
dd5c55c
Make test parallel
EmyrkAug 10, 2022
ae22f89
Fix typo
EmyrkAug 10, 2022
0266963
Drop an arg from Filter
EmyrkAug 10, 2022
af457b9
Test timed out
EmyrkAug 10, 2022
21f4f21
Test less random, rename param
EmyrkAug 11, 2022
2c87220
Add rego comments with explainations
EmyrkAug 11, 2022
1c407ab
Add more rego comments
EmyrkAug 11, 2022
9f9b2d1
Move fakeauthorizer code
EmyrkAug 11, 2022
ab55cf5
Unexport partial fields
EmyrkAug 11, 2022
abf098d
Reduce wait
EmyrkAug 11, 2022
44c7370
Linting + Typos
EmyrkAug 11, 2022
4611322
Wording
EmyrkAug 11, 2022
c8e26a8
Test runs long
EmyrkAug 11, 2022
510e94b
The single test is taking too long
EmyrkAug 11, 2022
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
16 changes: 14 additions & 2 deletionscoderd/authorize.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,9 +10,21 @@ import (
"github.com/coder/coder/coderd/rbac"
)

func AuthorizeFilter[O rbac.Objecter](api *API, r *http.Request, action rbac.Action, objects []O) []O {
func AuthorizeFilter[O rbac.Objecter](api *API, r *http.Request, action rbac.Action, objects []O)([]O, error) {
roles := httpmw.AuthorizationUserRoles(r)
return rbac.Filter(r.Context(), api.Authorizer, roles.ID.String(), roles.Roles, action, objects)
objects, err := rbac.Filter(r.Context(), api.Authorizer, roles.ID.String(), roles.Roles, action, objects)
if err != nil {
// Log the error as Filter should not be erroring.
api.Logger.Error(r.Context(), "filter failed",
slog.Error(err),
slog.F("user_id", roles.ID),
slog.F("username", roles.Username),
slog.F("route", r.URL.Path),
slog.F("action", action),
)
return nil, err
}
return objects, nil
}

// Authorize will return false if the user is not authorized to do the action.
Expand Down
34 changes: 27 additions & 7 deletionscoderd/coderd_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -63,7 +63,7 @@ func TestBuildInfo(t *testing.T) {
// TestAuthorizeAllEndpoints will check `authorize` is called on every endpoint registered.
func TestAuthorizeAllEndpoints(t *testing.T) {
t.Parallel()
authorizer := &fakeAuthorizer{}
authorizer := &recordingAuthorizer{}

ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
Expand DownExpand Up@@ -563,21 +563,41 @@ type authCall struct {
Object rbac.Object
}

typefakeAuthorizer struct {
typerecordingAuthorizer struct {
Called *authCall
AlwaysReturn error
}

func (f *fakeAuthorizer) ByRoleName(_ context.Context, subjectID string, roleNames []string, action rbac.Action, object rbac.Object) error {
f.Called = &authCall{
func (r *recordingAuthorizer) ByRoleName(_ context.Context, subjectID string, roleNames []string, action rbac.Action, object rbac.Object) error {
r.Called = &authCall{
SubjectID: subjectID,
Roles: roleNames,
Action: action,
Object: object,
}
returnf.AlwaysReturn
returnr.AlwaysReturn
}

func (f *fakeAuthorizer) reset() {
f.Called = nil
func (r *recordingAuthorizer) PrepareByRoleName(_ context.Context, subjectID string, roles []string, action rbac.Action, _ string) (rbac.PreparedAuthorized, error) {
return &fakePreparedAuthorizer{
Original: r,
SubjectID: subjectID,
Roles: roles,
Action: action,
}, nil
}

func (r *recordingAuthorizer) reset() {
r.Called = nil
}

type fakePreparedAuthorizer struct {
Original *recordingAuthorizer
SubjectID string
Roles []string
Action rbac.Action
}

func (f *fakePreparedAuthorizer) Authorize(ctx context.Context, object rbac.Object) error {
return f.Original.ByRoleName(ctx, f.SubjectID, f.Roles, f.Action, object)
}
9 changes: 8 additions & 1 deletioncoderd/provisionerdaemons.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,7 +50,14 @@ func (api *API) provisionerDaemons(rw http.ResponseWriter, r *http.Request) {
if daemons == nil {
daemons = []database.ProvisionerDaemon{}
}
daemons = AuthorizeFilter(api, r, rbac.ActionRead, daemons)
daemons, err = AuthorizeFilter(api, r, rbac.ActionRead, daemons)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching provisioner daemons.",
Detail: err.Error(),
})
return
}

httpapi.Write(rw, http.StatusOK, daemons)
}
Expand Down
71 changes: 48 additions & 23 deletionscoderd/rbac/authz.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,24 +11,41 @@ import (

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

type PreparedAuthorized interface {
Authorize(ctx context.Context, object Object) error
}

// Filter takes in a list of objects, and will filter the list removing all
// the elements the subject does not have permission for.
// Filter does not allocate a new slice, and will use the existing one
// passed in. This can cause memory leaks if the slice is held for a prolonged
// period of time.
func Filter[O Objecter](ctx context.Context, auth Authorizer, subjID string, subjRoles []string, action Action, objects []O) []O {
// the elements the subject does not have permission for. All objects must be
// of the same type.
func Filter[O Objecter](ctx context.Context, auth Authorizer, subjID string, subjRoles []string, action Action, objects []O) ([]O, error) {
if len(objects) == 0 {
// Nothing to filter
return objects, nil
}
objectType := objects[0].RBACObject().Type

filtered := make([]O, 0)
prepared, err := auth.PrepareByRoleName(ctx, subjID, subjRoles, action, objectType)
if err != nil {
return nil, xerrors.Errorf("prepare: %w", err)
}

for i := range objects {
object := objects[i]
err := auth.ByRoleName(ctx, subjID, subjRoles, action, object.RBACObject())
rbacObj := object.RBACObject()
if rbacObj.Type != objectType {
return nil, xerrors.Errorf("object types must be uniform across the set (%s), found %s", objectType, object.RBACObject().Type)
}
err := prepared.Authorize(ctx, rbacObj)
if err == nil {
filtered = append(filtered, object)
}
}
return filtered
return filtered, nil
}

// RegoAuthorizer will use a prepared rego query for performing authorize()
Expand All@@ -45,7 +62,7 @@ func NewAuthorizer() (*RegoAuthorizer, error) {
query, err := rego.New(
// allowed is the `allow` field from the prepared query. This is the field to check if authorization is
// granted.
rego.Query("allowed =data.authz.allow"),
rego.Query("data.authz.allow"),
rego.Module("policy.rego", policy),
).PrepareForEval(ctx)

Expand All@@ -64,14 +81,11 @@ type authSubject struct {
// This is the function intended to be used outside this package.
// The role is fetched from the builtin map located in memory.
func (a RegoAuthorizer) ByRoleName(ctx context.Context, subjectID string, roleNames []string, action Action, object Object) error {
roles := make([]Role, 0, len(roleNames))
for _, n := range roleNames {
r, err := RoleByName(n)
if err != nil {
return xerrors.Errorf("get role permissions: %w", err)
}
roles = append(roles, r)
roles, err := RolesByNames(roleNames)
if err != nil {
return err
}

return a.Authorize(ctx, subjectID, roles, action, object)
}

Expand All@@ -92,18 +106,29 @@ func (a RegoAuthorizer) Authorize(ctx context.Context, subjectID string, roles [
return ForbiddenWithInternal(xerrors.Errorf("eval rego: %w", err), input, results)
}

iflen(results) != 1 {
return ForbiddenWithInternal(xerrors.Errorf("expect only 1 result, got %d", len(results)), input, results)
if!results.Allowed() {
return ForbiddenWithInternal(xerrors.Errorf("policy disallows request"), input, results)
}

allowedResult, ok := (results[0].Bindings["allowed"]).(bool)
if !ok {
return ForbiddenWithInternal(xerrors.Errorf("expected allowed to be a bool but got %T", allowedResult), input, results)
return nil
}

// Prepare will partially execute the rego policy leaving the object fields unknown (except for the type).
// This will vastly speed up performance if batch authorization on the same type of objects is needed.
func (RegoAuthorizer) Prepare(ctx context.Context, subjectID string, roles []Role, action Action, objectType string) (*PartialAuthorizer, error) {
auth, err := newPartialAuthorizer(ctx, subjectID, roles, action, objectType)
if err != nil {
return nil, xerrors.Errorf("new partial authorizer: %w", err)
}

if !allowedResult {
return ForbiddenWithInternal(xerrors.Errorf("policy disallows request"), input, results)
return auth, nil
}

func (a RegoAuthorizer) PrepareByRoleName(ctx context.Context, subjectID string, roleNames []string, action Action, objectType string) (PreparedAuthorized, error) {
roles, err := RolesByNames(roleNames)
if err != nil {
return nil, err
}

returnnil
returna.Prepare(ctx, subjectID, roles, action, objectType)
}
Loading

[8]ページ先頭

©2009-2025 Movatter.jp