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

Commit3ae42f4

Browse files
authored
chore: Update rego to be partial execution friendly (#3449)
- Improves performance of batch authorization calls- Enables possibility to convert rego auth calls into SQL WHERE clauses
1 parent4a17e0d commit3ae42f4

16 files changed

+1124
-818
lines changed

‎coderd/authorize.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,21 @@ import (
1010
"github.com/coder/coder/coderd/rbac"
1111
)
1212

13-
funcAuthorizeFilter[O rbac.Objecter](api*API,r*http.Request,action rbac.Action,objects []O) []O {
13+
funcAuthorizeFilter[O rbac.Objecter](api*API,r*http.Request,action rbac.Action,objects []O)([]O,error) {
1414
roles:=httpmw.AuthorizationUserRoles(r)
15-
returnrbac.Filter(r.Context(),api.Authorizer,roles.ID.String(),roles.Roles,action,objects)
15+
objects,err:=rbac.Filter(r.Context(),api.Authorizer,roles.ID.String(),roles.Roles,action,objects)
16+
iferr!=nil {
17+
// Log the error as Filter should not be erroring.
18+
api.Logger.Error(r.Context(),"filter failed",
19+
slog.Error(err),
20+
slog.F("user_id",roles.ID),
21+
slog.F("username",roles.Username),
22+
slog.F("route",r.URL.Path),
23+
slog.F("action",action),
24+
)
25+
returnnil,err
26+
}
27+
returnobjects,nil
1628
}
1729

1830
// Authorize will return false if the user is not authorized to do the action.

‎coderd/coderd_test.go

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ func TestBuildInfo(t *testing.T) {
6363
// TestAuthorizeAllEndpoints will check `authorize` is called on every endpoint registered.
6464
funcTestAuthorizeAllEndpoints(t*testing.T) {
6565
t.Parallel()
66-
authorizer:=&fakeAuthorizer{}
66+
authorizer:=&recordingAuthorizer{}
6767

6868
ctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitLong)
6969
defercancel()
@@ -563,21 +563,41 @@ type authCall struct {
563563
Object rbac.Object
564564
}
565565

566-
typefakeAuthorizerstruct {
566+
typerecordingAuthorizerstruct {
567567
Called*authCall
568568
AlwaysReturnerror
569569
}
570570

571-
func (f*fakeAuthorizer)ByRoleName(_ context.Context,subjectIDstring,roleNames []string,action rbac.Action,object rbac.Object)error {
572-
f.Called=&authCall{
571+
func (r*recordingAuthorizer)ByRoleName(_ context.Context,subjectIDstring,roleNames []string,action rbac.Action,object rbac.Object)error {
572+
r.Called=&authCall{
573573
SubjectID:subjectID,
574574
Roles:roleNames,
575575
Action:action,
576576
Object:object,
577577
}
578-
returnf.AlwaysReturn
578+
returnr.AlwaysReturn
579579
}
580580

581-
func (f*fakeAuthorizer)reset() {
582-
f.Called=nil
581+
func (r*recordingAuthorizer)PrepareByRoleName(_ context.Context,subjectIDstring,roles []string,action rbac.Action,_string) (rbac.PreparedAuthorized,error) {
582+
return&fakePreparedAuthorizer{
583+
Original:r,
584+
SubjectID:subjectID,
585+
Roles:roles,
586+
Action:action,
587+
},nil
588+
}
589+
590+
func (r*recordingAuthorizer)reset() {
591+
r.Called=nil
592+
}
593+
594+
typefakePreparedAuthorizerstruct {
595+
Original*recordingAuthorizer
596+
SubjectIDstring
597+
Roles []string
598+
Action rbac.Action
599+
}
600+
601+
func (f*fakePreparedAuthorizer)Authorize(ctx context.Context,object rbac.Object)error {
602+
returnf.Original.ByRoleName(ctx,f.SubjectID,f.Roles,f.Action,object)
583603
}

‎coderd/provisionerdaemons.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,14 @@ func (api *API) provisionerDaemons(rw http.ResponseWriter, r *http.Request) {
5050
ifdaemons==nil {
5151
daemons= []database.ProvisionerDaemon{}
5252
}
53-
daemons=AuthorizeFilter(api,r,rbac.ActionRead,daemons)
53+
daemons,err=AuthorizeFilter(api,r,rbac.ActionRead,daemons)
54+
iferr!=nil {
55+
httpapi.Write(rw,http.StatusInternalServerError, codersdk.Response{
56+
Message:"Internal error fetching provisioner daemons.",
57+
Detail:err.Error(),
58+
})
59+
return
60+
}
5461

5562
httpapi.Write(rw,http.StatusOK,daemons)
5663
}

‎coderd/rbac/authz.go

Lines changed: 48 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,41 @@ import (
1111

1212
typeAuthorizerinterface {
1313
ByRoleName(ctx context.Context,subjectIDstring,roleNames []string,actionAction,objectObject)error
14+
PrepareByRoleName(ctx context.Context,subjectIDstring,roleNames []string,actionAction,objectTypestring) (PreparedAuthorized,error)
15+
}
16+
17+
typePreparedAuthorizedinterface {
18+
Authorize(ctx context.Context,objectObject)error
1419
}
1520

1621
// Filter takes in a list of objects, and will filter the list removing all
17-
// the elements the subject does not have permission for.
18-
// Filter does not allocate a new slice, and will use the existing one
19-
// passed in. This can cause memory leaks if the slice is held for a prolonged
20-
// period of time.
21-
funcFilter[OObjecter](ctx context.Context,authAuthorizer,subjIDstring,subjRoles []string,actionAction,objects []O) []O {
22+
// the elements the subject does not have permission for. All objects must be
23+
// of the same type.
24+
funcFilter[OObjecter](ctx context.Context,authAuthorizer,subjIDstring,subjRoles []string,actionAction,objects []O) ([]O,error) {
25+
iflen(objects)==0 {
26+
// Nothing to filter
27+
returnobjects,nil
28+
}
29+
objectType:=objects[0].RBACObject().Type
30+
2231
filtered:=make([]O,0)
32+
prepared,err:=auth.PrepareByRoleName(ctx,subjID,subjRoles,action,objectType)
33+
iferr!=nil {
34+
returnnil,xerrors.Errorf("prepare: %w",err)
35+
}
2336

2437
fori:=rangeobjects {
2538
object:=objects[i]
26-
err:=auth.ByRoleName(ctx,subjID,subjRoles,action,object.RBACObject())
39+
rbacObj:=object.RBACObject()
40+
ifrbacObj.Type!=objectType {
41+
returnnil,xerrors.Errorf("object types must be uniform across the set (%s), found %s",objectType,object.RBACObject().Type)
42+
}
43+
err:=prepared.Authorize(ctx,rbacObj)
2744
iferr==nil {
2845
filtered=append(filtered,object)
2946
}
3047
}
31-
returnfiltered
48+
returnfiltered,nil
3249
}
3350

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

@@ -64,14 +81,11 @@ type authSubject struct {
6481
// This is the function intended to be used outside this package.
6582
// The role is fetched from the builtin map located in memory.
6683
func (aRegoAuthorizer)ByRoleName(ctx context.Context,subjectIDstring,roleNames []string,actionAction,objectObject)error {
67-
roles:=make([]Role,0,len(roleNames))
68-
for_,n:=rangeroleNames {
69-
r,err:=RoleByName(n)
70-
iferr!=nil {
71-
returnxerrors.Errorf("get role permissions: %w",err)
72-
}
73-
roles=append(roles,r)
84+
roles,err:=RolesByNames(roleNames)
85+
iferr!=nil {
86+
returnerr
7487
}
88+
7589
returna.Authorize(ctx,subjectID,roles,action,object)
7690
}
7791

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

95-
iflen(results)!=1 {
96-
returnForbiddenWithInternal(xerrors.Errorf("expect only 1 result, got %d",len(results)),input,results)
109+
if!results.Allowed() {
110+
returnForbiddenWithInternal(xerrors.Errorf("policy disallows request"),input,results)
97111
}
98112

99-
allowedResult,ok:= (results[0].Bindings["allowed"]).(bool)
100-
if!ok {
101-
returnForbiddenWithInternal(xerrors.Errorf("expected allowed to be a bool but got %T",allowedResult),input,results)
113+
returnnil
114+
}
115+
116+
// Prepare will partially execute the rego policy leaving the object fields unknown (except for the type).
117+
// This will vastly speed up performance if batch authorization on the same type of objects is needed.
118+
func (RegoAuthorizer)Prepare(ctx context.Context,subjectIDstring,roles []Role,actionAction,objectTypestring) (*PartialAuthorizer,error) {
119+
auth,err:=newPartialAuthorizer(ctx,subjectID,roles,action,objectType)
120+
iferr!=nil {
121+
returnnil,xerrors.Errorf("new partial authorizer: %w",err)
102122
}
103123

104-
if!allowedResult {
105-
returnForbiddenWithInternal(xerrors.Errorf("policy disallows request"),input,results)
124+
returnauth,nil
125+
}
126+
127+
func (aRegoAuthorizer)PrepareByRoleName(ctx context.Context,subjectIDstring,roleNames []string,actionAction,objectTypestring) (PreparedAuthorized,error) {
128+
roles,err:=RolesByNames(roleNames)
129+
iferr!=nil {
130+
returnnil,err
106131
}
107132

108-
returnnil
133+
returna.Prepare(ctx,subjectID,roles,action,objectType)
109134
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp