diff --git a/coderd/coderd.go b/coderd/coderd.go index 25ac1afec2f36..3e44bdade20b7 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -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() diff --git a/coderd/rbac/authz.go b/coderd/rbac/authz.go index 51ad87b8fbe09..591df4bf5ba76 100644 --- a/coderd/rbac/authz.go +++ b/coderd/rbac/authz.go @@ -4,6 +4,7 @@ import ( "context" _ "embed" "fmt" + "sync" "github.com/open-policy-agent/opa/rego" "go.opentelemetry.io/otel/attribute" @@ -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 { diff --git a/coderd/rbac/authz_internal_test.go b/coderd/rbac/authz_internal_test.go index d5c254ecfc3c2..bf5342c3b27e4 100644 --- a/coderd/rbac/authz_internal_test.go +++ b/coderd/rbac/authz_internal_test.go @@ -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") } @@ -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 != "" { @@ -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 diff --git a/coderd/rbac/builtin_test.go b/coderd/rbac/builtin_test.go index 2616466c39e1e..c915515d2119d 100644 --- a/coderd/rbac/builtin_test.go +++ b/coderd/rbac/builtin_test.go @@ -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) @@ -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()