Skip to content

Commit 32fbd10

Browse files
authored
chore: Optimize parial rego execution byte allocations (#6144)
* chore: Implement benchmark for authorizer.Prepare Identify time + alloc cost before optimizing
1 parent ab9cba9 commit 32fbd10

File tree

5 files changed

+51
-25
lines changed

5 files changed

+51
-25
lines changed

coderd/rbac/authz.go

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,8 @@ func Filter[O Objecter](ctx context.Context, auth Authorizer, subject Subject, a
139139

140140
// RegoAuthorizer will use a prepared rego query for performing authorize()
141141
type RegoAuthorizer struct {
142-
query rego.PreparedEvalQuery
142+
query rego.PreparedEvalQuery
143+
partialQuery rego.PreparedPartialQuery
143144

144145
authorizeHist *prometheus.HistogramVec
145146
prepareHist prometheus.Histogram
@@ -151,9 +152,10 @@ var (
151152
// Load the policy from policy.rego in this directory.
152153
//
153154
//go:embed policy.rego
154-
policy string
155-
queryOnce sync.Once
156-
query rego.PreparedEvalQuery
155+
policy string
156+
queryOnce sync.Once
157+
query rego.PreparedEvalQuery
158+
partialQuery rego.PreparedPartialQuery
157159
)
158160

159161
func NewAuthorizer(registry prometheus.Registerer) *RegoAuthorizer {
@@ -166,6 +168,21 @@ func NewAuthorizer(registry prometheus.Registerer) *RegoAuthorizer {
166168
if err != nil {
167169
panic(xerrors.Errorf("compile rego: %w", err))
168170
}
171+
172+
partialQuery, err = rego.New(
173+
rego.Unknowns([]string{
174+
"input.object.id",
175+
"input.object.owner",
176+
"input.object.org_owner",
177+
"input.object.acl_user_list",
178+
"input.object.acl_group_list",
179+
}),
180+
rego.Query("data.authz.allow = true"),
181+
rego.Module("policy.rego", policy),
182+
).PrepareForPartial(context.Background())
183+
if err != nil {
184+
panic(xerrors.Errorf("compile partial rego: %w", err))
185+
}
169186
})
170187

171188
// Register metrics to prometheus.
@@ -207,7 +224,8 @@ func NewAuthorizer(registry prometheus.Registerer) *RegoAuthorizer {
207224
})
208225

209226
return &RegoAuthorizer{
210-
query: query,
227+
query: query,
228+
partialQuery: partialQuery,
211229

212230
authorizeHist: authorizeHistogram,
213231
prepareHist: prepareHistogram,
@@ -289,7 +307,7 @@ func (a RegoAuthorizer) Prepare(ctx context.Context, subject Subject, action Act
289307
)
290308
defer span.End()
291309

292-
prepared, err := newPartialAuthorizer(ctx, subject, action, objectType)
310+
prepared, err := a.newPartialAuthorizer(ctx, subject, action, objectType)
293311
if err != nil {
294312
return nil, xerrors.Errorf("new partial authorizer: %w", err)
295313
}

coderd/rbac/authz_internal_test.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -977,9 +977,14 @@ func testAuthorize(t *testing.T, name string, subject Subject, sets ...[]authTes
977977

978978
d, _ := json.Marshal(map[string]interface{}{
979979
// This is not perfect marshal, but it is good enough for debugging this test.
980-
"subject": subject,
981-
"object": c.resource,
982-
"action": a,
980+
"subject": authSubject{
981+
ID: subject.ID,
982+
Roles: must(subject.Roles.Expand()),
983+
Groups: subject.Groups,
984+
Scope: must(subject.Scope.Expand()),
985+
},
986+
"object": c.resource,
987+
"action": a,
983988
})
984989

985990
// Logging only

coderd/rbac/authz_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,17 @@ func BenchmarkRBACFilter(b *testing.B) {
189189
)
190190

191191
authorizer := rbac.NewAuthorizer(prometheus.NewRegistry())
192+
193+
for _, c := range benchCases {
194+
b.Run("PrepareOnly-"+c.Name, func(b *testing.B) {
195+
obType := rbac.ResourceWorkspace.Type
196+
for i := 0; i < b.N; i++ {
197+
_, err := authorizer.Prepare(context.Background(), c.Actor, rbac.ActionRead, obType)
198+
require.NoError(b, err)
199+
}
200+
})
201+
}
202+
192203
for _, c := range benchCases {
193204
b.Run(c.Name, func(b *testing.B) {
194205
objects := benchmarkSetup(orgs, users, b.N)

coderd/rbac/builtin_internal_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,11 @@ func BenchmarkRBACValueAllocation(b *testing.B) {
5151
})
5252
b.Run("JSONRegoValue", func(b *testing.B) {
5353
for i := 0; i < b.N; i++ {
54-
_, err := ast.InterfaceToValue(jsonSubject)
54+
_, err := ast.InterfaceToValue(map[string]interface{}{
55+
"subject": jsonSubject,
56+
"action": ActionRead,
57+
"object": obj,
58+
})
5559
require.NoError(b, err)
5660
}
5761
})

coderd/rbac/partial.go

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ EachQueryLoop:
127127
pa.subjectInput, pa.subjectAction, pa.subjectResourceType, nil)
128128
}
129129

130-
func newPartialAuthorizer(ctx context.Context, subject Subject, action Action, objectType string) (*PartialAuthorizer, error) {
130+
func (a RegoAuthorizer) newPartialAuthorizer(ctx context.Context, subject Subject, action Action, objectType string) (*PartialAuthorizer, error) {
131131
if subject.Roles == nil {
132132
return nil, xerrors.Errorf("subject must have roles")
133133
}
@@ -140,20 +140,8 @@ func newPartialAuthorizer(ctx context.Context, subject Subject, action Action, o
140140
return nil, xerrors.Errorf("prepare input: %w", err)
141141
}
142142

143-
// Run the rego policy with a few unknown fields. This should simplify our
144-
// policy to a set of queries.
145-
partialQueries, err := rego.New(
146-
rego.Query("data.authz.allow = true"),
147-
rego.Module("policy.rego", policy),
148-
rego.Unknowns([]string{
149-
"input.object.id",
150-
"input.object.owner",
151-
"input.object.org_owner",
152-
"input.object.acl_user_list",
153-
"input.object.acl_group_list",
154-
}),
155-
rego.ParsedInput(input),
156-
).Partial(ctx)
143+
partialQueries, err := a.partialQuery.Partial(ctx, rego.EvalParsedInput(input))
144+
157145
if err != nil {
158146
return nil, xerrors.Errorf("prepare: %w", err)
159147
}

0 commit comments

Comments
 (0)