chore: Merge more rbac files (#6927)

* chore: Merge more rbac files

- Remove cache.go -> authz.go
- Remove query.go -> authz.go
- Remove role.go -> roles.go

* Order imports

* fmt
This commit is contained in:
Steven Masley
2023-04-03 09:05:06 -05:00
committed by GitHub
parent 333718d1fa
commit fab8da633b
8 changed files with 358 additions and 375 deletions

View File

@ -3,6 +3,7 @@ package rbac
import (
"context"
_ "embed"
"strings"
"sync"
"time"
@ -15,6 +16,7 @@ import (
"golang.org/x/xerrors"
"github.com/coder/coder/coderd/rbac/regosql"
"github.com/coder/coder/coderd/rbac/regosql/sqltypes"
"github.com/coder/coder/coderd/tracing"
"github.com/coder/coder/coderd/util/slice"
)
@ -34,6 +36,12 @@ func AllActions() []Action {
return []Action{ActionCreate, ActionRead, ActionUpdate, ActionDelete}
}
type AuthCall struct {
Actor Subject
Action Action
Object Object
}
// Subject is a struct that contains all the elements of a subject in an rbac
// authorize.
type Subject struct {
@ -519,6 +527,160 @@ func (a RegoAuthorizer) newPartialAuthorizer(ctx context.Context, subject Subjec
return pAuth, nil
}
// AuthorizeFilter is a compiled partial query that can be converted to SQL.
// This allows enforcing the policy on the database side in a WHERE clause.
type AuthorizeFilter interface {
SQLString() string
}
type authorizedSQLFilter struct {
sqlString string
auth *PartialAuthorizer
}
// ConfigWithACL is the basic configuration for converting rego to SQL when
// the object has group and user ACL fields.
func ConfigWithACL() regosql.ConvertConfig {
return regosql.ConvertConfig{
VariableConverter: regosql.DefaultVariableConverter(),
}
}
// ConfigWithoutACL is the basic configuration for converting rego to SQL when
// the object has no ACL fields.
func ConfigWithoutACL() regosql.ConvertConfig {
return regosql.ConvertConfig{
VariableConverter: regosql.NoACLConverter(),
}
}
func Compile(cfg regosql.ConvertConfig, pa *PartialAuthorizer) (AuthorizeFilter, error) {
root, err := regosql.ConvertRegoAst(cfg, pa.partialQueries)
if err != nil {
return nil, xerrors.Errorf("convert rego ast: %w", err)
}
// Generate the SQL
gen := sqltypes.NewSQLGenerator()
sqlString := root.SQLString(gen)
if len(gen.Errors()) > 0 {
var errStrings []string
for _, err := range gen.Errors() {
errStrings = append(errStrings, err.Error())
}
return nil, xerrors.Errorf("sql generation errors: %v", strings.Join(errStrings, ", "))
}
return &authorizedSQLFilter{
sqlString: sqlString,
auth: pa,
}, nil
}
func (a *authorizedSQLFilter) SQLString() string {
return a.sqlString
}
type cachedCalls struct {
authz Authorizer
}
// Cacher returns an Authorizer that can use a cache stored on a context
// to short circuit duplicate calls to the Authorizer. This is useful when
// multiple calls are made to the Authorizer for the same subject, action, and
// object. The cache is on each `ctx` and is not shared between requests.
// If no cache is found on the context, the Authorizer is called as normal.
//
// Cacher is safe for multiple actors.
func Cacher(authz Authorizer) Authorizer {
return &cachedCalls{authz: authz}
}
func (c *cachedCalls) Authorize(ctx context.Context, subject Subject, action Action, object Object) error {
cache := cacheFromContext(ctx)
resp, ok := cache.Load(subject, action, object)
if ok {
return resp
}
err := c.authz.Authorize(ctx, subject, action, object)
cache.Save(subject, action, object, err)
return err
}
// Prepare returns the underlying PreparedAuthorized. The cache does not apply
// to prepared authorizations. These should be using a SQL filter, and
// therefore the cache is not needed.
func (c *cachedCalls) Prepare(ctx context.Context, subject Subject, action Action, objectType string) (PreparedAuthorized, error) {
return c.authz.Prepare(ctx, subject, action, objectType)
}
// authorizeCache enabled caching of Authorizer calls for a given request. This
// prevents the cost of running the same rbac checks multiple times.
// A cache hit must match on all 3 values: subject, action, and object.
type authorizeCache struct {
sync.Mutex
// calls is a list of all calls made to the Authorizer.
// This list is cached per request context. The size of this list is expected
// to be incredibly small. Often 1 or 2 calls.
calls []cachedAuthCall
}
type cachedAuthCall struct {
AuthCall
Err error
}
// cacheContextKey is a context key used to store the cache in the context.
type cacheContextKey struct{}
// cacheFromContext returns the cache from the context.
// If there is no cache, a nil value is returned.
// The nil cache can still be called as a normal cache, but will not cache or
// return any values.
func cacheFromContext(ctx context.Context) *authorizeCache {
cache, _ := ctx.Value(cacheContextKey{}).(*authorizeCache)
return cache
}
func WithCacheCtx(ctx context.Context) context.Context {
return context.WithValue(ctx, cacheContextKey{}, &authorizeCache{})
}
//nolint:revive
func (c *authorizeCache) Load(subject Subject, action Action, object Object) (error, bool) {
if c == nil {
return nil, false
}
c.Lock()
defer c.Unlock()
for _, call := range c.calls {
if call.Action == action && call.Object.Equal(object) && call.Actor.Equal(subject) {
return call.Err, true
}
}
return nil, false
}
func (c *authorizeCache) Save(subject Subject, action Action, object Object, err error) {
if c == nil {
return
}
c.Lock()
defer c.Unlock()
c.calls = append(c.calls, cachedAuthCall{
AuthCall: AuthCall{
Actor: subject,
Action: action,
Object: object,
},
Err: err,
})
}
// rbacTraceAttributes are the attributes that are added to all spans created by
// the rbac package. These attributes should help to debug slow spans.
func rbacTraceAttributes(actor Subject, action Action, objectType string, extra ...attribute.KeyValue) trace.SpanStartOption {