mirror of
https://github.com/coder/coder.git
synced 2025-07-03 16:13:58 +00:00
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:
@ -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 {
|
||||
|
Reference in New Issue
Block a user