mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
chore: push rbac actions to policy package (#13274)
Just moved `rbac.Action` -> `policy.Action`. This is for the stacked PR to not have circular dependencies when doing autogen. Without this, the autogen can produce broken golang code, which prevents the autogen from compiling. So just avoiding circular dependencies. Doing this in it's own PR to reduce LoC diffs in the primary PR, since this has 0 functional changes.
This commit is contained in:
+8
-10
@@ -3,12 +3,14 @@ package rbac
|
||||
import (
|
||||
"github.com/open-policy-agent/opa/ast"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/rbac/policy"
|
||||
)
|
||||
|
||||
// regoInputValue returns a rego input value for the given subject, action, and
|
||||
// object. This rego input is already parsed and can be used directly in a
|
||||
// rego query.
|
||||
func regoInputValue(subject Subject, action Action, object Object) (ast.Value, error) {
|
||||
func regoInputValue(subject Subject, action policy.Action, object Object) (ast.Value, error) {
|
||||
regoSubj, err := subject.regoValue()
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("subject: %w", err)
|
||||
@@ -34,7 +36,7 @@ func regoInputValue(subject Subject, action Action, object Object) (ast.Value, e
|
||||
|
||||
// regoPartialInputValue is the same as regoInputValue but only includes the
|
||||
// object type. This is for partial evaluations.
|
||||
func regoPartialInputValue(subject Subject, action Action, objectType string) (ast.Value, error) {
|
||||
func regoPartialInputValue(subject Subject, action policy.Action, objectType string) (ast.Value, error) {
|
||||
regoSubj, err := subject.regoValue()
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("subject: %w", err)
|
||||
@@ -103,11 +105,11 @@ func (s Subject) regoValue() (ast.Value, error) {
|
||||
func (z Object) regoValue() ast.Value {
|
||||
userACL := ast.NewObject()
|
||||
for k, v := range z.ACLUserList {
|
||||
userACL.Insert(ast.StringTerm(k), ast.NewTerm(regoSlice(v)))
|
||||
userACL.Insert(ast.StringTerm(k), ast.NewTerm(regoSliceString(v...)))
|
||||
}
|
||||
grpACL := ast.NewObject()
|
||||
for k, v := range z.ACLGroupList {
|
||||
grpACL.Insert(ast.StringTerm(k), ast.NewTerm(regoSlice(v)))
|
||||
grpACL.Insert(ast.StringTerm(k), ast.NewTerm(regoSliceString(v...)))
|
||||
}
|
||||
return ast.NewObject(
|
||||
[2]*ast.Term{
|
||||
@@ -200,10 +202,6 @@ func (perm Permission) regoValue() ast.Value {
|
||||
)
|
||||
}
|
||||
|
||||
func (act Action) regoValue() ast.Value {
|
||||
return ast.StringTerm(string(act)).Value
|
||||
}
|
||||
|
||||
type regoValue interface {
|
||||
regoValue() ast.Value
|
||||
}
|
||||
@@ -218,10 +216,10 @@ func regoSlice[T regoValue](slice []T) *ast.Array {
|
||||
return ast.NewArray(terms...)
|
||||
}
|
||||
|
||||
func regoSliceString(slice ...string) *ast.Array {
|
||||
func regoSliceString[T ~string](slice ...T) *ast.Array {
|
||||
terms := make([]*ast.Term, len(slice))
|
||||
for i, v := range slice {
|
||||
terms[i] = ast.StringTerm(v)
|
||||
terms[i] = ast.StringTerm(string(v))
|
||||
}
|
||||
return ast.NewArray(terms...)
|
||||
}
|
||||
|
||||
+19
-28
@@ -19,30 +19,21 @@ import (
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/rbac/policy"
|
||||
"github.com/coder/coder/v2/coderd/rbac/regosql"
|
||||
"github.com/coder/coder/v2/coderd/rbac/regosql/sqltypes"
|
||||
"github.com/coder/coder/v2/coderd/tracing"
|
||||
"github.com/coder/coder/v2/coderd/util/slice"
|
||||
)
|
||||
|
||||
// Action represents the allowed actions to be done on an object.
|
||||
type Action string
|
||||
|
||||
const (
|
||||
ActionCreate Action = "create"
|
||||
ActionRead Action = "read"
|
||||
ActionUpdate Action = "update"
|
||||
ActionDelete Action = "delete"
|
||||
)
|
||||
|
||||
// AllActions is a helper function to return all the possible actions types.
|
||||
func AllActions() []Action {
|
||||
return []Action{ActionCreate, ActionRead, ActionUpdate, ActionDelete}
|
||||
func AllActions() []policy.Action {
|
||||
return []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete}
|
||||
}
|
||||
|
||||
type AuthCall struct {
|
||||
Actor Subject
|
||||
Action Action
|
||||
Action policy.Action
|
||||
Object Object
|
||||
}
|
||||
|
||||
@@ -52,7 +43,7 @@ type AuthCall struct {
|
||||
//
|
||||
// Note that this ignores some fields such as the permissions within a given
|
||||
// role, as this assumes all roles are static to a given role name.
|
||||
func hashAuthorizeCall(actor Subject, action Action, object Object) [32]byte {
|
||||
func hashAuthorizeCall(actor Subject, action policy.Action, object Object) [32]byte {
|
||||
var hashOut [32]byte
|
||||
hash := sha256.New()
|
||||
|
||||
@@ -139,8 +130,8 @@ type Authorizer interface {
|
||||
// Authorize will authorize the given subject to perform the given action
|
||||
// on the given object. Authorize is pure and deterministic with respect to
|
||||
// its arguments and the surrounding object.
|
||||
Authorize(ctx context.Context, subject Subject, action Action, object Object) error
|
||||
Prepare(ctx context.Context, subject Subject, action Action, objectType string) (PreparedAuthorized, error)
|
||||
Authorize(ctx context.Context, subject Subject, action policy.Action, object Object) error
|
||||
Prepare(ctx context.Context, subject Subject, action policy.Action, objectType string) (PreparedAuthorized, error)
|
||||
}
|
||||
|
||||
type PreparedAuthorized interface {
|
||||
@@ -154,7 +145,7 @@ type PreparedAuthorized interface {
|
||||
//
|
||||
// Ideally the 'CompileToSQL' is used instead for large sets. This cost scales
|
||||
// linearly with the number of objects passed in.
|
||||
func Filter[O Objecter](ctx context.Context, auth Authorizer, subject Subject, action Action, objects []O) ([]O, error) {
|
||||
func Filter[O Objecter](ctx context.Context, auth Authorizer, subject Subject, action policy.Action, objects []O) ([]O, error) {
|
||||
if len(objects) == 0 {
|
||||
// Nothing to filter
|
||||
return objects, nil
|
||||
@@ -236,7 +227,7 @@ var (
|
||||
// Load the policy from policy.rego in this directory.
|
||||
//
|
||||
//go:embed policy.rego
|
||||
policy string
|
||||
regoPolicy string
|
||||
queryOnce sync.Once
|
||||
query rego.PreparedEvalQuery
|
||||
partialQuery rego.PreparedPartialQuery
|
||||
@@ -254,7 +245,7 @@ func NewAuthorizer(registry prometheus.Registerer) *RegoAuthorizer {
|
||||
var err error
|
||||
query, err = rego.New(
|
||||
rego.Query("data.authz.allow"),
|
||||
rego.Module("policy.rego", policy),
|
||||
rego.Module("policy.rego", regoPolicy),
|
||||
).PrepareForEval(context.Background())
|
||||
if err != nil {
|
||||
panic(xerrors.Errorf("compile rego: %w", err))
|
||||
@@ -269,7 +260,7 @@ func NewAuthorizer(registry prometheus.Registerer) *RegoAuthorizer {
|
||||
"input.object.acl_group_list",
|
||||
}),
|
||||
rego.Query("data.authz.allow = true"),
|
||||
rego.Module("policy.rego", policy),
|
||||
rego.Module("policy.rego", regoPolicy),
|
||||
).PrepareForPartial(context.Background())
|
||||
if err != nil {
|
||||
panic(xerrors.Errorf("compile partial rego: %w", err))
|
||||
@@ -334,7 +325,7 @@ type authSubject struct {
|
||||
// It returns `nil` if the subject is authorized to perform the action on
|
||||
// the object.
|
||||
// If an error is returned, the authorization is denied.
|
||||
func (a RegoAuthorizer) Authorize(ctx context.Context, subject Subject, action Action, object Object) error {
|
||||
func (a RegoAuthorizer) Authorize(ctx context.Context, subject Subject, action policy.Action, object Object) error {
|
||||
start := time.Now()
|
||||
ctx, span := tracing.StartSpan(ctx,
|
||||
trace.WithTimestamp(start), // Reuse the time.Now for metric and trace
|
||||
@@ -365,7 +356,7 @@ func (a RegoAuthorizer) Authorize(ctx context.Context, subject Subject, action A
|
||||
// It is a different function so the exported one can add tracing + metrics.
|
||||
// That code tends to clutter up the actual logic, so it's separated out.
|
||||
// nolint:revive
|
||||
func (a RegoAuthorizer) authorize(ctx context.Context, subject Subject, action Action, object Object) error {
|
||||
func (a RegoAuthorizer) authorize(ctx context.Context, subject Subject, action policy.Action, object Object) error {
|
||||
if subject.Roles == nil {
|
||||
return xerrors.Errorf("subject must have roles")
|
||||
}
|
||||
@@ -392,7 +383,7 @@ func (a RegoAuthorizer) authorize(ctx context.Context, subject Subject, action A
|
||||
|
||||
// Prepare will partially execute the rego policy leaving the object fields unknown (except for the type).
|
||||
// This will vastly speed up performance if batch authorization on the same type of objects is needed.
|
||||
func (a RegoAuthorizer) Prepare(ctx context.Context, subject Subject, action Action, objectType string) (PreparedAuthorized, error) {
|
||||
func (a RegoAuthorizer) Prepare(ctx context.Context, subject Subject, action policy.Action, objectType string) (PreparedAuthorized, error) {
|
||||
start := time.Now()
|
||||
ctx, span := tracing.StartSpan(ctx,
|
||||
trace.WithTimestamp(start),
|
||||
@@ -428,7 +419,7 @@ type PartialAuthorizer struct {
|
||||
|
||||
// input is used purely for debugging and logging.
|
||||
subjectInput Subject
|
||||
subjectAction Action
|
||||
subjectAction policy.Action
|
||||
subjectResourceType Object
|
||||
|
||||
// preparedQueries are the compiled set of queries after partial evaluation.
|
||||
@@ -537,7 +528,7 @@ EachQueryLoop:
|
||||
pa.subjectInput, pa.subjectAction, pa.subjectResourceType, nil)
|
||||
}
|
||||
|
||||
func (a RegoAuthorizer) newPartialAuthorizer(ctx context.Context, subject Subject, action Action, objectType string) (*PartialAuthorizer, error) {
|
||||
func (a RegoAuthorizer) newPartialAuthorizer(ctx context.Context, subject Subject, action policy.Action, objectType string) (*PartialAuthorizer, error) {
|
||||
if subject.Roles == nil {
|
||||
return nil, xerrors.Errorf("subject must have roles")
|
||||
}
|
||||
@@ -676,7 +667,7 @@ func Cacher(authz Authorizer) Authorizer {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *authCache) Authorize(ctx context.Context, subject Subject, action Action, object Object) error {
|
||||
func (c *authCache) Authorize(ctx context.Context, subject Subject, action policy.Action, object Object) error {
|
||||
authorizeCacheKey := hashAuthorizeCall(subject, action, object)
|
||||
|
||||
var err error
|
||||
@@ -697,13 +688,13 @@ func (c *authCache) Authorize(ctx context.Context, subject Subject, action Actio
|
||||
// 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 *authCache) Prepare(ctx context.Context, subject Subject, action Action, objectType string) (PreparedAuthorized, error) {
|
||||
func (c *authCache) Prepare(ctx context.Context, subject Subject, action policy.Action, objectType string) (PreparedAuthorized, error) {
|
||||
return c.authz.Prepare(ctx, subject, action, objectType)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
func rbacTraceAttributes(actor Subject, action policy.Action, objectType string, extra ...attribute.KeyValue) trace.SpanStartOption {
|
||||
return trace.WithAttributes(
|
||||
append(extra,
|
||||
attribute.StringSlice("subject_roles", actor.SafeRoleNames()),
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/rbac/policy"
|
||||
"github.com/coder/coder/v2/coderd/rbac/regosql"
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
)
|
||||
@@ -59,7 +60,7 @@ func TestFilterError(t *testing.T) {
|
||||
Scope: ScopeAll,
|
||||
}
|
||||
|
||||
_, err := Filter(context.Background(), auth, subject, ActionRead, []Object{ResourceUser, ResourceWorkspace})
|
||||
_, err := Filter(context.Background(), auth, subject, policy.ActionRead, []Object{ResourceUser, ResourceWorkspace})
|
||||
require.ErrorContains(t, err, "object types must be uniform")
|
||||
})
|
||||
|
||||
@@ -67,7 +68,7 @@ func TestFilterError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
auth := &MockAuthorizer{
|
||||
AuthorizeFunc: func(ctx context.Context, subject Subject, action Action, object Object) error {
|
||||
AuthorizeFunc: func(ctx context.Context, subject Subject, action policy.Action, object Object) error {
|
||||
// Authorize func always returns nil, unless the context is canceled.
|
||||
return ctx.Err()
|
||||
},
|
||||
@@ -97,7 +98,7 @@ func TestFilterError(t *testing.T) {
|
||||
ResourceUser,
|
||||
}
|
||||
|
||||
_, err := Filter(ctx, auth, subject, ActionRead, objects)
|
||||
_, err := Filter(ctx, auth, subject, policy.ActionRead, objects)
|
||||
require.ErrorIs(t, err, context.Canceled)
|
||||
})
|
||||
|
||||
@@ -117,7 +118,7 @@ func TestFilterError(t *testing.T) {
|
||||
bomb: cancel,
|
||||
}
|
||||
|
||||
_, err := Filter(ctx, auth, subject, ActionRead, objects)
|
||||
_, err := Filter(ctx, auth, subject, policy.ActionRead, objects)
|
||||
require.ErrorIs(t, err, context.Canceled)
|
||||
})
|
||||
})
|
||||
@@ -150,7 +151,7 @@ func TestFilter(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Name string
|
||||
Actor Subject
|
||||
Action Action
|
||||
Action policy.Action
|
||||
ObjectType string
|
||||
}{
|
||||
{
|
||||
@@ -160,7 +161,7 @@ func TestFilter(t *testing.T) {
|
||||
Roles: RoleNames{},
|
||||
},
|
||||
ObjectType: ResourceWorkspace.Type,
|
||||
Action: ActionRead,
|
||||
Action: policy.ActionRead,
|
||||
},
|
||||
{
|
||||
Name: "Admin",
|
||||
@@ -169,7 +170,7 @@ func TestFilter(t *testing.T) {
|
||||
Roles: RoleNames{RoleOrgMember(orgIDs[0]), "auditor", RoleOwner(), RoleMember()},
|
||||
},
|
||||
ObjectType: ResourceWorkspace.Type,
|
||||
Action: ActionRead,
|
||||
Action: policy.ActionRead,
|
||||
},
|
||||
{
|
||||
Name: "OrgAdmin",
|
||||
@@ -178,7 +179,7 @@ func TestFilter(t *testing.T) {
|
||||
Roles: RoleNames{RoleOrgMember(orgIDs[0]), RoleOrgAdmin(orgIDs[0]), RoleMember()},
|
||||
},
|
||||
ObjectType: ResourceWorkspace.Type,
|
||||
Action: ActionRead,
|
||||
Action: policy.ActionRead,
|
||||
},
|
||||
{
|
||||
Name: "OrgMember",
|
||||
@@ -187,7 +188,7 @@ func TestFilter(t *testing.T) {
|
||||
Roles: RoleNames{RoleOrgMember(orgIDs[0]), RoleOrgMember(orgIDs[1]), RoleMember()},
|
||||
},
|
||||
ObjectType: ResourceWorkspace.Type,
|
||||
Action: ActionRead,
|
||||
Action: policy.ActionRead,
|
||||
},
|
||||
{
|
||||
Name: "ManyRoles",
|
||||
@@ -203,7 +204,7 @@ func TestFilter(t *testing.T) {
|
||||
},
|
||||
},
|
||||
ObjectType: ResourceWorkspace.Type,
|
||||
Action: ActionRead,
|
||||
Action: policy.ActionRead,
|
||||
},
|
||||
{
|
||||
Name: "SiteMember",
|
||||
@@ -212,7 +213,7 @@ func TestFilter(t *testing.T) {
|
||||
Roles: RoleNames{RoleMember()},
|
||||
},
|
||||
ObjectType: ResourceUser.Type,
|
||||
Action: ActionRead,
|
||||
Action: policy.ActionRead,
|
||||
},
|
||||
{
|
||||
Name: "ReadOrgs",
|
||||
@@ -227,7 +228,7 @@ func TestFilter(t *testing.T) {
|
||||
},
|
||||
},
|
||||
ObjectType: ResourceOrganization.Type,
|
||||
Action: ActionRead,
|
||||
Action: policy.ActionRead,
|
||||
},
|
||||
{
|
||||
Name: "ScopeApplicationConnect",
|
||||
@@ -236,7 +237,7 @@ func TestFilter(t *testing.T) {
|
||||
Roles: RoleNames{RoleOrgMember(orgIDs[0]), "auditor", RoleOwner(), RoleMember()},
|
||||
},
|
||||
ObjectType: ResourceWorkspace.Type,
|
||||
Action: ActionRead,
|
||||
Action: policy.ActionRead,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -263,7 +264,7 @@ func TestFilter(t *testing.T) {
|
||||
var allowedCount int
|
||||
for i, obj := range localObjects {
|
||||
obj.Type = tc.ObjectType
|
||||
err := auth.Authorize(ctx, actor, ActionRead, obj.RBACObject())
|
||||
err := auth.Authorize(ctx, actor, policy.ActionRead, obj.RBACObject())
|
||||
obj.Allowed = err == nil
|
||||
if err == nil {
|
||||
allowedCount++
|
||||
@@ -301,64 +302,64 @@ func TestAuthorizeDomain(t *testing.T) {
|
||||
|
||||
testAuthorize(t, "UserACLList", user, []authTestCase{
|
||||
{
|
||||
resource: ResourceWorkspace.WithOwner(unuseID.String()).InOrg(unuseID).WithACLUserList(map[string][]Action{
|
||||
resource: ResourceWorkspace.WithOwner(unuseID.String()).InOrg(unuseID).WithACLUserList(map[string][]policy.Action{
|
||||
user.ID: AllActions(),
|
||||
}),
|
||||
actions: AllActions(),
|
||||
allow: true,
|
||||
},
|
||||
{
|
||||
resource: ResourceWorkspace.WithOwner(unuseID.String()).InOrg(unuseID).WithACLUserList(map[string][]Action{
|
||||
resource: ResourceWorkspace.WithOwner(unuseID.String()).InOrg(unuseID).WithACLUserList(map[string][]policy.Action{
|
||||
user.ID: {WildcardSymbol},
|
||||
}),
|
||||
actions: AllActions(),
|
||||
allow: true,
|
||||
},
|
||||
{
|
||||
resource: ResourceWorkspace.WithOwner(unuseID.String()).InOrg(unuseID).WithACLUserList(map[string][]Action{
|
||||
user.ID: {ActionRead, ActionUpdate},
|
||||
resource: ResourceWorkspace.WithOwner(unuseID.String()).InOrg(unuseID).WithACLUserList(map[string][]policy.Action{
|
||||
user.ID: {policy.ActionRead, policy.ActionUpdate},
|
||||
}),
|
||||
actions: []Action{ActionCreate, ActionDelete},
|
||||
actions: []policy.Action{policy.ActionCreate, policy.ActionDelete},
|
||||
allow: false,
|
||||
},
|
||||
{
|
||||
// By default users cannot update templates
|
||||
resource: ResourceTemplate.InOrg(defOrg).WithACLUserList(map[string][]Action{
|
||||
user.ID: {ActionUpdate},
|
||||
resource: ResourceTemplate.InOrg(defOrg).WithACLUserList(map[string][]policy.Action{
|
||||
user.ID: {policy.ActionUpdate},
|
||||
}),
|
||||
actions: []Action{ActionUpdate},
|
||||
actions: []policy.Action{policy.ActionUpdate},
|
||||
allow: true,
|
||||
},
|
||||
})
|
||||
|
||||
testAuthorize(t, "GroupACLList", user, []authTestCase{
|
||||
{
|
||||
resource: ResourceWorkspace.WithOwner(unuseID.String()).InOrg(defOrg).WithGroupACL(map[string][]Action{
|
||||
resource: ResourceWorkspace.WithOwner(unuseID.String()).InOrg(defOrg).WithGroupACL(map[string][]policy.Action{
|
||||
allUsersGroup: AllActions(),
|
||||
}),
|
||||
actions: AllActions(),
|
||||
allow: true,
|
||||
},
|
||||
{
|
||||
resource: ResourceWorkspace.WithOwner(unuseID.String()).InOrg(defOrg).WithGroupACL(map[string][]Action{
|
||||
resource: ResourceWorkspace.WithOwner(unuseID.String()).InOrg(defOrg).WithGroupACL(map[string][]policy.Action{
|
||||
allUsersGroup: {WildcardSymbol},
|
||||
}),
|
||||
actions: AllActions(),
|
||||
allow: true,
|
||||
},
|
||||
{
|
||||
resource: ResourceWorkspace.WithOwner(unuseID.String()).InOrg(defOrg).WithGroupACL(map[string][]Action{
|
||||
allUsersGroup: {ActionRead, ActionUpdate},
|
||||
resource: ResourceWorkspace.WithOwner(unuseID.String()).InOrg(defOrg).WithGroupACL(map[string][]policy.Action{
|
||||
allUsersGroup: {policy.ActionRead, policy.ActionUpdate},
|
||||
}),
|
||||
actions: []Action{ActionCreate, ActionDelete},
|
||||
actions: []policy.Action{policy.ActionCreate, policy.ActionDelete},
|
||||
allow: false,
|
||||
},
|
||||
{
|
||||
// By default users cannot update templates
|
||||
resource: ResourceTemplate.InOrg(defOrg).WithGroupACL(map[string][]Action{
|
||||
allUsersGroup: {ActionUpdate},
|
||||
resource: ResourceTemplate.InOrg(defOrg).WithGroupACL(map[string][]policy.Action{
|
||||
allUsersGroup: {policy.ActionUpdate},
|
||||
}),
|
||||
actions: []Action{ActionUpdate},
|
||||
actions: []policy.Action{policy.ActionUpdate},
|
||||
allow: true,
|
||||
},
|
||||
})
|
||||
@@ -509,7 +510,7 @@ func TestAuthorizeDomain(t *testing.T) {
|
||||
testAuthorize(t, "ApplicationToken", user,
|
||||
// Create (connect) Actions
|
||||
cases(func(c authTestCase) authTestCase {
|
||||
c.actions = []Action{ActionCreate}
|
||||
c.actions = []policy.Action{policy.ActionCreate}
|
||||
return c
|
||||
}, []authTestCase{
|
||||
// Org + me
|
||||
@@ -537,7 +538,7 @@ func TestAuthorizeDomain(t *testing.T) {
|
||||
}),
|
||||
// Not create actions
|
||||
cases(func(c authTestCase) authTestCase {
|
||||
c.actions = []Action{ActionRead, ActionUpdate, ActionDelete}
|
||||
c.actions = []policy.Action{policy.ActionRead, policy.ActionUpdate, policy.ActionDelete}
|
||||
c.allow = false
|
||||
return c
|
||||
}, []authTestCase{
|
||||
@@ -566,7 +567,7 @@ func TestAuthorizeDomain(t *testing.T) {
|
||||
}),
|
||||
// Other Objects
|
||||
cases(func(c authTestCase) authTestCase {
|
||||
c.actions = []Action{ActionCreate, ActionRead, ActionUpdate, ActionDelete}
|
||||
c.actions = []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete}
|
||||
c.allow = false
|
||||
return c
|
||||
}, []authTestCase{
|
||||
@@ -607,14 +608,14 @@ func TestAuthorizeDomain(t *testing.T) {
|
||||
defOrg.String(): {{
|
||||
Negate: false,
|
||||
ResourceType: "*",
|
||||
Action: ActionRead,
|
||||
Action: policy.ActionRead,
|
||||
}},
|
||||
},
|
||||
User: []Permission{
|
||||
{
|
||||
Negate: false,
|
||||
ResourceType: "*",
|
||||
Action: ActionRead,
|
||||
Action: policy.ActionRead,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -623,7 +624,7 @@ func TestAuthorizeDomain(t *testing.T) {
|
||||
|
||||
testAuthorize(t, "ReadOnly", user,
|
||||
cases(func(c authTestCase) authTestCase {
|
||||
c.actions = []Action{ActionRead}
|
||||
c.actions = []policy.Action{policy.ActionRead}
|
||||
return c
|
||||
}, []authTestCase{
|
||||
// Read
|
||||
@@ -653,7 +654,7 @@ func TestAuthorizeDomain(t *testing.T) {
|
||||
|
||||
// Pass non-read actions
|
||||
cases(func(c authTestCase) authTestCase {
|
||||
c.actions = []Action{ActionCreate, ActionUpdate, ActionDelete}
|
||||
c.actions = []policy.Action{policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete}
|
||||
c.allow = false
|
||||
return c
|
||||
}, []authTestCase{
|
||||
@@ -822,7 +823,7 @@ func TestAuthorizeScope(t *testing.T) {
|
||||
|
||||
testAuthorize(t, "Admin_ScopeApplicationConnect", user,
|
||||
cases(func(c authTestCase) authTestCase {
|
||||
c.actions = []Action{ActionRead, ActionUpdate, ActionDelete}
|
||||
c.actions = []policy.Action{policy.ActionRead, policy.ActionUpdate, policy.ActionDelete}
|
||||
return c
|
||||
}, []authTestCase{
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID), allow: false},
|
||||
@@ -839,9 +840,9 @@ func TestAuthorizeScope(t *testing.T) {
|
||||
}),
|
||||
// Allowed by scope:
|
||||
[]authTestCase{
|
||||
{resource: ResourceWorkspaceApplicationConnect.InOrg(defOrg).WithOwner("not-me"), actions: []Action{ActionCreate}, allow: true},
|
||||
{resource: ResourceWorkspaceApplicationConnect.InOrg(defOrg).WithOwner(user.ID), actions: []Action{ActionCreate}, allow: true},
|
||||
{resource: ResourceWorkspaceApplicationConnect.InOrg(unusedID).WithOwner("not-me"), actions: []Action{ActionCreate}, allow: true},
|
||||
{resource: ResourceWorkspaceApplicationConnect.InOrg(defOrg).WithOwner("not-me"), actions: []policy.Action{policy.ActionCreate}, allow: true},
|
||||
{resource: ResourceWorkspaceApplicationConnect.InOrg(defOrg).WithOwner(user.ID), actions: []policy.Action{policy.ActionCreate}, allow: true},
|
||||
{resource: ResourceWorkspaceApplicationConnect.InOrg(unusedID).WithOwner("not-me"), actions: []policy.Action{policy.ActionCreate}, allow: true},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -856,7 +857,7 @@ func TestAuthorizeScope(t *testing.T) {
|
||||
|
||||
testAuthorize(t, "User_ScopeApplicationConnect", user,
|
||||
cases(func(c authTestCase) authTestCase {
|
||||
c.actions = []Action{ActionRead, ActionUpdate, ActionDelete}
|
||||
c.actions = []policy.Action{policy.ActionRead, policy.ActionUpdate, policy.ActionDelete}
|
||||
c.allow = false
|
||||
return c
|
||||
}, []authTestCase{
|
||||
@@ -874,9 +875,9 @@ func TestAuthorizeScope(t *testing.T) {
|
||||
}),
|
||||
// Allowed by scope:
|
||||
[]authTestCase{
|
||||
{resource: ResourceWorkspaceApplicationConnect.InOrg(defOrg).WithOwner(user.ID), actions: []Action{ActionCreate}, allow: true},
|
||||
{resource: ResourceWorkspaceApplicationConnect.InOrg(defOrg).WithOwner("not-me"), actions: []Action{ActionCreate}, allow: false},
|
||||
{resource: ResourceWorkspaceApplicationConnect.InOrg(unusedID).WithOwner("not-me"), actions: []Action{ActionCreate}, allow: false},
|
||||
{resource: ResourceWorkspaceApplicationConnect.InOrg(defOrg).WithOwner(user.ID), actions: []policy.Action{policy.ActionCreate}, allow: true},
|
||||
{resource: ResourceWorkspaceApplicationConnect.InOrg(defOrg).WithOwner("not-me"), actions: []policy.Action{policy.ActionCreate}, allow: false},
|
||||
{resource: ResourceWorkspaceApplicationConnect.InOrg(unusedID).WithOwner("not-me"), actions: []policy.Action{policy.ActionCreate}, allow: false},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -891,9 +892,9 @@ func TestAuthorizeScope(t *testing.T) {
|
||||
Role: Role{
|
||||
Name: "workspace_agent",
|
||||
DisplayName: "Workspace Agent",
|
||||
Site: Permissions(map[string][]Action{
|
||||
Site: Permissions(map[string][]policy.Action{
|
||||
// Only read access for workspaces.
|
||||
ResourceWorkspace.Type: {ActionRead},
|
||||
ResourceWorkspace.Type: {policy.ActionRead},
|
||||
}),
|
||||
Org: map[string][]Permission{},
|
||||
User: []Permission{},
|
||||
@@ -905,7 +906,7 @@ func TestAuthorizeScope(t *testing.T) {
|
||||
testAuthorize(t, "User_WorkspaceAgent", user,
|
||||
// Test cases without ID
|
||||
cases(func(c authTestCase) authTestCase {
|
||||
c.actions = []Action{ActionCreate, ActionUpdate, ActionDelete}
|
||||
c.actions = []policy.Action{policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete}
|
||||
c.allow = false
|
||||
return c
|
||||
}, []authTestCase{
|
||||
@@ -924,7 +925,7 @@ func TestAuthorizeScope(t *testing.T) {
|
||||
|
||||
// Test all cases with the workspace id
|
||||
cases(func(c authTestCase) authTestCase {
|
||||
c.actions = []Action{ActionCreate, ActionUpdate, ActionDelete}
|
||||
c.actions = []policy.Action{policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete}
|
||||
c.allow = false
|
||||
c.resource.WithID(workspaceID)
|
||||
return c
|
||||
@@ -943,7 +944,7 @@ func TestAuthorizeScope(t *testing.T) {
|
||||
}),
|
||||
// Test cases with random ids. These should always fail from the scope.
|
||||
cases(func(c authTestCase) authTestCase {
|
||||
c.actions = []Action{ActionRead, ActionCreate, ActionUpdate, ActionDelete}
|
||||
c.actions = []policy.Action{policy.ActionRead, policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete}
|
||||
c.allow = false
|
||||
c.resource.WithID(uuid.New())
|
||||
return c
|
||||
@@ -962,10 +963,10 @@ func TestAuthorizeScope(t *testing.T) {
|
||||
}),
|
||||
// Allowed by scope:
|
||||
[]authTestCase{
|
||||
{resource: ResourceWorkspace.WithID(workspaceID).InOrg(defOrg).WithOwner(user.ID), actions: []Action{ActionRead}, allow: true},
|
||||
{resource: ResourceWorkspace.WithID(workspaceID).InOrg(defOrg).WithOwner(user.ID), actions: []policy.Action{policy.ActionRead}, allow: true},
|
||||
// The scope will return true, but the user perms return false for resources not owned by the user.
|
||||
{resource: ResourceWorkspace.WithID(workspaceID).InOrg(defOrg).WithOwner("not-me"), actions: []Action{ActionRead}, allow: false},
|
||||
{resource: ResourceWorkspace.WithID(workspaceID).InOrg(unusedID).WithOwner("not-me"), actions: []Action{ActionRead}, allow: false},
|
||||
{resource: ResourceWorkspace.WithID(workspaceID).InOrg(defOrg).WithOwner("not-me"), actions: []policy.Action{policy.ActionRead}, allow: false},
|
||||
{resource: ResourceWorkspace.WithID(workspaceID).InOrg(unusedID).WithOwner("not-me"), actions: []policy.Action{policy.ActionRead}, allow: false},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -980,9 +981,9 @@ func TestAuthorizeScope(t *testing.T) {
|
||||
Role: Role{
|
||||
Name: "create_workspace",
|
||||
DisplayName: "Create Workspace",
|
||||
Site: Permissions(map[string][]Action{
|
||||
Site: Permissions(map[string][]policy.Action{
|
||||
// Only read access for workspaces.
|
||||
ResourceWorkspace.Type: {ActionCreate},
|
||||
ResourceWorkspace.Type: {policy.ActionCreate},
|
||||
}),
|
||||
Org: map[string][]Permission{},
|
||||
User: []Permission{},
|
||||
@@ -995,7 +996,7 @@ func TestAuthorizeScope(t *testing.T) {
|
||||
testAuthorize(t, "CreatWorkspaceScope", user,
|
||||
// All these cases will fail because a resource ID is set.
|
||||
cases(func(c authTestCase) authTestCase {
|
||||
c.actions = []Action{ActionCreate, ActionRead, ActionUpdate, ActionDelete}
|
||||
c.actions = []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete}
|
||||
c.allow = false
|
||||
c.resource.ID = uuid.NewString()
|
||||
return c
|
||||
@@ -1015,10 +1016,10 @@ func TestAuthorizeScope(t *testing.T) {
|
||||
|
||||
// Test create allowed by scope:
|
||||
[]authTestCase{
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID), actions: []Action{ActionCreate}, allow: true},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID), actions: []policy.Action{policy.ActionCreate}, allow: true},
|
||||
// The scope will return true, but the user perms return false for resources not owned by the user.
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner("not-me"), actions: []Action{ActionCreate}, allow: false},
|
||||
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner("not-me"), actions: []Action{ActionCreate}, allow: false},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner("not-me"), actions: []policy.Action{policy.ActionCreate}, allow: false},
|
||||
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner("not-me"), actions: []policy.Action{policy.ActionCreate}, allow: false},
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -1036,7 +1037,7 @@ func cases(opt func(c authTestCase) authTestCase, cases []authTestCase) []authTe
|
||||
|
||||
type authTestCase struct {
|
||||
resource Object
|
||||
actions []Action
|
||||
actions []policy.Action
|
||||
allow bool
|
||||
}
|
||||
|
||||
@@ -1127,16 +1128,16 @@ func must[T any](value T, err error) T {
|
||||
}
|
||||
|
||||
type MockAuthorizer struct {
|
||||
AuthorizeFunc func(context.Context, Subject, Action, Object) error
|
||||
AuthorizeFunc func(context.Context, Subject, policy.Action, Object) error
|
||||
}
|
||||
|
||||
var _ Authorizer = (*MockAuthorizer)(nil)
|
||||
|
||||
func (d *MockAuthorizer) Authorize(ctx context.Context, s Subject, a Action, o Object) error {
|
||||
func (d *MockAuthorizer) Authorize(ctx context.Context, s Subject, a policy.Action, o Object) error {
|
||||
return d.AuthorizeFunc(ctx, s, a, o)
|
||||
}
|
||||
|
||||
func (d *MockAuthorizer) Prepare(_ context.Context, subject Subject, action Action, _ string) (PreparedAuthorized, error) {
|
||||
func (d *MockAuthorizer) Prepare(_ context.Context, subject Subject, action policy.Action, _ string) (PreparedAuthorized, error) {
|
||||
return &mockPreparedAuthorizer{
|
||||
Original: d,
|
||||
Subject: subject,
|
||||
@@ -1152,7 +1153,7 @@ type mockPreparedAuthorizer struct {
|
||||
sync.RWMutex
|
||||
Original *MockAuthorizer
|
||||
Subject Subject
|
||||
Action Action
|
||||
Action policy.Action
|
||||
}
|
||||
|
||||
func (f *mockPreparedAuthorizer) Authorize(ctx context.Context, object Object) error {
|
||||
|
||||
+19
-18
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/coder/coder/v2/coderd/coderdtest"
|
||||
"github.com/coder/coder/v2/coderd/rbac"
|
||||
"github.com/coder/coder/v2/coderd/rbac/policy"
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
)
|
||||
|
||||
@@ -167,7 +168,7 @@ func BenchmarkRBACAuthorize(b *testing.B) {
|
||||
objects := benchmarkSetup(orgs, users, b.N)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
allowed := authorizer.Authorize(context.Background(), c.Actor, rbac.ActionRead, objects[b.N%len(objects)])
|
||||
allowed := authorizer.Authorize(context.Background(), c.Actor, policy.ActionRead, objects[b.N%len(objects)])
|
||||
_ = allowed
|
||||
}
|
||||
})
|
||||
@@ -191,30 +192,30 @@ func BenchmarkRBACAuthorizeGroups(b *testing.B) {
|
||||
// Same benchmark cases, but this time groups will be used to match.
|
||||
// Some '*' permissions will still match, but using a fake action reduces
|
||||
// the chance.
|
||||
neverMatchAction := rbac.Action("never-match-action")
|
||||
neverMatchAction := policy.Action("never-match-action")
|
||||
for _, c := range benchCases {
|
||||
b.Run(c.Name+"GroupACL", func(b *testing.B) {
|
||||
userGroupAllow := uuid.NewString()
|
||||
c.Actor.Groups = append(c.Actor.Groups, userGroupAllow)
|
||||
c.Actor.Scope = rbac.ScopeAll
|
||||
objects := benchmarkSetup(orgs, users, b.N, func(object rbac.Object) rbac.Object {
|
||||
m := map[string][]rbac.Action{
|
||||
m := map[string][]policy.Action{
|
||||
// Add the user's group
|
||||
// Noise
|
||||
uuid.NewString(): {rbac.ActionCreate, rbac.ActionRead, rbac.ActionUpdate, rbac.ActionDelete},
|
||||
uuid.NewString(): {rbac.ActionCreate, rbac.ActionRead, rbac.ActionUpdate},
|
||||
uuid.NewString(): {rbac.ActionCreate, rbac.ActionRead},
|
||||
uuid.NewString(): {rbac.ActionCreate},
|
||||
uuid.NewString(): {rbac.ActionRead, rbac.ActionUpdate, rbac.ActionDelete},
|
||||
uuid.NewString(): {rbac.ActionRead, rbac.ActionUpdate},
|
||||
uuid.NewString(): {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
||||
uuid.NewString(): {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate},
|
||||
uuid.NewString(): {policy.ActionCreate, policy.ActionRead},
|
||||
uuid.NewString(): {policy.ActionCreate},
|
||||
uuid.NewString(): {policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
||||
uuid.NewString(): {policy.ActionRead, policy.ActionUpdate},
|
||||
}
|
||||
for _, g := range c.Actor.Groups {
|
||||
// Every group the user is in will be added, but it will not match the perms. This makes the
|
||||
// authorizer look at many groups before finding the one that matches.
|
||||
m[g] = []rbac.Action{rbac.ActionCreate, rbac.ActionRead, rbac.ActionUpdate, rbac.ActionDelete}
|
||||
m[g] = []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete}
|
||||
}
|
||||
// This is the only group that will give permission.
|
||||
m[userGroupAllow] = []rbac.Action{neverMatchAction}
|
||||
m[userGroupAllow] = []policy.Action{neverMatchAction}
|
||||
return object.WithGroupACL(m)
|
||||
})
|
||||
b.ResetTimer()
|
||||
@@ -244,7 +245,7 @@ func BenchmarkRBACFilter(b *testing.B) {
|
||||
b.Run("PrepareOnly-"+c.Name, func(b *testing.B) {
|
||||
obType := rbac.ResourceWorkspace.Type
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := authorizer.Prepare(context.Background(), c.Actor, rbac.ActionRead, obType)
|
||||
_, err := authorizer.Prepare(context.Background(), c.Actor, policy.ActionRead, obType)
|
||||
require.NoError(b, err)
|
||||
}
|
||||
})
|
||||
@@ -254,7 +255,7 @@ func BenchmarkRBACFilter(b *testing.B) {
|
||||
b.Run(c.Name, func(b *testing.B) {
|
||||
objects := benchmarkSetup(orgs, users, b.N)
|
||||
b.ResetTimer()
|
||||
allowed, err := rbac.Filter(context.Background(), authorizer, c.Actor, rbac.ActionRead, objects)
|
||||
allowed, err := rbac.Filter(context.Background(), authorizer, c.Actor, policy.ActionRead, objects)
|
||||
require.NoError(b, err)
|
||||
_ = allowed
|
||||
})
|
||||
@@ -263,9 +264,9 @@ func BenchmarkRBACFilter(b *testing.B) {
|
||||
|
||||
func benchmarkSetup(orgs []uuid.UUID, users []uuid.UUID, size int, opts ...func(object rbac.Object) rbac.Object) []rbac.Object {
|
||||
// Create a "random" but deterministic set of objects.
|
||||
aclList := map[string][]rbac.Action{
|
||||
uuid.NewString(): {rbac.ActionRead, rbac.ActionUpdate},
|
||||
uuid.NewString(): {rbac.ActionCreate},
|
||||
aclList := map[string][]policy.Action{
|
||||
uuid.NewString(): {policy.ActionRead, policy.ActionUpdate},
|
||||
uuid.NewString(): {policy.ActionCreate},
|
||||
}
|
||||
objectList := make([]rbac.Object, size)
|
||||
for i := range objectList {
|
||||
@@ -297,7 +298,7 @@ func BenchmarkCacher(b *testing.B) {
|
||||
var (
|
||||
subj rbac.Subject
|
||||
obj rbac.Object
|
||||
action rbac.Action
|
||||
action policy.Action
|
||||
)
|
||||
for i := 0; i < b.N; i++ {
|
||||
if i%rat == 0 {
|
||||
@@ -359,7 +360,7 @@ func TestCacher(t *testing.T) {
|
||||
var (
|
||||
ctx = testutil.Context(t, testutil.WaitShort)
|
||||
authOut = make(chan error, 1) // buffered to not block
|
||||
authorizeFunc = func(ctx context.Context, subject rbac.Subject, action rbac.Action, object rbac.Object) error {
|
||||
authorizeFunc = func(ctx context.Context, subject rbac.Subject, action policy.Action, object rbac.Object) error {
|
||||
// Just return what you're told.
|
||||
return testutil.RequireRecvCtx(ctx, t, authOut)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/httpapi/httpapiconstraints"
|
||||
"github.com/coder/coder/v2/coderd/rbac/policy"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -28,7 +29,7 @@ type UnauthorizedError struct {
|
||||
|
||||
// These fields are for debugging purposes.
|
||||
subject Subject
|
||||
action Action
|
||||
action policy.Action
|
||||
// Note only the object type is set for partial execution.
|
||||
object Object
|
||||
|
||||
@@ -52,7 +53,7 @@ func IsUnauthorizedError(err error) bool {
|
||||
// ForbiddenWithInternal creates a new error that will return a simple
|
||||
// "forbidden" to the client, logging internally the more detailed message
|
||||
// provided.
|
||||
func ForbiddenWithInternal(internal error, subject Subject, action Action, object Object, output rego.ResultSet) *UnauthorizedError {
|
||||
func ForbiddenWithInternal(internal error, subject Subject, action policy.Action, object Object, output rego.ResultSet) *UnauthorizedError {
|
||||
return &UnauthorizedError{
|
||||
internal: internal,
|
||||
subject: subject,
|
||||
|
||||
@@ -2,6 +2,8 @@ package rbac
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/rbac/policy"
|
||||
)
|
||||
|
||||
const WildcardSymbol = "*"
|
||||
@@ -250,8 +252,8 @@ type Object struct {
|
||||
// Type is "workspace", "project", "app", etc
|
||||
Type string `json:"type"`
|
||||
|
||||
ACLUserList map[string][]Action ` json:"acl_user_list"`
|
||||
ACLGroupList map[string][]Action ` json:"acl_group_list"`
|
||||
ACLUserList map[string][]policy.Action ` json:"acl_user_list"`
|
||||
ACLGroupList map[string][]policy.Action ` json:"acl_group_list"`
|
||||
}
|
||||
|
||||
func (z Object) Equal(b Object) bool {
|
||||
@@ -279,7 +281,7 @@ func (z Object) Equal(b Object) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func equalACLLists(a, b map[string][]Action) bool {
|
||||
func equalACLLists(a, b map[string][]policy.Action) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
@@ -307,8 +309,8 @@ func (z Object) All() Object {
|
||||
Owner: "",
|
||||
OrgID: "",
|
||||
Type: z.Type,
|
||||
ACLUserList: map[string][]Action{},
|
||||
ACLGroupList: map[string][]Action{},
|
||||
ACLUserList: map[string][]policy.Action{},
|
||||
ACLGroupList: map[string][]policy.Action{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -359,7 +361,7 @@ func (z Object) WithOwner(ownerID string) Object {
|
||||
}
|
||||
|
||||
// WithACLUserList adds an ACL list to a given object
|
||||
func (z Object) WithACLUserList(acl map[string][]Action) Object {
|
||||
func (z Object) WithACLUserList(acl map[string][]policy.Action) Object {
|
||||
return Object{
|
||||
ID: z.ID,
|
||||
Owner: z.Owner,
|
||||
@@ -370,7 +372,7 @@ func (z Object) WithACLUserList(acl map[string][]Action) Object {
|
||||
}
|
||||
}
|
||||
|
||||
func (z Object) WithGroupACL(groups map[string][]Action) Object {
|
||||
func (z Object) WithGroupACL(groups map[string][]policy.Action) Object {
|
||||
return Object{
|
||||
ID: z.ID,
|
||||
Owner: z.Owner,
|
||||
|
||||
+28
-27
@@ -4,6 +4,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/rbac"
|
||||
"github.com/coder/coder/v2/coderd/rbac/policy"
|
||||
"github.com/coder/coder/v2/coderd/util/slice"
|
||||
)
|
||||
|
||||
@@ -24,8 +25,8 @@ func TestObjectEqual(t *testing.T) {
|
||||
{
|
||||
Name: "NilVs0",
|
||||
A: rbac.Object{
|
||||
ACLGroupList: map[string][]rbac.Action{},
|
||||
ACLUserList: map[string][]rbac.Action{},
|
||||
ACLGroupList: map[string][]policy.Action{},
|
||||
ACLUserList: map[string][]policy.Action{},
|
||||
},
|
||||
B: rbac.Object{},
|
||||
Expected: true,
|
||||
@@ -37,16 +38,16 @@ func TestObjectEqual(t *testing.T) {
|
||||
Owner: "owner",
|
||||
OrgID: "orgID",
|
||||
Type: "type",
|
||||
ACLUserList: map[string][]rbac.Action{},
|
||||
ACLGroupList: map[string][]rbac.Action{},
|
||||
ACLUserList: map[string][]policy.Action{},
|
||||
ACLGroupList: map[string][]policy.Action{},
|
||||
},
|
||||
B: rbac.Object{
|
||||
ID: "id",
|
||||
Owner: "owner",
|
||||
OrgID: "orgID",
|
||||
Type: "type",
|
||||
ACLUserList: map[string][]rbac.Action{},
|
||||
ACLGroupList: map[string][]rbac.Action{},
|
||||
ACLUserList: map[string][]policy.Action{},
|
||||
ACLGroupList: map[string][]policy.Action{},
|
||||
},
|
||||
Expected: true,
|
||||
},
|
||||
@@ -93,13 +94,13 @@ func TestObjectEqual(t *testing.T) {
|
||||
{
|
||||
Name: "DifferentACLUserList",
|
||||
A: rbac.Object{
|
||||
ACLUserList: map[string][]rbac.Action{
|
||||
"user1": {rbac.ActionRead},
|
||||
ACLUserList: map[string][]policy.Action{
|
||||
"user1": {policy.ActionRead},
|
||||
},
|
||||
},
|
||||
B: rbac.Object{
|
||||
ACLUserList: map[string][]rbac.Action{
|
||||
"user2": {rbac.ActionRead},
|
||||
ACLUserList: map[string][]policy.Action{
|
||||
"user2": {policy.ActionRead},
|
||||
},
|
||||
},
|
||||
Expected: false,
|
||||
@@ -107,13 +108,13 @@ func TestObjectEqual(t *testing.T) {
|
||||
{
|
||||
Name: "ACLUserDiff#Actions",
|
||||
A: rbac.Object{
|
||||
ACLUserList: map[string][]rbac.Action{
|
||||
"user1": {rbac.ActionRead},
|
||||
ACLUserList: map[string][]policy.Action{
|
||||
"user1": {policy.ActionRead},
|
||||
},
|
||||
},
|
||||
B: rbac.Object{
|
||||
ACLUserList: map[string][]rbac.Action{
|
||||
"user1": {rbac.ActionRead, rbac.ActionUpdate},
|
||||
ACLUserList: map[string][]policy.Action{
|
||||
"user1": {policy.ActionRead, policy.ActionUpdate},
|
||||
},
|
||||
},
|
||||
Expected: false,
|
||||
@@ -121,13 +122,13 @@ func TestObjectEqual(t *testing.T) {
|
||||
{
|
||||
Name: "ACLUserDiffAction",
|
||||
A: rbac.Object{
|
||||
ACLUserList: map[string][]rbac.Action{
|
||||
"user1": {rbac.ActionRead},
|
||||
ACLUserList: map[string][]policy.Action{
|
||||
"user1": {policy.ActionRead},
|
||||
},
|
||||
},
|
||||
B: rbac.Object{
|
||||
ACLUserList: map[string][]rbac.Action{
|
||||
"user1": {rbac.ActionUpdate},
|
||||
ACLUserList: map[string][]policy.Action{
|
||||
"user1": {policy.ActionUpdate},
|
||||
},
|
||||
},
|
||||
Expected: false,
|
||||
@@ -135,14 +136,14 @@ func TestObjectEqual(t *testing.T) {
|
||||
{
|
||||
Name: "ACLUserDiff#Users",
|
||||
A: rbac.Object{
|
||||
ACLUserList: map[string][]rbac.Action{
|
||||
"user1": {rbac.ActionRead},
|
||||
ACLUserList: map[string][]policy.Action{
|
||||
"user1": {policy.ActionRead},
|
||||
},
|
||||
},
|
||||
B: rbac.Object{
|
||||
ACLUserList: map[string][]rbac.Action{
|
||||
"user1": {rbac.ActionRead},
|
||||
"user2": {rbac.ActionRead},
|
||||
ACLUserList: map[string][]policy.Action{
|
||||
"user1": {policy.ActionRead},
|
||||
"user2": {policy.ActionRead},
|
||||
},
|
||||
},
|
||||
Expected: false,
|
||||
@@ -150,13 +151,13 @@ func TestObjectEqual(t *testing.T) {
|
||||
{
|
||||
Name: "DifferentACLGroupList",
|
||||
A: rbac.Object{
|
||||
ACLGroupList: map[string][]rbac.Action{
|
||||
"group1": {rbac.ActionRead},
|
||||
ACLGroupList: map[string][]policy.Action{
|
||||
"group1": {policy.ActionRead},
|
||||
},
|
||||
},
|
||||
B: rbac.Object{
|
||||
ACLGroupList: map[string][]rbac.Action{
|
||||
"group2": {rbac.ActionRead},
|
||||
ACLGroupList: map[string][]policy.Action{
|
||||
"group2": {policy.ActionRead},
|
||||
},
|
||||
},
|
||||
Expected: false,
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package policy
|
||||
|
||||
// Action represents the allowed actions to be done on an object.
|
||||
type Action string
|
||||
|
||||
const (
|
||||
ActionCreate Action = "create"
|
||||
ActionRead Action = "read"
|
||||
ActionUpdate Action = "update"
|
||||
ActionDelete Action = "delete"
|
||||
)
|
||||
+44
-42
@@ -8,6 +8,8 @@ import (
|
||||
"github.com/open-policy-agent/opa/ast"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/rbac/policy"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -144,22 +146,22 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
|
||||
memberRole := Role{
|
||||
Name: member,
|
||||
DisplayName: "Member",
|
||||
Site: Permissions(map[string][]Action{
|
||||
ResourceRoleAssignment.Type: {ActionRead},
|
||||
Site: Permissions(map[string][]policy.Action{
|
||||
ResourceRoleAssignment.Type: {policy.ActionRead},
|
||||
// All users can see the provisioner daemons.
|
||||
ResourceProvisionerDaemon.Type: {ActionRead},
|
||||
ResourceProvisionerDaemon.Type: {policy.ActionRead},
|
||||
// All users can see OAuth2 provider applications.
|
||||
ResourceOAuth2ProviderApp.Type: {ActionRead},
|
||||
ResourceOAuth2ProviderApp.Type: {policy.ActionRead},
|
||||
}),
|
||||
Org: map[string][]Permission{},
|
||||
User: append(allPermsExcept(ResourceWorkspaceDormant, ResourceUser, ResourceOrganizationMember),
|
||||
Permissions(map[string][]Action{
|
||||
Permissions(map[string][]policy.Action{
|
||||
// Users cannot do create/update/delete on themselves, but they
|
||||
// can read their own details.
|
||||
ResourceUser.Type: {ActionRead},
|
||||
ResourceUserWorkspaceBuildParameters.Type: {ActionRead},
|
||||
ResourceUser.Type: {policy.ActionRead},
|
||||
ResourceUserWorkspaceBuildParameters.Type: {policy.ActionRead},
|
||||
// Users can create provisioner daemons scoped to themselves.
|
||||
ResourceProvisionerDaemon.Type: {ActionCreate, ActionRead, ActionUpdate},
|
||||
ResourceProvisionerDaemon.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate},
|
||||
})...,
|
||||
),
|
||||
}.withCachedRegoValue()
|
||||
@@ -167,19 +169,19 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
|
||||
auditorRole := Role{
|
||||
Name: auditor,
|
||||
DisplayName: "Auditor",
|
||||
Site: Permissions(map[string][]Action{
|
||||
Site: Permissions(map[string][]policy.Action{
|
||||
// Should be able to read all template details, even in orgs they
|
||||
// are not in.
|
||||
ResourceTemplate.Type: {ActionRead},
|
||||
ResourceTemplateInsights.Type: {ActionRead},
|
||||
ResourceAuditLog.Type: {ActionRead},
|
||||
ResourceUser.Type: {ActionRead},
|
||||
ResourceGroup.Type: {ActionRead},
|
||||
ResourceTemplate.Type: {policy.ActionRead},
|
||||
ResourceTemplateInsights.Type: {policy.ActionRead},
|
||||
ResourceAuditLog.Type: {policy.ActionRead},
|
||||
ResourceUser.Type: {policy.ActionRead},
|
||||
ResourceGroup.Type: {policy.ActionRead},
|
||||
// Allow auditors to query deployment stats and insights.
|
||||
ResourceDeploymentStats.Type: {ActionRead},
|
||||
ResourceDeploymentValues.Type: {ActionRead},
|
||||
ResourceDeploymentStats.Type: {policy.ActionRead},
|
||||
ResourceDeploymentValues.Type: {policy.ActionRead},
|
||||
// Org roles are not really used yet, so grant the perm at the site level.
|
||||
ResourceOrganizationMember.Type: {ActionRead},
|
||||
ResourceOrganizationMember.Type: {policy.ActionRead},
|
||||
}),
|
||||
Org: map[string][]Permission{},
|
||||
User: []Permission{},
|
||||
@@ -188,21 +190,21 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
|
||||
templateAdminRole := Role{
|
||||
Name: templateAdmin,
|
||||
DisplayName: "Template Admin",
|
||||
Site: Permissions(map[string][]Action{
|
||||
ResourceTemplate.Type: {ActionCreate, ActionRead, ActionUpdate, ActionDelete},
|
||||
Site: Permissions(map[string][]policy.Action{
|
||||
ResourceTemplate.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
||||
// CRUD all files, even those they did not upload.
|
||||
ResourceFile.Type: {ActionCreate, ActionRead, ActionUpdate, ActionDelete},
|
||||
ResourceWorkspace.Type: {ActionRead},
|
||||
ResourceFile.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
||||
ResourceWorkspace.Type: {policy.ActionRead},
|
||||
// CRUD to provisioner daemons for now.
|
||||
ResourceProvisionerDaemon.Type: {ActionCreate, ActionRead, ActionUpdate, ActionDelete},
|
||||
ResourceProvisionerDaemon.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
||||
// Needs to read all organizations since
|
||||
ResourceOrganization.Type: {ActionRead},
|
||||
ResourceUser.Type: {ActionRead},
|
||||
ResourceGroup.Type: {ActionRead},
|
||||
ResourceOrganization.Type: {policy.ActionRead},
|
||||
ResourceUser.Type: {policy.ActionRead},
|
||||
ResourceGroup.Type: {policy.ActionRead},
|
||||
// Org roles are not really used yet, so grant the perm at the site level.
|
||||
ResourceOrganizationMember.Type: {ActionRead},
|
||||
ResourceOrganizationMember.Type: {policy.ActionRead},
|
||||
// Template admins can read all template insights data
|
||||
ResourceTemplateInsights.Type: {ActionRead},
|
||||
ResourceTemplateInsights.Type: {policy.ActionRead},
|
||||
}),
|
||||
Org: map[string][]Permission{},
|
||||
User: []Permission{},
|
||||
@@ -211,14 +213,14 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
|
||||
userAdminRole := Role{
|
||||
Name: userAdmin,
|
||||
DisplayName: "User Admin",
|
||||
Site: Permissions(map[string][]Action{
|
||||
ResourceRoleAssignment.Type: {ActionCreate, ActionRead, ActionUpdate, ActionDelete},
|
||||
ResourceUser.Type: {ActionCreate, ActionRead, ActionUpdate, ActionDelete},
|
||||
ResourceUserData.Type: {ActionCreate, ActionRead, ActionUpdate, ActionDelete},
|
||||
ResourceUserWorkspaceBuildParameters.Type: {ActionCreate, ActionRead, ActionUpdate, ActionDelete},
|
||||
Site: Permissions(map[string][]policy.Action{
|
||||
ResourceRoleAssignment.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
||||
ResourceUser.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
||||
ResourceUserData.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
||||
ResourceUserWorkspaceBuildParameters.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
||||
// Full perms to manage org members
|
||||
ResourceOrganizationMember.Type: {ActionCreate, ActionRead, ActionUpdate, ActionDelete},
|
||||
ResourceGroup.Type: {ActionCreate, ActionRead, ActionUpdate, ActionDelete},
|
||||
ResourceOrganizationMember.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
||||
ResourceGroup.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
||||
}),
|
||||
Org: map[string][]Permission{},
|
||||
User: []Permission{},
|
||||
@@ -277,19 +279,19 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
|
||||
{
|
||||
// All org members can read the organization
|
||||
ResourceType: ResourceOrganization.Type,
|
||||
Action: ActionRead,
|
||||
Action: policy.ActionRead,
|
||||
},
|
||||
{
|
||||
// Can read available roles.
|
||||
ResourceType: ResourceOrgRoleAssignment.Type,
|
||||
Action: ActionRead,
|
||||
Action: policy.ActionRead,
|
||||
},
|
||||
},
|
||||
},
|
||||
User: []Permission{
|
||||
{
|
||||
ResourceType: ResourceOrganizationMember.Type,
|
||||
Action: ActionRead,
|
||||
Action: policy.ActionRead,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -349,9 +351,9 @@ type ExpandableRoles interface {
|
||||
// Permission is the format passed into the rego.
|
||||
type Permission struct {
|
||||
// Negate makes this a negative permission
|
||||
Negate bool `json:"negate"`
|
||||
ResourceType string `json:"resource_type"`
|
||||
Action Action `json:"action"`
|
||||
Negate bool `json:"negate"`
|
||||
ResourceType string `json:"resource_type"`
|
||||
Action policy.Action `json:"action"`
|
||||
}
|
||||
|
||||
// Role is a set of permissions at multiple levels:
|
||||
@@ -521,7 +523,7 @@ func SiteRoles() []Role {
|
||||
// ChangeRoleSet is a helper function that finds the difference of 2 sets of
|
||||
// roles. When setting a user's new roles, it is equivalent to adding and
|
||||
// removing roles. This set determines the changes, so that the appropriate
|
||||
// RBAC checks can be applied using "ActionCreate" and "ActionDelete" for
|
||||
// RBAC checks can be applied using "policy.ActionCreate" and "policy.ActionDelete" for
|
||||
// "added" and "removed" roles respectively.
|
||||
func ChangeRoleSet(from []string, to []string) (added []string, removed []string) {
|
||||
has := make(map[string]struct{})
|
||||
@@ -579,7 +581,7 @@ func roleSplit(role string) (name string, orgID string, err error) {
|
||||
|
||||
// Permissions is just a helper function to make building roles that list out resources
|
||||
// and actions a bit easier.
|
||||
func Permissions(perms map[string][]Action) []Permission {
|
||||
func Permissions(perms map[string][]policy.Action) []Permission {
|
||||
list := make([]Permission, 0, len(perms))
|
||||
for k, actions := range perms {
|
||||
for _, act := range actions {
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/open-policy-agent/opa/ast"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/rbac/policy"
|
||||
)
|
||||
|
||||
// BenchmarkRBACValueAllocation benchmarks the cost of allocating a rego input
|
||||
@@ -27,13 +29,13 @@ func BenchmarkRBACValueAllocation(b *testing.B) {
|
||||
WithID(uuid.New()).
|
||||
InOrg(uuid.New()).
|
||||
WithOwner(uuid.NewString()).
|
||||
WithGroupACL(map[string][]Action{
|
||||
uuid.NewString(): {ActionRead, ActionCreate},
|
||||
uuid.NewString(): {ActionRead, ActionCreate},
|
||||
uuid.NewString(): {ActionRead, ActionCreate},
|
||||
}).WithACLUserList(map[string][]Action{
|
||||
uuid.NewString(): {ActionRead, ActionCreate},
|
||||
uuid.NewString(): {ActionRead, ActionCreate},
|
||||
WithGroupACL(map[string][]policy.Action{
|
||||
uuid.NewString(): {policy.ActionRead, policy.ActionCreate},
|
||||
uuid.NewString(): {policy.ActionRead, policy.ActionCreate},
|
||||
uuid.NewString(): {policy.ActionRead, policy.ActionCreate},
|
||||
}).WithACLUserList(map[string][]policy.Action{
|
||||
uuid.NewString(): {policy.ActionRead, policy.ActionCreate},
|
||||
uuid.NewString(): {policy.ActionRead, policy.ActionCreate},
|
||||
})
|
||||
|
||||
jsonSubject := authSubject{
|
||||
@@ -45,7 +47,7 @@ func BenchmarkRBACValueAllocation(b *testing.B) {
|
||||
|
||||
b.Run("ManualRegoValue", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := regoInputValue(actor, ActionRead, obj)
|
||||
_, err := regoInputValue(actor, policy.ActionRead, obj)
|
||||
require.NoError(b, err)
|
||||
}
|
||||
})
|
||||
@@ -53,7 +55,7 @@ func BenchmarkRBACValueAllocation(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := ast.InterfaceToValue(map[string]interface{}{
|
||||
"subject": jsonSubject,
|
||||
"action": ActionRead,
|
||||
"action": policy.ActionRead,
|
||||
"object": obj,
|
||||
})
|
||||
require.NoError(b, err)
|
||||
@@ -90,16 +92,16 @@ func TestRegoInputValue(t *testing.T) {
|
||||
WithID(uuid.New()).
|
||||
InOrg(uuid.New()).
|
||||
WithOwner(uuid.NewString()).
|
||||
WithGroupACL(map[string][]Action{
|
||||
uuid.NewString(): {ActionRead, ActionCreate},
|
||||
uuid.NewString(): {ActionRead, ActionCreate},
|
||||
uuid.NewString(): {ActionRead, ActionCreate},
|
||||
}).WithACLUserList(map[string][]Action{
|
||||
uuid.NewString(): {ActionRead, ActionCreate},
|
||||
uuid.NewString(): {ActionRead, ActionCreate},
|
||||
WithGroupACL(map[string][]policy.Action{
|
||||
uuid.NewString(): {policy.ActionRead, policy.ActionCreate},
|
||||
uuid.NewString(): {policy.ActionRead, policy.ActionCreate},
|
||||
uuid.NewString(): {policy.ActionRead, policy.ActionCreate},
|
||||
}).WithACLUserList(map[string][]policy.Action{
|
||||
uuid.NewString(): {policy.ActionRead, policy.ActionCreate},
|
||||
uuid.NewString(): {policy.ActionRead, policy.ActionCreate},
|
||||
})
|
||||
|
||||
action := ActionRead
|
||||
action := policy.ActionRead
|
||||
|
||||
t.Run("InputValue", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
+29
-28
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/rbac"
|
||||
"github.com/coder/coder/v2/coderd/rbac/policy"
|
||||
)
|
||||
|
||||
type authSubject struct {
|
||||
@@ -35,7 +36,7 @@ func TestOwnerExec(t *testing.T) {
|
||||
|
||||
auth := rbac.NewCachingAuthorizer(prometheus.NewRegistry())
|
||||
// Exec a random workspace
|
||||
err := auth.Authorize(context.Background(), owner, rbac.ActionCreate,
|
||||
err := auth.Authorize(context.Background(), owner, policy.ActionCreate,
|
||||
rbac.ResourceWorkspaceExecution.WithID(uuid.New()).InOrg(uuid.New()).WithOwner(uuid.NewString()))
|
||||
require.ErrorAsf(t, err, &rbac.UnauthorizedError{}, "expected unauthorized error")
|
||||
})
|
||||
@@ -49,7 +50,7 @@ func TestOwnerExec(t *testing.T) {
|
||||
auth := rbac.NewCachingAuthorizer(prometheus.NewRegistry())
|
||||
|
||||
// Exec a random workspace
|
||||
err := auth.Authorize(context.Background(), owner, rbac.ActionCreate,
|
||||
err := auth.Authorize(context.Background(), owner, policy.ActionCreate,
|
||||
rbac.ResourceWorkspaceExecution.WithID(uuid.New()).InOrg(uuid.New()).WithOwner(uuid.NewString()))
|
||||
require.NoError(t, err, "expected owner can")
|
||||
})
|
||||
@@ -94,7 +95,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
// Name the test case to better locate the failing test case.
|
||||
Name string
|
||||
Resource rbac.Object
|
||||
Actions []rbac.Action
|
||||
Actions []policy.Action
|
||||
// AuthorizeMap must cover all subjects in 'requiredSubjects'.
|
||||
// This map will run an Authorize() check with the resource, action,
|
||||
// and subjects. The subjects are split into 2 categories, "true" and
|
||||
@@ -105,7 +106,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
Name: "MyUser",
|
||||
Actions: []rbac.Action{rbac.ActionRead},
|
||||
Actions: []policy.Action{policy.ActionRead},
|
||||
Resource: rbac.ResourceUserObject(currentUser),
|
||||
AuthorizeMap: map[bool][]authSubject{
|
||||
true: {orgMemberMe, owner, memberMe, templateAdmin, userAdmin},
|
||||
@@ -114,7 +115,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Name: "AUser",
|
||||
Actions: []rbac.Action{rbac.ActionCreate, rbac.ActionUpdate, rbac.ActionDelete},
|
||||
Actions: []policy.Action{policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete},
|
||||
Resource: rbac.ResourceUser,
|
||||
AuthorizeMap: map[bool][]authSubject{
|
||||
true: {owner, userAdmin},
|
||||
@@ -124,7 +125,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
{
|
||||
Name: "ReadMyWorkspaceInOrg",
|
||||
// When creating the WithID won't be set, but it does not change the result.
|
||||
Actions: []rbac.Action{rbac.ActionRead},
|
||||
Actions: []policy.Action{policy.ActionRead},
|
||||
Resource: rbac.ResourceWorkspace.WithID(workspaceID).InOrg(orgID).WithOwner(currentUser.String()),
|
||||
AuthorizeMap: map[bool][]authSubject{
|
||||
true: {owner, orgMemberMe, orgAdmin, templateAdmin},
|
||||
@@ -134,7 +135,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
{
|
||||
Name: "C_RDMyWorkspaceInOrg",
|
||||
// When creating the WithID won't be set, but it does not change the result.
|
||||
Actions: []rbac.Action{rbac.ActionCreate, rbac.ActionUpdate, rbac.ActionDelete},
|
||||
Actions: []policy.Action{policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete},
|
||||
Resource: rbac.ResourceWorkspace.WithID(workspaceID).InOrg(orgID).WithOwner(currentUser.String()),
|
||||
AuthorizeMap: map[bool][]authSubject{
|
||||
true: {owner, orgMemberMe, orgAdmin},
|
||||
@@ -144,7 +145,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
{
|
||||
Name: "MyWorkspaceInOrgExecution",
|
||||
// When creating the WithID won't be set, but it does not change the result.
|
||||
Actions: []rbac.Action{rbac.ActionCreate, rbac.ActionRead, rbac.ActionUpdate, rbac.ActionDelete},
|
||||
Actions: []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
||||
Resource: rbac.ResourceWorkspaceExecution.WithID(workspaceID).InOrg(orgID).WithOwner(currentUser.String()),
|
||||
AuthorizeMap: map[bool][]authSubject{
|
||||
true: {owner, orgMemberMe},
|
||||
@@ -154,7 +155,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
{
|
||||
Name: "MyWorkspaceInOrgAppConnect",
|
||||
// When creating the WithID won't be set, but it does not change the result.
|
||||
Actions: []rbac.Action{rbac.ActionCreate, rbac.ActionRead, rbac.ActionUpdate, rbac.ActionDelete},
|
||||
Actions: []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
||||
Resource: rbac.ResourceWorkspaceApplicationConnect.WithID(workspaceID).InOrg(orgID).WithOwner(currentUser.String()),
|
||||
AuthorizeMap: map[bool][]authSubject{
|
||||
true: {owner, orgAdmin, orgMemberMe},
|
||||
@@ -163,7 +164,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Name: "Templates",
|
||||
Actions: []rbac.Action{rbac.ActionCreate, rbac.ActionUpdate, rbac.ActionDelete},
|
||||
Actions: []policy.Action{policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete},
|
||||
Resource: rbac.ResourceTemplate.WithID(templateID).InOrg(orgID),
|
||||
AuthorizeMap: map[bool][]authSubject{
|
||||
true: {owner, orgAdmin, templateAdmin},
|
||||
@@ -172,7 +173,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Name: "ReadTemplates",
|
||||
Actions: []rbac.Action{rbac.ActionRead},
|
||||
Actions: []policy.Action{policy.ActionRead},
|
||||
Resource: rbac.ResourceTemplate.InOrg(orgID),
|
||||
AuthorizeMap: map[bool][]authSubject{
|
||||
true: {owner, orgAdmin, templateAdmin},
|
||||
@@ -181,7 +182,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Name: "Files",
|
||||
Actions: []rbac.Action{rbac.ActionCreate},
|
||||
Actions: []policy.Action{policy.ActionCreate},
|
||||
Resource: rbac.ResourceFile.WithID(fileID),
|
||||
AuthorizeMap: map[bool][]authSubject{
|
||||
true: {owner, templateAdmin},
|
||||
@@ -190,7 +191,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Name: "MyFile",
|
||||
Actions: []rbac.Action{rbac.ActionCreate, rbac.ActionRead, rbac.ActionUpdate, rbac.ActionDelete},
|
||||
Actions: []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
||||
Resource: rbac.ResourceFile.WithID(fileID).WithOwner(currentUser.String()),
|
||||
AuthorizeMap: map[bool][]authSubject{
|
||||
true: {owner, memberMe, orgMemberMe, templateAdmin},
|
||||
@@ -199,7 +200,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Name: "CreateOrganizations",
|
||||
Actions: []rbac.Action{rbac.ActionCreate},
|
||||
Actions: []policy.Action{policy.ActionCreate},
|
||||
Resource: rbac.ResourceOrganization,
|
||||
AuthorizeMap: map[bool][]authSubject{
|
||||
true: {owner},
|
||||
@@ -208,7 +209,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Name: "Organizations",
|
||||
Actions: []rbac.Action{rbac.ActionUpdate, rbac.ActionDelete},
|
||||
Actions: []policy.Action{policy.ActionUpdate, policy.ActionDelete},
|
||||
Resource: rbac.ResourceOrganization.WithID(orgID).InOrg(orgID),
|
||||
AuthorizeMap: map[bool][]authSubject{
|
||||
true: {owner, orgAdmin},
|
||||
@@ -217,7 +218,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Name: "ReadOrganizations",
|
||||
Actions: []rbac.Action{rbac.ActionRead},
|
||||
Actions: []policy.Action{policy.ActionRead},
|
||||
Resource: rbac.ResourceOrganization.WithID(orgID).InOrg(orgID),
|
||||
AuthorizeMap: map[bool][]authSubject{
|
||||
true: {owner, orgAdmin, orgMemberMe, templateAdmin},
|
||||
@@ -226,7 +227,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Name: "RoleAssignment",
|
||||
Actions: []rbac.Action{rbac.ActionCreate, rbac.ActionUpdate, rbac.ActionDelete},
|
||||
Actions: []policy.Action{policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete},
|
||||
Resource: rbac.ResourceRoleAssignment,
|
||||
AuthorizeMap: map[bool][]authSubject{
|
||||
true: {owner, userAdmin},
|
||||
@@ -235,7 +236,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Name: "ReadRoleAssignment",
|
||||
Actions: []rbac.Action{rbac.ActionRead},
|
||||
Actions: []policy.Action{policy.ActionRead},
|
||||
Resource: rbac.ResourceRoleAssignment,
|
||||
AuthorizeMap: map[bool][]authSubject{
|
||||
true: {owner, orgAdmin, orgMemberMe, otherOrgAdmin, otherOrgMember, memberMe, templateAdmin, userAdmin},
|
||||
@@ -244,7 +245,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Name: "OrgRoleAssignment",
|
||||
Actions: []rbac.Action{rbac.ActionCreate, rbac.ActionUpdate, rbac.ActionDelete},
|
||||
Actions: []policy.Action{policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete},
|
||||
Resource: rbac.ResourceOrgRoleAssignment.InOrg(orgID),
|
||||
AuthorizeMap: map[bool][]authSubject{
|
||||
true: {owner, orgAdmin},
|
||||
@@ -253,7 +254,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Name: "ReadOrgRoleAssignment",
|
||||
Actions: []rbac.Action{rbac.ActionRead},
|
||||
Actions: []policy.Action{policy.ActionRead},
|
||||
Resource: rbac.ResourceOrgRoleAssignment.InOrg(orgID),
|
||||
AuthorizeMap: map[bool][]authSubject{
|
||||
true: {owner, orgAdmin, orgMemberMe},
|
||||
@@ -262,7 +263,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Name: "APIKey",
|
||||
Actions: []rbac.Action{rbac.ActionCreate, rbac.ActionRead, rbac.ActionUpdate, rbac.ActionDelete},
|
||||
Actions: []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
||||
Resource: rbac.ResourceAPIKey.WithID(apiKeyID).WithOwner(currentUser.String()),
|
||||
AuthorizeMap: map[bool][]authSubject{
|
||||
true: {owner, orgMemberMe, memberMe},
|
||||
@@ -271,7 +272,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Name: "UserData",
|
||||
Actions: []rbac.Action{rbac.ActionCreate, rbac.ActionRead, rbac.ActionUpdate, rbac.ActionDelete},
|
||||
Actions: []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
||||
Resource: rbac.ResourceUserData.WithID(currentUser).WithOwner(currentUser.String()),
|
||||
AuthorizeMap: map[bool][]authSubject{
|
||||
true: {owner, orgMemberMe, memberMe, userAdmin},
|
||||
@@ -280,7 +281,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Name: "ManageOrgMember",
|
||||
Actions: []rbac.Action{rbac.ActionCreate, rbac.ActionUpdate, rbac.ActionDelete},
|
||||
Actions: []policy.Action{policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete},
|
||||
Resource: rbac.ResourceOrganizationMember.WithID(currentUser).InOrg(orgID).WithOwner(currentUser.String()),
|
||||
AuthorizeMap: map[bool][]authSubject{
|
||||
true: {owner, orgAdmin, userAdmin},
|
||||
@@ -289,7 +290,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Name: "ReadOrgMember",
|
||||
Actions: []rbac.Action{rbac.ActionRead},
|
||||
Actions: []policy.Action{policy.ActionRead},
|
||||
Resource: rbac.ResourceOrganizationMember.WithID(currentUser).InOrg(orgID).WithOwner(currentUser.String()),
|
||||
AuthorizeMap: map[bool][]authSubject{
|
||||
true: {owner, orgAdmin, userAdmin, orgMemberMe, templateAdmin},
|
||||
@@ -298,10 +299,10 @@ func TestRolePermissions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Name: "AllUsersGroupACL",
|
||||
Actions: []rbac.Action{rbac.ActionRead},
|
||||
Actions: []policy.Action{policy.ActionRead},
|
||||
Resource: rbac.ResourceTemplate.WithID(templateID).InOrg(orgID).WithGroupACL(
|
||||
map[string][]rbac.Action{
|
||||
orgID.String(): {rbac.ActionRead},
|
||||
map[string][]policy.Action{
|
||||
orgID.String(): {policy.ActionRead},
|
||||
}),
|
||||
|
||||
AuthorizeMap: map[bool][]authSubject{
|
||||
@@ -311,7 +312,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Name: "Groups",
|
||||
Actions: []rbac.Action{rbac.ActionRead},
|
||||
Actions: []policy.Action{policy.ActionRead},
|
||||
Resource: rbac.ResourceGroup.WithID(groupID).InOrg(orgID),
|
||||
AuthorizeMap: map[bool][]authSubject{
|
||||
true: {owner, orgAdmin, userAdmin, templateAdmin},
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"github.com/google/uuid"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/rbac/policy"
|
||||
)
|
||||
|
||||
type WorkspaceAgentScopeParams struct {
|
||||
@@ -58,7 +60,7 @@ var builtinScopes = map[ScopeName]Scope{
|
||||
Role: Role{
|
||||
Name: fmt.Sprintf("Scope_%s", ScopeAll),
|
||||
DisplayName: "All operations",
|
||||
Site: Permissions(map[string][]Action{
|
||||
Site: Permissions(map[string][]policy.Action{
|
||||
ResourceWildcard.Type: {WildcardSymbol},
|
||||
}),
|
||||
Org: map[string][]Permission{},
|
||||
@@ -71,8 +73,8 @@ var builtinScopes = map[ScopeName]Scope{
|
||||
Role: Role{
|
||||
Name: fmt.Sprintf("Scope_%s", ScopeApplicationConnect),
|
||||
DisplayName: "Ability to connect to applications",
|
||||
Site: Permissions(map[string][]Action{
|
||||
ResourceWorkspaceApplicationConnect.Type: {ActionCreate},
|
||||
Site: Permissions(map[string][]policy.Action{
|
||||
ResourceWorkspaceApplicationConnect.Type: {policy.ActionCreate},
|
||||
}),
|
||||
Org: map[string][]Permission{},
|
||||
User: []Permission{},
|
||||
|
||||
Reference in New Issue
Block a user