Skip to content

chore: Compile rego once to save CPU cycles in testing #4169

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 1 commit into from
Sep 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 1 addition & 7 deletions coderd/coderd.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,7 @@ func New(options *Options) *API {
options.MetricsCacheRefreshInterval = time.Hour
}
if options.Authorizer == nil {
var err error
options.Authorizer, err = rbac.NewAuthorizer()
if err != nil {
// This should never happen, as the unit tests would fail if the
// default built in authorizer failed.
panic(xerrors.Errorf("rego authorize panic: %w", err))
}
options.Authorizer = rbac.NewAuthorizer()
}
if options.PrometheusRegistry == nil {
options.PrometheusRegistry = prometheus.NewRegistry()
Expand Down
46 changes: 26 additions & 20 deletions coderd/rbac/authz.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
_ "embed"
"fmt"
"sync"

"github.com/open-policy-agent/opa/rego"
"go.opentelemetry.io/otel/attribute"
Expand Down Expand Up @@ -66,32 +67,37 @@ type RegoAuthorizer struct {

var _ Authorizer = (*RegoAuthorizer)(nil)

// Load the policy from policy.rego in this directory.
//
//go:embed policy.rego
var policy string
var (
// Load the policy from policy.rego in this directory.
//
//go:embed policy.rego
policy string
queryOnce sync.Once
query rego.PreparedEvalQuery
)

const (
rolesOkCheck = "role_ok"
scopeOkCheck = "scope_ok"
)

func NewAuthorizer() (*RegoAuthorizer, error) {
ctx := context.Background()
query, err := rego.New(
// Bind the results to 2 variables for easy checking later.
rego.Query(
fmt.Sprintf("%s := data.authz.role_allow "+
"%s := data.authz.scope_allow",
rolesOkCheck, scopeOkCheck),
),
rego.Module("policy.rego", policy),
).PrepareForEval(ctx)

if err != nil {
return nil, xerrors.Errorf("prepare query: %w", err)
}
return &RegoAuthorizer{query: query}, nil
func NewAuthorizer() *RegoAuthorizer {
queryOnce.Do(func() {
var err error
query, err = rego.New(
// Bind the results to 2 variables for easy checking later.
rego.Query(
fmt.Sprintf("%s := data.authz.role_allow "+
"%s := data.authz.scope_allow",
rolesOkCheck, scopeOkCheck),
),
rego.Module("policy.rego", policy),
).PrepareForEval(context.Background())
if err != nil {
panic(xerrors.Errorf("compile rego: %w", err))
}
})
return &RegoAuthorizer{query: query}
}

type authSubject struct {
Expand Down
12 changes: 4 additions & 8 deletions coderd/rbac/authz_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,8 @@ func (w fakeObject) RBACObject() Object {

func TestFilterError(t *testing.T) {
t.Parallel()
auth, err := NewAuthorizer()
require.NoError(t, err)

_, err = Filter(context.Background(), auth, uuid.NewString(), []string{}, ScopeAll, ActionRead, []Object{ResourceUser, ResourceWorkspace})
auth := NewAuthorizer()
_, err := Filter(context.Background(), auth, uuid.NewString(), []string{}, ScopeAll, ActionRead, []Object{ResourceUser, ResourceWorkspace})
require.ErrorContains(t, err, "object types must be uniform")
}

Expand Down Expand Up @@ -160,8 +158,7 @@ func TestFilter(t *testing.T) {

ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
defer cancel()
auth, err := NewAuthorizer()
require.NoError(t, err, "new auth")
auth := NewAuthorizer()

scope := ScopeAll
if tc.Scope != "" {
Expand Down Expand Up @@ -742,8 +739,7 @@ type authTestCase struct {

func testAuthorize(t *testing.T, name string, subject subject, sets ...[]authTestCase) {
t.Helper()
authorizer, err := NewAuthorizer()
require.NoError(t, err)
authorizer := NewAuthorizer()
for _, cases := range sets {
for i, c := range cases {
c := c
Expand Down
8 changes: 2 additions & 6 deletions coderd/rbac/builtin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,7 @@ func BenchmarkRBACFilter(b *testing.B) {
},
}

authorizer, err := rbac.NewAuthorizer()
if err != nil {
require.NoError(b, err)
}
authorizer := rbac.NewAuthorizer()
for _, c := range benchCases {
b.Run(c.Name, func(b *testing.B) {
objects := benchmarkSetup(orgs, users, b.N)
Expand Down Expand Up @@ -119,8 +116,7 @@ type authSubject struct {
func TestRolePermissions(t *testing.T) {
t.Parallel()

auth, err := rbac.NewAuthorizer()
require.NoError(t, err, "new rego authorizer")
auth := rbac.NewAuthorizer()

// currentUser is anything that references "me", "mine", or "my".
currentUser := uuid.New()
Expand Down